Post on 27-Jul-2015
ОПТИМИЗАЦИЯ ТРАССИРОВАНИЯ С ИСПОЛЬЗОВАНИЕМ EXPRESSION TEMPLATES
Игорь ГусаровSoftware Expert, Kaspersky Lab
2
БУДЕМ ГОВОРИТЬ О FRONT-END
СКОЛЬКО СТОИТ ОТЛАДОЧНЫЙ ВЫВОД
КТО ВИНОВАТ И ЧТО ДЕЛАТЬ
РЕЗУЛЬТАТЫ И ПЛАНЫ
ORGANIZATIONAL CHANGES
TO ALL FACEBOOK USERS: STAY SAFE!
OUR NEW SLED TEAM
LEGAL PRODUCT EXPERTISE TO HELP WITH PROJECT EFFORTS
14
22
7
3
27
22
17
Ради большей наглядности фрагменты исходного текста C++ приведены на слайдах в упрощённом виде.
Или история про то, как маленькая функция раздулась до десяти килобайт кода.
СКОЛЬКО СТОИТ ОТЛАДОЧНЫЙ ВЫВОД
4
PERFORMANCE TEAM
Исследует производительность программ и их влияние на производительность ОС.
- задержки при Startup / Boot / Hibernate / Resume;- профилирование, поиск узких мест;- оптимизация основных сценариев;- контроль потребления ресурсов.
Проблема: рыхлый код
5
ПРОВЕДЁМ ЭКСПЕРИМЕНТ
1. Возьмём реальный продукт - KIS 2015.
2. Вырежем для опытов фрагмент на 3 млн строк.
3. Сколько в нём отладочного вывода? 15 тыс строк.
4. Как изменится размер исполняемых модулей,если выкинуть весь отладочный вывод?
10%объёма исполняемых модулей
0,5%строк исходного текста C++
6
СИНТЕТИЧЕСКИЙ ПРИМЕРvoid TestTraceStream(){ TRACE(level) << "Hello" << ' ' << "World!"; SomeFunc();}
void TestTracePrintf(){ TRACE(level, "%s%c%s", "Hello", ' ', "World!"); SomeFunc();}
подготовка
вывод сообщения
полезная нагрузка
Разберёмся в деталях - откуда взялось так много кода и как сделать так, чтобы его стало меньше.
КТО ВИНОВАТ И ЧТО ДЕЛАТЬ
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;}
ЧТО ПИШУТ РАЗРАБОТЧИКИ
9
ЧТО ДОЛЖЕН УМЕТЬ ТРАССИРОВЩИК
1. Использовать синтаксис потоков вывода C++.Поскольку он всем знаком, и давно используется в кодовой базе.
2. Выводить любые типы данных.И чтобы разработчикам не требовалось писать ничего сложнее привычного оператора << для нового типа.
3. Работать из разных потоков (threads).Не создавая конкуренции. Простаивать на синхронизации из-за отладочного вывода - это последнее дело.
4. Не создавать лишних исключений.Мало кому понравится, если отладочный вывод начнёт влиять на ход работы смыслового кода.
5. Не вычислять выводимые значения, если они не нужны.И вообще тратить минимум усилий на выражения, которые не должны выводиться.
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
ЧТО ВЫНУЖДЕН ДЕЛАТЬ КОМПИЛЯТОР
MakeTraceStream(tracer, level).SelfRef() << foo() << "data size = " << m_x ;
Не оптимальный по размеру и простоте машинный код
Необходим код раскрутки стека
Захват критической секцииили создание буфера
Выход: требуется деструктор
Инлайн-подстановка операторов вывода
Вход: создание временного объекта
MakeTraceStream(h.tracer, h.level).SelfRef() << ...
12
ЧТО БУДЕМ ОПТИМИЗИРОВАТЬ
Необходим код раскрутки стека
Инлайн-подстановка операторов вывода
Вход: создание временного объекта3. Избавимся от повторяющегося кода.Будь то инлайн-подстановка или инстанциация кучи сложных функций.
2. Минимизируем объём кода в точке вызова.Особенно тех инструкций, которые выполняются при выключенном выводе.
1. Избавимся от кода обработки исключений.Не пожертвовав при этом ни exception safety, ни thread safety.
MakeTraceStream(tracer, level).SelfRef() << foo() << "data size = " << m_x ; tracer level foo() "data size = " m_x MakeTraceStream SelfRef << << << ;
Надо вычислять на месте
Эти вычисления можно вынести в отдельную общую функцию
13
КАК БУДЕМ ОПТИМИЗИРОВАТЬ
MakeTraceStream(tracer, level).SelfRef() << foo() << "data size = " << m_x ;
TracePut(tracer, { level, foo(), "data size = ", m_x });tracer <<= KLTRACE_LAZY_OUTPUT() << level << foo() << "data size = " << m_x;
tracer <<= SomeSpecialType() << level << foo() << "data size = " << m_x;
1
что
2
куда
3
как
14
ЧТО ДАЮТ EXPRESSION TEMPLATES
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
ВОЛШЕБНЫЙ ОПЕРАТОР <<=
template <typename TracerType, typename Typelist>TracerType& operator<<=(TracerType& tracer, const ArgumentPack<Typelist>& args);
Задача: убрать параметризацию функции вывода по Typelist.Заменим параметризацию типом на параметризацию данными.
ArgumentPackconst T1* p1const T2* p2
const Tn* pn
...
{ const Descriptor* d = &DescriptorsFor<TracerType, Typelist>::head; DoOutput<TracerType>(tracer, args.begin(), d);}
1-2 типа несколько тысяч комбинаций
addr p1; func* f1addr p2; func* f2
addr pn; func* fn
...
addr p1addr p2
addr pn
... +
func* f1
func* f2
func* fn
...
NULL
16
ВЫВОД ОДНОГО ЗНАЧЕНИЯ
WorkerFuncФункция с неизменным типом аргументов, которая выводит конкретный тип данных в конкретный тип потока.
template <typename TracerType, typename ValueType>void OutputWorker(void* tracer, addr_t valuePtr){ *(TracerType*)tracer << *(const ValueType*)valuePtr;}
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
ВЫВОД ВСЕГО ВЫРАЖЕНИЯ
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
ПРИЯТНЫЕ ОСОБЕННОСТИ
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.
20
НЕПРИЯТНЫЕ ОСОБЕННОСТИ
2. Возможна неоднозначность при выборе оператора <<.
1. Операторы вывода должны быть видны для ADL.namespace myproj{template <typename AnyStream, typename T1, typename T2>Stream& operator<<(AnyStream& os, const std::pair<T1, T2>& arg);
void foo(const std::pair<int, int>& data){ TRACE_INFO() << data; // Compilation error. Could be solved in C++11}} // namespace myproj
template <typename Stream> operator<<(const Stream& os, const MyType& arg);template <typename X> operator<<(const ArgumentPack& os, const X& arg);
ArgumentPack() << MyType(); // неоднозначность
21
НЕПРИЯТНЫЕ ОСОБЕННОСТИ
3. Нужны специальные меры для вывода std::endl.template <typename Char, typename Traits>basic_ostream<Char, Traits>& endl(basic_ostream<Char, Traits>& os);
struct AbstractManipulator; // умеет инициаизироваться выражением std::endltemplate <typename Typelist>operator<<(const ArgumentPack& os, const AbstractManipulator& manip);
4. Нельзя подменять поток в процессе вычисления выражения.template <typename AnyStream>CommaInserterStream<AnyStream> operator<<(AnyStream& os, MyCoolManip*);
5. При выключенной оптимизации код становится только хуже.Для эффективной упаковки аргументов в кортеж обязательно нужна инлайн-подстановка.
23
СИНТЕТИЧЕСКИЙ ПРИМЕРvoid TestTraceStream(){ TRACE(level) << "Hello" << ' ' << "World!"; SomeFunc();}
void TestTracePrintf(){ TRACE(level, "%s%c%s", "Hello", ' ', "World!"); SomeFunc();}
Stream Printf Expr. Templates
ВОПРОСЫ?Kaspersky Lab HQ
39A/3 Leningradskoe Shosse
Moscow, 125212, Russian Federation
Tel: +7 (495) 797-8700
www.kaspersky.com
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