В докладе будет рассказано о тех фундаментальных причинах, приводящих к неоптимальному коду в продукте, будет предложен подход, лишённый найденных недостатков.
Докладываемый подход опирается на технологию Expression Templates, которая позволяет уменьшить количество действий и объём ресурсов, которые требуются для выполнения неких промежуточных действий в процессе формирования каждой записи в журнал. Эта технология используется для уменьшения количества промежуточных операций при вычислении сложных математических выражений. Новизна докладываемого подхода в том, что тот же самый принцип, на котором основана технология Expression Templates можно применить для того, чтобы целенаправленно исключить те промежуточные действия, которые в конечном итоге приводят к неоптимальному коду.
Завершается доклад обсуждением полученного эффекта, путей возможного дальнейшего развития и возможностей применения этой же технологии в других задачах.
2. БУДЕМ ГОВОРИТЬ О FRONT-END
2
СКОЛЬКО СТОИТ ОТЛАДОЧНЫЙ ВЫВОД
КТО ВИНОВАТ И ЧТО ДЕЛАТЬ
РЕЗУЛЬТАТЫ И ПЛАНЫ22
7
3
Ради большей наглядности фрагменты исходного текста C++ приведены на слайдах в упрощённом виде.
3. Или история про то, как маленькая функция раздулась до десяти
килобайт кода.
СКОЛЬКО СТОИТ ОТЛАДОЧНЫЙ ВЫВОД
4. PERFORMANCE TEAM
4
Исследует производительность программ и их влияние на
производительность ОС.
- задержки при Startup / Boot / Hibernate / Resume;
- профилирование, поиск узких мест;
- оптимизация основных сценариев;
- контроль потребления ресурсов.
Проблема: рыхлый код
5. ПРОВЕДЁМ ЭКСПЕРИМЕНТ
5
1. Возьмём реальный продукт - KIS 2015.
2. Вырежем для опытов фрагмент на 3 млн строк.
3. Сколько в нём отладочного вывода? 15 тыс строк.
4. Как изменится размер исполняемых модулей,
если выкинуть весь отладочный вывод?
10%
объёма исполняемых модулей
0,5%
строк исходного текста C++
7. Разберёмся в деталях - откуда взялось так много кода и как сделать
так, чтобы его стало меньше.
КТО ВИНОВАТ И ЧТО ДЕЛАТЬ
8. #define MY_WARN() TRACE_WARN() << "MyClass(" << this << ")::" FUNCTION " "
#define LOGGED_RETURN(code) do { MY_WARN() << (code); return (code); } while(0)
// Now replace every 'return' with 'LOGGED_RETURN'
void Service::Method()
{
try
{
TRACE_INFO() << "Trying";
DoSomething();
}
catch (...)
{
TRACE_ERR() << "Failed";
}
}
void Service::~Service()
{
TRACE_INFO() << "Done " << m_filename;
}
ЧТО ПИШУТ РАЗРАБОТЧИКИ
8
9. ЧТО ДОЛЖЕН УМЕТЬ ТРАССИРОВЩИК
9
1. Использовать синтаксис потоков вывода C++.
Поскольку он всем знаком, и давно используется в кодовой базе.
2. Выводить любые типы данных.
И чтобы разработчикам не требовалось писать ничего сложнее привычного оператора << для нового типа.
3. Работать из разных потоков (threads).
Не создавая конкуренции. Простаивать на синхронизации из-за отладочного вывода - это последнее дело.
4. Не создавать лишних исключений.
Мало кому понравится, если отладочный вывод начнёт влиять на ход работы смыслового кода.
5. Не вычислять выводимые значения, если они не нужны.
И вообще тратить минимум усилий на выражения, которые не должны выводиться.
10. ЧТО СКРЫВАЕТСЯ ЗА МАКРОСАМИ
10
if (!ShouldTrace(GET_TRACER(), LevelWarn))
(void)0;
else
MakeTraceStream(GET_TRACER(), LevelWarn) << ...
if (const TraceHolder& h = TraceHolder(GET_TRACER(), LevelWarn))
(void)0;
else
MakeTraceStream(h.tracer, h.level).SelfRef() << ...MakeTraceStream(h.tracer, h.level).SelfRef() << ...
#define MY_WARN() TRACE_WARN() << "MyClass(" << this << ")::" FUNCTION " "
11. ЧТО ВЫНУЖДЕН ДЕЛАТЬ КОМПИЛЯТОР
11
MakeTraceStream(tracer, level).SelfRef() << foo() << "data size = " << m_x ;
Не оптимальный по размеру и простоте машинный код
Необходим код
раскрутки стека
Захват критической секции
или создание буфера
Выход: требуется
деструктор
Инлайн-подстановка
операторов вывода
Вход: создание
временного объекта
MakeTraceStream(h.tracer, h.level).SelfRef() << ...
12. ЧТО БУДЕМ ОПТИМИЗИРОВАТЬ
12
Необходим код
раскрутки стека
Инлайн-подстановка
операторов вывода
Вход: создание
временного объекта3. Избавимся от повторяющегося кода.
Будь то инлайн-подстановка или инстанциация кучи сложных функций.
2. Минимизируем объём кода в точке вызова.
Особенно тех инструкций, которые выполняются при выключенном выводе.
1. Избавимся от кода обработки исключений.
Не пожертвовав при этом ни exception safety, ни thread safety.
MakeTraceStream(tracer, level).SelfRef() << foo() << "data size = " << m_x ;tracer level foo() "data size = " m_xMakeTraceStream SelfRef << << << ;
Надо вычислять на месте
Эти вычисления можно вынести в отдельную общую функцию
14. ЧТО ДАЮТ EXPRESSION TEMPLATES
14
a + b * c
ExprPlus< Ta, ExprMult< Tb, Tc > >
&a, &b, &c
тип :
содержимое :
SomeSpecialType() << level << foo() << "data size = " << m_x;
ArgumentPack<Typelist<> >
ArgumentPack<Typelist<level_t> >
ArgumentPack<Typelist<level_t, long> >
ArgumentPack<Typelist<level_t, long, const char [13]> >
ArgumentPack<Typelist<level_t, long, const char [13], int> > &a1 &a2 &a3 &a4
&a1 &a2 &a3
&a1 &a2
&a1
Они позволяют сформировать кортеж из аргументов, отложив вычисления на потом.
15. ВОЛШЕБНЫЙ ОПЕРАТОР <<=
15
template <typename TracerType, typename Typelist>
TracerType& operator<<=(TracerType& tracer, const ArgumentPack<Typelist>& args);
Задача: убрать параметризацию функции вывода по Typelist.
Заменим параметризацию типом на параметризацию данными.
ArgumentPack
const T1* p1
const T2* p2
const Tn* pn
...
{
const Descriptor* d = &DescriptorsFor<TracerType, Typelist>::head;
DoOutput<TracerType>(tracer, args.begin(), d);
}
1-2 типа несколько тысяч комбинаций
addr p1; func* f1
addr p2; func* f2
addr pn; func* fn
...
addr p1
addr p2
addr pn
...
+
func* f1
func* f2
func* fn
...
NULL
16. ВЫВОД ОДНОГО ЗНАЧЕНИЯ
16
WorkerFunc
Функция с неизменным типом аргументов, которая выводит конкретный тип данных в конкретный тип потока.
template <typename TracerType, typename ValueType>
void OutputWorker(void* tracer, addr_t valuePtr)
{
*(TracerType*)tracer << *(const ValueType*)valuePtr;
}
17. СТАТИЧЕСКАЯ ИНИЦИАЛИЗАЦИЯ СПИСКА
17
struct Descriptor
{
WorkerFunc* worker;
const Descriptor* next;
};
// DescriptorsFor consists of a single static member named 'head'.
template <typename TracerType, typename Typelist>
const Descriptor DescriptorsFor<TracerType, Typelist>::head =
{
WorkerFunc<TracerType, Typelist::Head>,
&DescriptorsFor<TracerType, Typelist::Tail>
};
Descriptor
Статически инициализируемый список из ссылок на рабочие функции.
18. ВЫВОД ВСЕГО ВЫРАЖЕНИЯ
18
template <typename TracerType>
void DoOutput(TracerType& tracer, const addr_t* args, const Descriptor* d)
{
try
{
output_traits<TracerType>::actual_type actualStream(tracer);
for (int i = 0; d; ++i, d = d->next)
d->worker(&actualStream, args[i]);
}
catch (...)
{
}
}
DoOutput
Единая функция для любых операций вывода.
19. ПРИЯТНЫЕ ОСОБЕННОСТИ
19
1. Простые типы можно класть в ArgumentPack по значению.
template <typename T> struct PackTraits : PackByRef {};
template <> struct PackTraits<int> : PackByVal {};
2. Макрос можно использовать с любым потоковым выводом.
char buf[128];
buf <<= KLTRACE_LAZY_OUTPUT() << "Hello, " << n << " Worlds!";
3. Метод годится для цепочки любых однородных операторов.
dst <<= KLTRACE_LAZY_FORMAT("Hello, %1 %2!n") % n % m_who;
filename <<= KLTRACE_LAZY_PATH() / diskRoot / m_configPath / "config.xml";
4. Можно получить несколько функций на каждый аргумент.
my_container <<= KLTRACE_LAZY_APPEND() + my_vector + my_list + my_range;
// instantiates begin() and end() for each source.
21. НЕПРИЯТНЫЕ ОСОБЕННОСТИ
21
3. Нужны специальные меры для вывода std::endl.
template <typename Char, typename Traits>
basic_ostream<Char, Traits>& endl(basic_ostream<Char, Traits>& os);
struct AbstractManipulator; // умеет инициаизироваться выражением std::endl
template <typename Typelist>
operator<<(const ArgumentPack& os, const AbstractManipulator& manip);
4. Нельзя подменять поток в процессе вычисления выражения.
template <typename AnyStream>
CommaInserterStream<AnyStream> operator<<(AnyStream& os, MyCoolManip*);
5. При выключенной оптимизации код становится только хуже.
Для эффективной упаковки аргументов в кортеж обязательно нужна инлайн-подстановка.
25. ССЫЛКИ И КОНТАКТЫ
25
"Pimp my log", Marc Eaddy http://www.youtube.com/watch?v=TS_waQZcZVc
Эти слайды доступны на http://meetingcpp.ru/
Пример кода доступен там http://meetingcpp.ru/
Игорь Гусаров Igor.Gusarov@kaspersky.com
Александр Леденев Alexander.Ledenev@kaspersky.com
Андрей Солодовников Andrey.Solodovnikov@kaspersky.com
Editor's Notes
To add background use Background button and choose from drop-down menu. Please mind the size of text areas.
To update contents automatically fill in names of dividing slides and press Update button.
Please mind the size of text areas.
To add Dividing slide use Add section, or select from Structure drop down list.
When a new Dividing slide is inserted, Content slide will be automatically updated.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
To add Dividing slide use Add section, or select from Structure drop down list.
When a new Dividing slide is inserted, Content slide will be automatically updated.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
Please mind the size of text areas.
To add Dividing slide use Add section, or select from Structure drop down list.
When a new Dividing slide is inserted, Content slide will be automatically updated.
Please mind the size of text areas.
Please mind the size of text areas.
The best way to finish your presentation is to place call to action text or contacts. Try to initiate feedback and further communication.
Please mind the size of text areas.