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

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

Upload: platonov-sergey

Post on 27-Jul-2015

277 views

Category:

Software


0 download

TRANSCRIPT

ОПТИМИЗАЦИЯ ТРАССИРОВАНИЯ С ИСПОЛЬЗОВАНИЕМ 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/

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