25
ОПТИМИЗАЦИЯ ТРАССИРОВАНИЯ С ИСПОЛЬЗОВАНИЕМ EXPRESSION TEMPLATES Игорь Гусаров Software Expert, Kaspersky Lab

Оптимизация трассирования с использованием Expression templates

Embed Size (px)

Citation preview

Page 1: Оптимизация трассирования с использованием Expression templates

ОПТИМИЗАЦИЯ ТРАССИРОВАНИЯ С ИСПОЛЬЗОВАНИЕМ EXPRESSION TEMPLATES

Игорь ГусаровSoftware Expert, Kaspersky Lab

Page 2: Оптимизация трассирования с использованием Expression templates

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++ приведены на слайдах в упрощённом виде.

Page 3: Оптимизация трассирования с использованием Expression templates

Или история про то, как маленькая функция раздулась до десяти килобайт кода.

СКОЛЬКО СТОИТ ОТЛАДОЧНЫЙ ВЫВОД

Page 4: Оптимизация трассирования с использованием Expression templates

4

PERFORMANCE TEAM

Исследует производительность программ и их влияние на производительность ОС.

- задержки при Startup / Boot / Hibernate / Resume;- профилирование, поиск узких мест;- оптимизация основных сценариев;- контроль потребления ресурсов.

Проблема: рыхлый код

Page 5: Оптимизация трассирования с использованием Expression templates

5

ПРОВЕДЁМ ЭКСПЕРИМЕНТ

1. Возьмём реальный продукт - KIS 2015.

2. Вырежем для опытов фрагмент на 3 млн строк.

3. Сколько в нём отладочного вывода? 15 тыс строк.

4. Как изменится размер исполняемых модулей,если выкинуть весь отладочный вывод?

10%объёма исполняемых модулей

0,5%строк исходного текста C++

Page 6: Оптимизация трассирования с использованием Expression templates

6

СИНТЕТИЧЕСКИЙ ПРИМЕРvoid TestTraceStream(){ TRACE(level) << "Hello" << ' ' << "World!"; SomeFunc();}

void TestTracePrintf(){ TRACE(level, "%s%c%s", "Hello", ' ', "World!"); SomeFunc();}

подготовка

вывод сообщения

полезная нагрузка

Page 7: Оптимизация трассирования с использованием Expression templates

Разберёмся в деталях - откуда взялось так много кода и как сделать так, чтобы его стало меньше.

КТО ВИНОВАТ И ЧТО ДЕЛАТЬ

Page 8: Оптимизация трассирования с использованием Expression templates

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;}

ЧТО ПИШУТ РАЗРАБОТЧИКИ

Page 9: Оптимизация трассирования с использованием Expression templates

9

ЧТО ДОЛЖЕН УМЕТЬ ТРАССИРОВЩИК

1. Использовать синтаксис потоков вывода C++.Поскольку он всем знаком, и давно используется в кодовой базе.

2. Выводить любые типы данных.И чтобы разработчикам не требовалось писать ничего сложнее привычного оператора << для нового типа.

3. Работать из разных потоков (threads).Не создавая конкуренции. Простаивать на синхронизации из-за отладочного вывода - это последнее дело.

4. Не создавать лишних исключений.Мало кому понравится, если отладочный вывод начнёт влиять на ход работы смыслового кода.

5. Не вычислять выводимые значения, если они не нужны.И вообще тратить минимум усилий на выражения, которые не должны выводиться.

Page 10: Оптимизация трассирования с использованием Expression templates

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 " "

Page 11: Оптимизация трассирования с использованием Expression templates

11

ЧТО ВЫНУЖДЕН ДЕЛАТЬ КОМПИЛЯТОР

MakeTraceStream(tracer, level).SelfRef() << foo() << "data size = " << m_x ;

Не оптимальный по размеру и простоте машинный код

Необходим код раскрутки стека

Захват критической секцииили создание буфера

Выход: требуется деструктор

Инлайн-подстановка операторов вывода

Вход: создание временного объекта

MakeTraceStream(h.tracer, h.level).SelfRef() << ...

Page 12: Оптимизация трассирования с использованием Expression templates

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 << << << ;

Надо вычислять на месте

Эти вычисления можно вынести в отдельную общую функцию

Page 13: Оптимизация трассирования с использованием Expression templates

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

как

Page 14: Оптимизация трассирования с использованием Expression templates

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

Они позволяют сформировать кортеж из аргументов, отложив вычисления на потом.

Page 15: Оптимизация трассирования с использованием Expression templates

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

Page 16: Оптимизация трассирования с использованием Expression templates

16

ВЫВОД ОДНОГО ЗНАЧЕНИЯ

WorkerFuncФункция с неизменным типом аргументов, которая выводит конкретный тип данных в конкретный тип потока.

template <typename TracerType, typename ValueType>void OutputWorker(void* tracer, addr_t valuePtr){ *(TracerType*)tracer << *(const ValueType*)valuePtr;}

Page 17: Оптимизация трассирования с использованием Expression templates

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Статически инициализируемый список из ссылок на рабочие функции.

Page 18: Оптимизация трассирования с использованием Expression templates

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Единая функция для любых операций вывода.

Page 19: Оптимизация трассирования с использованием Expression templates

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.

Page 20: Оптимизация трассирования с использованием Expression templates

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(); // неоднозначность

Page 21: Оптимизация трассирования с использованием Expression templates

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. При выключенной оптимизации код становится только хуже.Для эффективной упаковки аргументов в кортеж обязательно нужна инлайн-подстановка.

Page 22: Оптимизация трассирования с использованием Expression templates

Победа!

РЕЗУЛЬТАТЫ

Page 23: Оптимизация трассирования с использованием Expression templates

23

СИНТЕТИЧЕСКИЙ ПРИМЕРvoid TestTraceStream(){ TRACE(level) << "Hello" << ' ' << "World!"; SomeFunc();}

void TestTracePrintf(){ TRACE(level, "%s%c%s", "Hello", ' ', "World!"); SomeFunc();}

Stream Printf Expr. Templates

Page 24: Оптимизация трассирования с использованием Expression templates

ВОПРОСЫ?Kaspersky Lab HQ

39A/3 Leningradskoe Shosse

Moscow, 125212, Russian Federation

Tel: +7 (495) 797-8700

www.kaspersky.com

Page 25: Оптимизация трассирования с использованием Expression templates

25

ССЫЛКИ И КОНТАКТЫ

"Pimp my log", Marc Eaddy http://www.youtube.com/watch?v=TS_waQZcZVc

Эти слайды доступны на http://meetingcpp.ru/Пример кода доступен там http://meetingcpp.ru/

Игорь Гусаров [email protected]Александр Леденев [email protected]Андрей Солодовников [email protected]