SlideShare a Scribd company logo
Исключения C++ через призму
компиляторных оптимизаций
LLVM
Роман Русяев
Samsung R&D
сompilers developer
rusyaev.rm@gmail.com
План доклада
• Введение в реализацию исключений
• Как исключения поддержаны в LLVM
• Влияние исключений на оптимизации компилятора (на
примере LLVM)
2
Цель доклада
Как оптимизирующий компилятор работает с исключениями C++, и
как это может отразиться на производительности ваших
приложений:
• насколько дороги исключения, даже если они не выбрасываются?
3
Цель доклада
Как оптимизирующий компилятор работает с исключениями C++, и
как это может отразиться на производительности ваших
приложений:
• насколько дороги исключения, даже если они не выбрасываются?
• когда лучше исключения не использовать
4
Цель доклада
Как оптимизирующий компилятор работает с исключениями C++, и
как это может отразиться на производительности ваших
приложений:
• насколько дороги исключения, даже если они не выбрасываются?
• когда лучше исключения не использовать
• noexcept везде, где можно
5
noexcept везде, где можно
• const везде, где можно
6
noexcept везде, где можно
• const везде, где можно
• Практично ли это?
• синтаксический мусор
• с появлением семантики перемещения все стало сложнее
7
noexcept везде, где можно
• const везде, где можно
• Практично ли это?
• синтаксический мусор
• с появлением семантики перемещения все стало сложнее
• const – всегда, когда нужно (ссылки, указатели, функции члены
etc)
8
noexcept везде, где можно
• const везде, где можно
• Практично ли это?
• синтаксический мусор
• с появлением семантики перемещения все стало сложнее
• const – всегда, когда нужно (ссылки, указатели, функции члены
etc)
• А что с noexcept?
9
Введение в реализацию исключений
10
Zero-Cost Exception Handling (0eh)
• придуман для IA-64 (Itanium)
• спецификация описана в Itanium C++ ABI:
https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html
11
Zero-Cost Exception Handling (0eh)
12
не выполняется дополнительного кода, если
исключение не выбрасывается
Терминология
• спецификация исключений функции:
void foo() noexcept
void foo() throw() // deprecated
void foo() throw(type1, type2, ...) // deprecated
• cleanup
• обработчик исключения
• stack unwinding
13
stack unwinding
• stack unwinding: осуществляет вызов деструкторов локальных
объектов каждого стекового фрейма, пока не будет найден фрейм
с обработчиком исключения, соответствующим объекту
брошенного исключения.
14
stack unwinding
• stack unwinding: осуществляет вызов деструкторов локальных
объектов каждого стекового фрейма, пока не будет найден фрейм
с обработчиком исключения, соответствующим объекту
брошенного исключения. Состоит из 2х этапов:
• search phase: поиск обработчика исключения
• clean up phase: вызов деструкторов локальных объектов и передача на
обработчик исключения
15
stack unwinding, cleanup
• stack unwinding: осуществляет вызов деструкторов локальных
объектов каждого стекового фрейма, пока не будет найден фрейм
с обработчиком исключения, соответствующим объекту
брошенного исключения. Состоит из 2х этапов:
• search phase: поиск обработчика исключения
• clean up phase: вызов деструкторов локальных объектов и передача на
обработчик исключения
• cleanup: выполняет вызовы деструкторов локальных объектов в
процессе stack unwinding
16
stack unwinding, cleanup, обработчики исключений
• stack unwinding: осуществляет вызов деструкторов локальных
объектов каждого стекового фрейма, пока не будет найден фрейм
с обработчиком исключения, соответствующим объекту
брошенного исключения. Состоит из 2х этапов:
• search phase: поиск обработчика исключения
• clean up phase: вызов деструкторов локальных объектов и передача на
обработчик исключения
• cleanup: выполняет вызовы деструкторов локальных объектов в
процессе stack unwinding
• обработчик исключения: код, отвечающий за обработку
выброшенного исключения
17
std::terminate
• исключение не ловится
• не соблюден noexcept спецификатор функции
• исключение бросается из cleanup
• … (еще ~10 случаев)
18
Что происходит при бросании исключения?
19
foo
phase 1: search
…
user code
C++ runtime code
…
Что происходит при бросании исключения?
20
foo
phase 1: search
phase 2: clean up
…
user code
…
std::terminate
no handler/error
C++ runtime code
Что происходит при бросании исключения?
21
foo
phase 1: search
phase 2: clean up
… handler/clean up
user code
…
std::terminate
no handler/error
no handler/error
C++ runtime code
Что происходит при бросании исключения?
22
foo
phase 1: search
phase 2: clean up
… handler/clean up
…
user code
…
std::terminate
no handler/error
no handler/error
C++ runtime code
Что происходит при бросании исключения?
23
foo
phase 1: search
phase 2: clean up
… handler/clean up
…
user code
…
std::terminate
no handler/error
no handler/error
C++ runtime code
Что происходит при бросании исключения?
24
foo
phase 1: search
phase 2: clean up
… handler/clean up
…
user code
…
std::terminate
no handler/error
no handler/error
std::terminate
функция с noexcept
вызывает не noexcept
C++ runtime code
Поддержка исключений в LLVM
25
Введение в LLVM IR
• IR (Intermediate Representation) - структура данных или язык,
используемые внутри компилятора для отображения исходного
языка
• Будем использовать урезанный вариант LLVM IR
26
Пример инструкций на LLVM IR псевдокоде
%val ; обозначение локальной переменной или метки
alloca type ; выделить память для объекта типа type
call func_name ; вызов функции с именем func_name
ret ; инструкция возврата из функции
27
Граф потока управления (Control Flow
Graph – CFG)
BB1
BB2
BB3 BB4
BB5
…
…
28
Базовый блок (Basic Block)
instr1
instr2
…
instrN
terminator BB2
… …
BB2
…
29
Поддержка исключений в LLVM: invoke
• вызов функции с неявным переходом на участок кода, если
бросили исключение
invoke foo()
30
Поддержка исключений в LLVM: invoke
• вызов функции с неявным переходом на участок кода, если
бросили исключение
invoke foo() to label %1
31
Поддержка исключений в LLVM: invoke
• вызов функции с неявным переходом на участок кода, если
бросили исключение
invoke foo() to label %1 unwind label %2
32
Поддержка исключений в LLVM: invoke, landing pad
• вызов функции с неявным переходом на участок кода, если
бросили исключение
invoke foo() to label %1 unwind label %2
• участок кода, ответственный за обработку исключения
landingpad
33
Поддержка исключений в LLVM: invoke, landing pad, resume
• вызов функции с неявным переходом на участок кода, если
бросили исключение
invoke foo() to label %1 unwind label %2
• участок кода, ответственный за обработку исключения
landingpad
• инструкция, продолжающая раскрутку стека
resume
34
Влияние исключений на компиляторные
оптимизации
35
Накладные расходы (с точки зрения
оптимизатора)
• увеличение размера кода функции за счет создания
дополнительного кода для cleanup
• усложнение потока управления, появляющегося в результате
наличия инструкции invoke
36
Накладные расходы
Как побороть?
37
PruneEH
• преобразует инструкции invoke в call
• ставит признак nounwind для функций, которые не бросают
исключения
38
PruneEH
void extF() noexcept;
struct S {
~S() { extF(); }
};
void bar() {
extF();
}
void foo() {
S s;
bar();
}
39
void bar() {
call extF()
ret
}
PruneEH
void extF() noexcept;
struct S {
~S() { extF(); }
};
void bar() {
extF();
}
void foo() {
S s;
bar();
}
40
void bar() {
call extF()
ret
}
void foo() {
%1 = alloca S
invoke bar() to label %4 unwind label %5
…
}
PruneEH
void extF() noexcept;
struct S {
~S() { extF(); }
};
void bar() {
extF();
}
void foo() {
S s;
bar();
}
41
void bar() {
call extF()
ret
}
void foo() {
%1 = alloca S
invoke bar() to label %4 unwind label %5
; <label>:4:
call ~S(%1)
ret
; <label>:5:
landingpad ; cleanup
call ~S(%1)
resume
}
PruneEH
void foo() {
%1 = alloca S
call bar()
call ~S(%1)
ret
}
42
void foo() {
%1 = alloca S
invoke bar() to label %4 unwind label %5
; <label>:4:
call ~S(%1)
ret
; <label>:5:
landingpad ; cleanup
call ~S(%1)
resume
}
Остальные оптимизации
•Simplify the CFG
•Global Variable Optimizer
•Instruction combining
43
Накладные расходы
А где побороть не удается?
44
Inline
void foo() {
// foo actions
bar();
}
void bar() {
// bar actions
}
void foo() {
// foo actions
// bar actions
}
45
Inline: эвристики
• llvm/Analysis/InlineCost.h
• llvm/lib/Analysis/InlineCost.cpp
CallAnalyzer::analyzeBlock(…) {
…
addCost(…); // for each instruction add cost
…
}
46
Inline: эвристики
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
extF();
}
void bar() {
call extF()
ret
}
47
Inline: эвристики
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
extF();
}
void foo() {
MyStruct obj;
bar();
}
void bar() {
call extF()
ret
}
void foo() {
%0 = alloca MyStruct
invoke bar() to label %4 unwind label %5
…
}
48
Inline: эвристики
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
extF();
}
void foo() {
MyStruct obj;
bar();
}
void bar() {
call extF()
ret
}
void foo() {
%0 = alloca MyStruct
invoke bar() to label %4 unwind label %5
; <label>:4:
call ~MyStruct(%0)
ret
; <label>:5:
landingpad ; cleanup
call ~MyStruct(%0)
resume
}
49
Inline: эвристики
void foo() {
MyStruct obj1;
…
bar();
…
if (…) {
MyStruct obj2;
bar();
…
else {
MyStruct obj3;
bar();
…
bar();
} 50
Inline: invoke
• все инструкции call без noexcept  invoke
• поток управления от новых инструкций invoke  landing pad проинлайненной
инструкции invoke
51
invoke f2()
f1()
lpad …
before after
Inline: invoke
• все инструкции call без noexcept  invoke
• поток управления от новых инструкций invoke  landing pad проинлайненной
инструкции invoke
52
invoke f2()
f1()
call f3()
f2()
lpad …
before after
Inline: invoke
• все инструкции call без noexcept  invoke
• поток управления от новых инструкций invoke  landing pad проинлайненной
инструкции invoke
53
invoke f2()
f1()
call f3()
f2()
lpad …
invoke f3()
f1()
lpad …
before after
Inline: invoke
• все инструкции call без noexcept  invoke
• поток управления от новых инструкций invoke  landing pad проинлайненной
инструкции invoke
void foo() {
%0 = alloca MyStruct
invoke extF() to label %4 unwind label %6
; <label>:4:
call ~MyStruct(%0)
ret
; <label>:6:
landingpad
call ~MyStruct(%0)
resume
}
54
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
extF();
}
void foo() {
MyStruct obj;
bar();
}
Inline: resume
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
extF();
}
void foo() {
MyStruct obj;
bar();
}
void bar() {
call extF()
ret
}
void foo() {
%0 = alloca MyStruct
invoke bar() to label %4 unwind label %5
; <label>:4:
call ~MyStruct(%0)
ret
; <label>:5:
landingpad ; cleanup
call ~MyStruct(%0)
resume
}
55
Inline: resume
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
MyStruct obj;
extF();
}
void foo() {
MyStruct obj;
bar();
} 56
Inline: resume
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
MyStruct obj;
extF();
}
void foo() {
MyStruct obj;
bar();
}
void bar() {
call extF()
ret
}
57
void bar() {
%0 = alloca MyStruct
invoke extF() to label %4 unwind label %5
; <label>:4:
call ~MyStruct(%0)
ret
; <label>:5:
landingpad ; cleanup
call ~MyStruct(%0)
resume
}
• Инструкции resume подставляемой функции  на landing pad
проинлайненной инструкции invoke
58
Inline: resume
invoke f2()
f1() f2()
lpad1 …
before after
invoke f3()
lpad2 …
• Инструкции resume подставляемой функции  на landing pad
проинлайненной инструкции invoke
59
Inline: resume
invoke f2()
f1() f2()
lpad1 …
invoke f3()
f1()
lpad2 …
before after
invoke f3()
lpad2 …
lpad1
Inline: resume void foo() {
%1 = alloca MyStruct
%2 = alloca MyStruct
invoke extF() to label %7 unwind label %5
; <label>:5:
landingpad
call ~MyStruct(%1)
call ~MyStruct(%2)
resume
; <label>:7:
call ~MyStruct(%1)
call ~MyStruct(%2)
}
60
void bar() {
%0 = alloca MyStruct
invoke extF() to label %4 unwind label %5
; <label>:4:
call ~MyStruct(%0)
ret
; <label>:5:
landingpad ; cleanup
call ~MyStruct(%0)
resume
}
Tail Call
void foo() {
goto bar_label;
}
void bar() {
bar_label:
// bar actions
}
void foo() {
bar();
}
void bar() {
// bar actions
}
61
Tail Call
Не применима к инструкциям invoke
62
Loop Fusion
for (int i = 0; i < n; i++)
a[i] = ...;
for (int j = 0; j < n; j++)
b[j] = ...;
for (int i = 0; i < n; i++) {
a[i] = ...;
b[i] = ...;
}
63
Loop Fusion
• Если в цикле есть call, который может бросать
исключение, то цикл не рассматривается как кандидат
для оптимизации
64
LICM (Loop Invariant Code Motion)
int y = foo();
int x;
for (int i = 0; i < n; i++) {
x = y;
a[i] = x + ...;
}
int y = foo();
int x = y;
for (int i = 0; i < n; i++) {
a[i] = x + ...;
}
65
LICM (Loop Invariant Code Motion)
• не работает для инструкций invoke
• не работает для инструкций call, потенциально
бросающих исключения
66
ADCE (Aggressive Dead Code Elimination)
int global;
void f () {
int i;
i = 1;
global = 1;
global = 2;
}
67
int global;
void f () {
global = 2;
}
ADCE (Aggressive Dead Code Elimination)
bool AggressiveDeadCodeElimination::isAlwaysLive(Instruction &Inst) {
if (Inst.isEHPad() || …) { // landing pad
…
return true;
}
if (Inst.isTerminator() == false) // invoke is terminator instruction
return false;
if (isa<BranchInst>(Inst) || isa<SwitchInst>(Inst)) // invoke is not included here
return false;
return true;
}
68
Sinking
• Переносит инструкции в базовые блоки преемники, чтобы убрать
лишнее исполнение инструкций
69
def -> val
use val
before
Sinking
• Переносит инструкции в базовые блоки преемники, чтобы убрать
лишнее исполнение инструкций
70
def -> val
use val
before after
def -> val
use val
Sinking
71
bool isSafeToMove(Instruction *Inst, …) {
if (… || Inst->mayThrow())
return false;
…
}
Merged Load/Store Motion
• Объединяет инструкции записи в память по одному адресу, для
уменьшения статического размера кода
72
store addr1
before
store addr1
Merged Load/Store Motion
• Объединяет инструкции записи в память по одному адресу, для
уменьшения статического размера кода
73
store addr1
before after
store addr1
store addr1
Merged Load/Store Motion
74
/// True when instruction is a sink barrier for a store
bool MergedLoadStoreMotion::isStoreSinkBarrierInRange(const Instruction &Start,
const Instruction &End) {
for (const Instruction &Inst : make_range(Start.getIterator(), End.getIterator())) {
if (Inst.mayThrow())
return true;
}
…
}
GVNHoist
• Объединяет инструкции записи в память по одному адресу, для
уменьшения статического размера кода и сокращения критического
пути
75
load addr1
before
load addr1
GVNHoist
• Объединяет инструкции записи в память по одному адресу, для
уменьшения статического размера кода и сокращения критического
пути
76
load addr1
before after
load addr1
load addr1
GVNHoist
bool isGuaranteedToTransferExecutionToSuccessor(const Instruction *I) {
…
// Calls can throw, or contain an infinite loop, or kill the process.
If (auto CS = ImmutableCallSite(I)) {
// Call sites that throw have implicit non-local control flow.
If (!CS.doesNotThrow())
return false;
…
}
77
LICM Loop Versioning
• Дублирование циклов с проверкой на пересечение адресов и
применение LICM к копии цикла
78
runtime memcheck
LOOP LOOP COPY
LICM Loop Versioning
bool LoopVersioningLICM::instructionSafeForVersioning(Instruction *I) {
…
// Avoid loops with possiblity of throw
if (I->mayThrow())
return false;
…
}
79
Остальные оптимизации
• mayHaveSideEffects
• mayThrow
• doesNotThrow
80
Выводы
• zero-cost далеко не всегда нулевой, даже если исключение не
бросается
81
Выводы
• zero-cost далеко не всегда нулевой, даже если исключение не
бросается:
• современные компиляторы имеют специальные оптимизации для
обработки исключений
82
Выводы
• zero-cost далеко не всегда нулевой, даже если исключение не
бросается:
• современные компиляторы имеют специальные оптимизации для
обработки исключений
• если вы разрабатываете библиотеку, стоит подумать об отказе от
исключений
83
Выводы
• zero-cost далеко не всегда нулевой, даже если исключение не
бросается:
• современные компиляторы имеют специальные оптимизации для
обработки исключений
• если вы разрабатываете библиотеку, стоит подумать об отказе от
исключений
• noexcept везде, где можно
84
Выводы
• zero-cost далеко не всегда нулевой, даже если исключение не
бросается:
• современные компиляторы имеют специальные оптимизации для
обработки исключений
• если вы разрабатываете библиотеку, стоит подумать об отказе от
исключений
• noexcept везде, где можно:
• аккуратно, т.к. это часть интерфейса
85
Q & A
Роман Русяев
Samsung R&D
сompilers developer
rusyaev.rm@gmail.com
86
APPENDIX
87
setjmp/longjmp (sjlj)
• setjmp – запомнить, куда нужно прыгнуть
• longjmp – эмулирует throw
Очень непроизводительно:
• много дополнительных структур данных
• много дополнительного выполняемого кода вне зависимости от
факта бросания исключения
• Нарушение главного принципа C++ - “you only pay for what you
use”
88
Библиотека поддержки исключений
• LLVM:
• libcxxabi
• libunwind
• GCC:
• libsupc++
• libgcc
89
Библиотека поддержки исключений: throw
• _cxa_allocate_exception
• __cxa_throw:
• _Unwind_RaiseException
• …
90
Библиотека поддержки исключений: catch
• __cxa_begin_catch
• __cxa_end_catch
91
Что такое LLVM
• инфраструктура для разработки компиляторов
• компилятор и инструменты, основанные на LLVM IR
http://llvm.org
92
Введение в LLVM
• LLVM IR
• множество проектов, использующих инфраструктуру LLVM
• компилятор на основе LLVM – clang
• LLVM библиотеки (support library, command line library, …)
• алгоритмы над LLVM IR (трансформации, оптимизации, …)
• инструменты для работы с LLVM IR
• …
93
Введение в LLVM IR: основные концепции
• Модули
• Функции
• Глобальные переменные
• Метаданные
• …
94
Основы работы компилятора
frontend
middle-end
backend
compiler
input
output
95
Middle-end
• Работает с IR
• Выполняет:
• анализы
• трансформации
• оптимизации
• Проход компилятора (pass) – выполнение над IR анализа,
трансформации или оптимизации
96

More Related Content

PDF
Павел Довгалюк, Обратная отладка
PDF
Цена ошибки
PDF
DI в C++ тонкости и нюансы
PDF
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
PPTX
Как мы уменьшили количество ошибок в Unreal Engine с помощью статического ана...
PPTX
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
PPT
Юнит-тестирование и Google Mock. Влад Лосев, Google
PDF
Михаил Давыдов - JavaScript. Базовые знания
Павел Довгалюк, Обратная отладка
Цена ошибки
DI в C++ тонкости и нюансы
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVM
Как мы уменьшили количество ошибок в Unreal Engine с помощью статического ана...
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
Юнит-тестирование и Google Mock. Влад Лосев, Google
Михаил Давыдов - JavaScript. Базовые знания

What's hot (20)

PDF
TeaVM: dead code elimination and devirtualization
PDF
C++ exceptions
PDF
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
PDF
Акторы на C++: стоило ли оно того?
PPTX
Основы и применение статического анализа кода при разработке лекция 1
PPTX
Асинхронность и сопрограммы
PDF
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
PPTX
Deep Dive C# by Sergey Teplyakov
PPTX
Евгений Рыжков, Андрей Карпов Как потратить 10 лет на разработку анализатора ...
PPTX
Павел Беликов, Как избежать ошибок, используя современный C++
PPTX
Аскетичная разработка браузера
PPTX
Григорий Демченко, Асинхронность и неблокирующая синхронизация
PPTX
Async clinic by by Sergey Teplyakov
PDF
Борис Сазонов, RAII потоки и CancellationToken в C++
PPTX
Евгений Зуев, С++ в России: Стандарт языка и его реализация
PDF
Михаил Давыдов — JavaScript: Базовые знания
PDF
Объектно-ориентированное программирование. Лекция 5 и 6
PPTX
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
PDF
Для чего мы делали свой акторный фреймворк и что из этого вышло?
PDF
хитрости выведения типов
TeaVM: dead code elimination and devirtualization
C++ exceptions
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Акторы на C++: стоило ли оно того?
Основы и применение статического анализа кода при разработке лекция 1
Асинхронность и сопрограммы
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Deep Dive C# by Sergey Teplyakov
Евгений Рыжков, Андрей Карпов Как потратить 10 лет на разработку анализатора ...
Павел Беликов, Как избежать ошибок, используя современный C++
Аскетичная разработка браузера
Григорий Демченко, Асинхронность и неблокирующая синхронизация
Async clinic by by Sergey Teplyakov
Борис Сазонов, RAII потоки и CancellationToken в C++
Евгений Зуев, С++ в России: Стандарт языка и его реализация
Михаил Давыдов — JavaScript: Базовые знания
Объектно-ориентированное программирование. Лекция 5 и 6
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Для чего мы делали свой акторный фреймворк и что из этого вышло?
хитрости выведения типов
Ad

Similar to Исключения C++ через призму компиляторных оптимизаций. Роман Русяев ➠ CoreHard Autumn 2019 (20)

PPTX
Статический анализ кода проектов, построенных на движке Unreal Engine
PPTX
Static code analysis of the projects built on Unreal Engine
PPTX
Александр Фокин, Рефлексия в C++
PPTX
Поддержка Java 8 в Excelsior JET
PPTX
Принципы работы статического анализатора кода PVS-Studio
PDF
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
PDF
Как приручить дракона: введение в LLVM
PDF
Tech Talks @NSU: Как приручить дракона: введение в LLVM
PDF
Контейнер сервисов
PPTX
200 open source проектов спустя: опыт статического анализа исходного кода
PPTX
200 open source проектов спустя: опыт статического анализа исходного кода
PPTX
Статический анализ и написание качественного кода на C/C++ для встраиваемых с...
PPTX
Что могут статические анализаторы, чего не могут программисты и тестировщики
PPTX
Delegates and events in C#
PDF
Построение компилятора на базе LLVM — Павел Сычев
PDF
ekbpy'2012 - Михаил Коробов - Python 3
PDF
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
PPTX
Java 8 Support at the JVM Level
PPTX
Статические анализаторы кода как DevSecOps решение
PDF
Сверхоптимизация кода на Python
Статический анализ кода проектов, построенных на движке Unreal Engine
Static code analysis of the projects built on Unreal Engine
Александр Фокин, Рефлексия в C++
Поддержка Java 8 в Excelsior JET
Принципы работы статического анализатора кода PVS-Studio
Как мы учились чинить самолеты в воздухе / Евгений Коломеец (Virtuozzo)
Как приручить дракона: введение в LLVM
Tech Talks @NSU: Как приручить дракона: введение в LLVM
Контейнер сервисов
200 open source проектов спустя: опыт статического анализа исходного кода
200 open source проектов спустя: опыт статического анализа исходного кода
Статический анализ и написание качественного кода на C/C++ для встраиваемых с...
Что могут статические анализаторы, чего не могут программисты и тестировщики
Delegates and events in C#
Построение компилятора на базе LLVM — Павел Сычев
ekbpy'2012 - Михаил Коробов - Python 3
ПВТ - весна 2015 - Лекция 8. Многопоточное программирование без использования...
Java 8 Support at the JVM Level
Статические анализаторы кода как DevSecOps решение
Сверхоптимизация кода на Python
Ad

More from corehard_by (20)

PPTX
C++ CoreHard Autumn 2018. Создание пакетов для открытых библиотек через conan...
PPTX
C++ CoreHard Autumn 2018. Что должен знать каждый C++ программист или Как про...
PDF
C++ CoreHard Autumn 2018. Actors vs CSP vs Tasks vs ... - Евгений Охотников
PPTX
C++ CoreHard Autumn 2018. Знай свое "железо": иерархия памяти - Александр Титов
PPTX
C++ CoreHard Autumn 2018. Информационная безопасность и разработка ПО - Евген...
PPTX
C++ CoreHard Autumn 2018. Заглядываем под капот «Поясов по C++» - Илья Шишков
PPTX
C++ CoreHard Autumn 2018. Ускорение сборки C++ проектов, способы и последстви...
PPTX
C++ CoreHard Autumn 2018. Метаклассы: воплощаем мечты в реальность - Сергей С...
PPTX
C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...
PPTX
C++ CoreHard Autumn 2018. Кодогенерация C++ кроссплатформенно. Продолжение - ...
PDF
C++ CoreHard Autumn 2018. Concurrency and Parallelism in C++17 and C++20/23 -...
PPTX
C++ CoreHard Autumn 2018. Обработка списков на C++ в функциональном стиле - В...
PPTX
C++ Corehard Autumn 2018. Обучаем на Python, применяем на C++ - Павел Филонов
PDF
C++ CoreHard Autumn 2018. Asynchronous programming with ranges - Ivan Čukić
PDF
C++ CoreHard Autumn 2018. Debug C++ Without Running - Anastasia Kazakova
PDF
C++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухин
PDF
C++ CoreHard Autumn 2018. Text Formatting For a Future Range-Based Standard L...
PPTX
Исключительная модель памяти. Алексей Ткаченко ➠ CoreHard Autumn 2019
PDF
Как помочь и как помешать компилятору. Андрей Олейников ➠ CoreHard Autumn 2019
PDF
Автоматизируй это. Кирилл Тихонов ➠ CoreHard Autumn 2019
C++ CoreHard Autumn 2018. Создание пакетов для открытых библиотек через conan...
C++ CoreHard Autumn 2018. Что должен знать каждый C++ программист или Как про...
C++ CoreHard Autumn 2018. Actors vs CSP vs Tasks vs ... - Евгений Охотников
C++ CoreHard Autumn 2018. Знай свое "железо": иерархия памяти - Александр Титов
C++ CoreHard Autumn 2018. Информационная безопасность и разработка ПО - Евген...
C++ CoreHard Autumn 2018. Заглядываем под капот «Поясов по C++» - Илья Шишков
C++ CoreHard Autumn 2018. Ускорение сборки C++ проектов, способы и последстви...
C++ CoreHard Autumn 2018. Метаклассы: воплощаем мечты в реальность - Сергей С...
C++ CoreHard Autumn 2018. Что не умеет оптимизировать компилятор - Александр ...
C++ CoreHard Autumn 2018. Кодогенерация C++ кроссплатформенно. Продолжение - ...
C++ CoreHard Autumn 2018. Concurrency and Parallelism in C++17 and C++20/23 -...
C++ CoreHard Autumn 2018. Обработка списков на C++ в функциональном стиле - В...
C++ Corehard Autumn 2018. Обучаем на Python, применяем на C++ - Павел Филонов
C++ CoreHard Autumn 2018. Asynchronous programming with ranges - Ivan Čukić
C++ CoreHard Autumn 2018. Debug C++ Without Running - Anastasia Kazakova
C++ CoreHard Autumn 2018. Полезный constexpr - Антон Полухин
C++ CoreHard Autumn 2018. Text Formatting For a Future Range-Based Standard L...
Исключительная модель памяти. Алексей Ткаченко ➠ CoreHard Autumn 2019
Как помочь и как помешать компилятору. Андрей Олейников ➠ CoreHard Autumn 2019
Автоматизируй это. Кирилл Тихонов ➠ CoreHard Autumn 2019

Исключения C++ через призму компиляторных оптимизаций. Роман Русяев ➠ CoreHard Autumn 2019

  • 1. Исключения C++ через призму компиляторных оптимизаций LLVM Роман Русяев Samsung R&D сompilers developer rusyaev.rm@gmail.com
  • 2. План доклада • Введение в реализацию исключений • Как исключения поддержаны в LLVM • Влияние исключений на оптимизации компилятора (на примере LLVM) 2
  • 3. Цель доклада Как оптимизирующий компилятор работает с исключениями C++, и как это может отразиться на производительности ваших приложений: • насколько дороги исключения, даже если они не выбрасываются? 3
  • 4. Цель доклада Как оптимизирующий компилятор работает с исключениями C++, и как это может отразиться на производительности ваших приложений: • насколько дороги исключения, даже если они не выбрасываются? • когда лучше исключения не использовать 4
  • 5. Цель доклада Как оптимизирующий компилятор работает с исключениями C++, и как это может отразиться на производительности ваших приложений: • насколько дороги исключения, даже если они не выбрасываются? • когда лучше исключения не использовать • noexcept везде, где можно 5
  • 6. noexcept везде, где можно • const везде, где можно 6
  • 7. noexcept везде, где можно • const везде, где можно • Практично ли это? • синтаксический мусор • с появлением семантики перемещения все стало сложнее 7
  • 8. noexcept везде, где можно • const везде, где можно • Практично ли это? • синтаксический мусор • с появлением семантики перемещения все стало сложнее • const – всегда, когда нужно (ссылки, указатели, функции члены etc) 8
  • 9. noexcept везде, где можно • const везде, где можно • Практично ли это? • синтаксический мусор • с появлением семантики перемещения все стало сложнее • const – всегда, когда нужно (ссылки, указатели, функции члены etc) • А что с noexcept? 9
  • 10. Введение в реализацию исключений 10
  • 11. Zero-Cost Exception Handling (0eh) • придуман для IA-64 (Itanium) • спецификация описана в Itanium C++ ABI: https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html 11
  • 12. Zero-Cost Exception Handling (0eh) 12 не выполняется дополнительного кода, если исключение не выбрасывается
  • 13. Терминология • спецификация исключений функции: void foo() noexcept void foo() throw() // deprecated void foo() throw(type1, type2, ...) // deprecated • cleanup • обработчик исключения • stack unwinding 13
  • 14. stack unwinding • stack unwinding: осуществляет вызов деструкторов локальных объектов каждого стекового фрейма, пока не будет найден фрейм с обработчиком исключения, соответствующим объекту брошенного исключения. 14
  • 15. stack unwinding • stack unwinding: осуществляет вызов деструкторов локальных объектов каждого стекового фрейма, пока не будет найден фрейм с обработчиком исключения, соответствующим объекту брошенного исключения. Состоит из 2х этапов: • search phase: поиск обработчика исключения • clean up phase: вызов деструкторов локальных объектов и передача на обработчик исключения 15
  • 16. stack unwinding, cleanup • stack unwinding: осуществляет вызов деструкторов локальных объектов каждого стекового фрейма, пока не будет найден фрейм с обработчиком исключения, соответствующим объекту брошенного исключения. Состоит из 2х этапов: • search phase: поиск обработчика исключения • clean up phase: вызов деструкторов локальных объектов и передача на обработчик исключения • cleanup: выполняет вызовы деструкторов локальных объектов в процессе stack unwinding 16
  • 17. stack unwinding, cleanup, обработчики исключений • stack unwinding: осуществляет вызов деструкторов локальных объектов каждого стекового фрейма, пока не будет найден фрейм с обработчиком исключения, соответствующим объекту брошенного исключения. Состоит из 2х этапов: • search phase: поиск обработчика исключения • clean up phase: вызов деструкторов локальных объектов и передача на обработчик исключения • cleanup: выполняет вызовы деструкторов локальных объектов в процессе stack unwinding • обработчик исключения: код, отвечающий за обработку выброшенного исключения 17
  • 18. std::terminate • исключение не ловится • не соблюден noexcept спецификатор функции • исключение бросается из cleanup • … (еще ~10 случаев) 18
  • 19. Что происходит при бросании исключения? 19 foo phase 1: search … user code C++ runtime code …
  • 20. Что происходит при бросании исключения? 20 foo phase 1: search phase 2: clean up … user code … std::terminate no handler/error C++ runtime code
  • 21. Что происходит при бросании исключения? 21 foo phase 1: search phase 2: clean up … handler/clean up user code … std::terminate no handler/error no handler/error C++ runtime code
  • 22. Что происходит при бросании исключения? 22 foo phase 1: search phase 2: clean up … handler/clean up … user code … std::terminate no handler/error no handler/error C++ runtime code
  • 23. Что происходит при бросании исключения? 23 foo phase 1: search phase 2: clean up … handler/clean up … user code … std::terminate no handler/error no handler/error C++ runtime code
  • 24. Что происходит при бросании исключения? 24 foo phase 1: search phase 2: clean up … handler/clean up … user code … std::terminate no handler/error no handler/error std::terminate функция с noexcept вызывает не noexcept C++ runtime code
  • 26. Введение в LLVM IR • IR (Intermediate Representation) - структура данных или язык, используемые внутри компилятора для отображения исходного языка • Будем использовать урезанный вариант LLVM IR 26
  • 27. Пример инструкций на LLVM IR псевдокоде %val ; обозначение локальной переменной или метки alloca type ; выделить память для объекта типа type call func_name ; вызов функции с именем func_name ret ; инструкция возврата из функции 27
  • 28. Граф потока управления (Control Flow Graph – CFG) BB1 BB2 BB3 BB4 BB5 … … 28
  • 29. Базовый блок (Basic Block) instr1 instr2 … instrN terminator BB2 … … BB2 … 29
  • 30. Поддержка исключений в LLVM: invoke • вызов функции с неявным переходом на участок кода, если бросили исключение invoke foo() 30
  • 31. Поддержка исключений в LLVM: invoke • вызов функции с неявным переходом на участок кода, если бросили исключение invoke foo() to label %1 31
  • 32. Поддержка исключений в LLVM: invoke • вызов функции с неявным переходом на участок кода, если бросили исключение invoke foo() to label %1 unwind label %2 32
  • 33. Поддержка исключений в LLVM: invoke, landing pad • вызов функции с неявным переходом на участок кода, если бросили исключение invoke foo() to label %1 unwind label %2 • участок кода, ответственный за обработку исключения landingpad 33
  • 34. Поддержка исключений в LLVM: invoke, landing pad, resume • вызов функции с неявным переходом на участок кода, если бросили исключение invoke foo() to label %1 unwind label %2 • участок кода, ответственный за обработку исключения landingpad • инструкция, продолжающая раскрутку стека resume 34
  • 35. Влияние исключений на компиляторные оптимизации 35
  • 36. Накладные расходы (с точки зрения оптимизатора) • увеличение размера кода функции за счет создания дополнительного кода для cleanup • усложнение потока управления, появляющегося в результате наличия инструкции invoke 36
  • 38. PruneEH • преобразует инструкции invoke в call • ставит признак nounwind для функций, которые не бросают исключения 38
  • 39. PruneEH void extF() noexcept; struct S { ~S() { extF(); } }; void bar() { extF(); } void foo() { S s; bar(); } 39 void bar() { call extF() ret }
  • 40. PruneEH void extF() noexcept; struct S { ~S() { extF(); } }; void bar() { extF(); } void foo() { S s; bar(); } 40 void bar() { call extF() ret } void foo() { %1 = alloca S invoke bar() to label %4 unwind label %5 … }
  • 41. PruneEH void extF() noexcept; struct S { ~S() { extF(); } }; void bar() { extF(); } void foo() { S s; bar(); } 41 void bar() { call extF() ret } void foo() { %1 = alloca S invoke bar() to label %4 unwind label %5 ; <label>:4: call ~S(%1) ret ; <label>:5: landingpad ; cleanup call ~S(%1) resume }
  • 42. PruneEH void foo() { %1 = alloca S call bar() call ~S(%1) ret } 42 void foo() { %1 = alloca S invoke bar() to label %4 unwind label %5 ; <label>:4: call ~S(%1) ret ; <label>:5: landingpad ; cleanup call ~S(%1) resume }
  • 43. Остальные оптимизации •Simplify the CFG •Global Variable Optimizer •Instruction combining 43
  • 44. Накладные расходы А где побороть не удается? 44
  • 45. Inline void foo() { // foo actions bar(); } void bar() { // bar actions } void foo() { // foo actions // bar actions } 45
  • 46. Inline: эвристики • llvm/Analysis/InlineCost.h • llvm/lib/Analysis/InlineCost.cpp CallAnalyzer::analyzeBlock(…) { … addCost(…); // for each instruction add cost … } 46
  • 47. Inline: эвристики void extF(); struct MyStruct { ~MyStruct() { extF(); } }; void bar() { extF(); } void bar() { call extF() ret } 47
  • 48. Inline: эвристики void extF(); struct MyStruct { ~MyStruct() { extF(); } }; void bar() { extF(); } void foo() { MyStruct obj; bar(); } void bar() { call extF() ret } void foo() { %0 = alloca MyStruct invoke bar() to label %4 unwind label %5 … } 48
  • 49. Inline: эвристики void extF(); struct MyStruct { ~MyStruct() { extF(); } }; void bar() { extF(); } void foo() { MyStruct obj; bar(); } void bar() { call extF() ret } void foo() { %0 = alloca MyStruct invoke bar() to label %4 unwind label %5 ; <label>:4: call ~MyStruct(%0) ret ; <label>:5: landingpad ; cleanup call ~MyStruct(%0) resume } 49
  • 50. Inline: эвристики void foo() { MyStruct obj1; … bar(); … if (…) { MyStruct obj2; bar(); … else { MyStruct obj3; bar(); … bar(); } 50
  • 51. Inline: invoke • все инструкции call без noexcept  invoke • поток управления от новых инструкций invoke  landing pad проинлайненной инструкции invoke 51 invoke f2() f1() lpad … before after
  • 52. Inline: invoke • все инструкции call без noexcept  invoke • поток управления от новых инструкций invoke  landing pad проинлайненной инструкции invoke 52 invoke f2() f1() call f3() f2() lpad … before after
  • 53. Inline: invoke • все инструкции call без noexcept  invoke • поток управления от новых инструкций invoke  landing pad проинлайненной инструкции invoke 53 invoke f2() f1() call f3() f2() lpad … invoke f3() f1() lpad … before after
  • 54. Inline: invoke • все инструкции call без noexcept  invoke • поток управления от новых инструкций invoke  landing pad проинлайненной инструкции invoke void foo() { %0 = alloca MyStruct invoke extF() to label %4 unwind label %6 ; <label>:4: call ~MyStruct(%0) ret ; <label>:6: landingpad call ~MyStruct(%0) resume } 54 void extF(); struct MyStruct { ~MyStruct() { extF(); } }; void bar() { extF(); } void foo() { MyStruct obj; bar(); }
  • 55. Inline: resume void extF(); struct MyStruct { ~MyStruct() { extF(); } }; void bar() { extF(); } void foo() { MyStruct obj; bar(); } void bar() { call extF() ret } void foo() { %0 = alloca MyStruct invoke bar() to label %4 unwind label %5 ; <label>:4: call ~MyStruct(%0) ret ; <label>:5: landingpad ; cleanup call ~MyStruct(%0) resume } 55
  • 56. Inline: resume void extF(); struct MyStruct { ~MyStruct() { extF(); } }; void bar() { MyStruct obj; extF(); } void foo() { MyStruct obj; bar(); } 56
  • 57. Inline: resume void extF(); struct MyStruct { ~MyStruct() { extF(); } }; void bar() { MyStruct obj; extF(); } void foo() { MyStruct obj; bar(); } void bar() { call extF() ret } 57 void bar() { %0 = alloca MyStruct invoke extF() to label %4 unwind label %5 ; <label>:4: call ~MyStruct(%0) ret ; <label>:5: landingpad ; cleanup call ~MyStruct(%0) resume }
  • 58. • Инструкции resume подставляемой функции  на landing pad проинлайненной инструкции invoke 58 Inline: resume invoke f2() f1() f2() lpad1 … before after invoke f3() lpad2 …
  • 59. • Инструкции resume подставляемой функции  на landing pad проинлайненной инструкции invoke 59 Inline: resume invoke f2() f1() f2() lpad1 … invoke f3() f1() lpad2 … before after invoke f3() lpad2 … lpad1
  • 60. Inline: resume void foo() { %1 = alloca MyStruct %2 = alloca MyStruct invoke extF() to label %7 unwind label %5 ; <label>:5: landingpad call ~MyStruct(%1) call ~MyStruct(%2) resume ; <label>:7: call ~MyStruct(%1) call ~MyStruct(%2) } 60 void bar() { %0 = alloca MyStruct invoke extF() to label %4 unwind label %5 ; <label>:4: call ~MyStruct(%0) ret ; <label>:5: landingpad ; cleanup call ~MyStruct(%0) resume }
  • 61. Tail Call void foo() { goto bar_label; } void bar() { bar_label: // bar actions } void foo() { bar(); } void bar() { // bar actions } 61
  • 62. Tail Call Не применима к инструкциям invoke 62
  • 63. Loop Fusion for (int i = 0; i < n; i++) a[i] = ...; for (int j = 0; j < n; j++) b[j] = ...; for (int i = 0; i < n; i++) { a[i] = ...; b[i] = ...; } 63
  • 64. Loop Fusion • Если в цикле есть call, который может бросать исключение, то цикл не рассматривается как кандидат для оптимизации 64
  • 65. LICM (Loop Invariant Code Motion) int y = foo(); int x; for (int i = 0; i < n; i++) { x = y; a[i] = x + ...; } int y = foo(); int x = y; for (int i = 0; i < n; i++) { a[i] = x + ...; } 65
  • 66. LICM (Loop Invariant Code Motion) • не работает для инструкций invoke • не работает для инструкций call, потенциально бросающих исключения 66
  • 67. ADCE (Aggressive Dead Code Elimination) int global; void f () { int i; i = 1; global = 1; global = 2; } 67 int global; void f () { global = 2; }
  • 68. ADCE (Aggressive Dead Code Elimination) bool AggressiveDeadCodeElimination::isAlwaysLive(Instruction &Inst) { if (Inst.isEHPad() || …) { // landing pad … return true; } if (Inst.isTerminator() == false) // invoke is terminator instruction return false; if (isa<BranchInst>(Inst) || isa<SwitchInst>(Inst)) // invoke is not included here return false; return true; } 68
  • 69. Sinking • Переносит инструкции в базовые блоки преемники, чтобы убрать лишнее исполнение инструкций 69 def -> val use val before
  • 70. Sinking • Переносит инструкции в базовые блоки преемники, чтобы убрать лишнее исполнение инструкций 70 def -> val use val before after def -> val use val
  • 71. Sinking 71 bool isSafeToMove(Instruction *Inst, …) { if (… || Inst->mayThrow()) return false; … }
  • 72. Merged Load/Store Motion • Объединяет инструкции записи в память по одному адресу, для уменьшения статического размера кода 72 store addr1 before store addr1
  • 73. Merged Load/Store Motion • Объединяет инструкции записи в память по одному адресу, для уменьшения статического размера кода 73 store addr1 before after store addr1 store addr1
  • 74. Merged Load/Store Motion 74 /// True when instruction is a sink barrier for a store bool MergedLoadStoreMotion::isStoreSinkBarrierInRange(const Instruction &Start, const Instruction &End) { for (const Instruction &Inst : make_range(Start.getIterator(), End.getIterator())) { if (Inst.mayThrow()) return true; } … }
  • 75. GVNHoist • Объединяет инструкции записи в память по одному адресу, для уменьшения статического размера кода и сокращения критического пути 75 load addr1 before load addr1
  • 76. GVNHoist • Объединяет инструкции записи в память по одному адресу, для уменьшения статического размера кода и сокращения критического пути 76 load addr1 before after load addr1 load addr1
  • 77. GVNHoist bool isGuaranteedToTransferExecutionToSuccessor(const Instruction *I) { … // Calls can throw, or contain an infinite loop, or kill the process. If (auto CS = ImmutableCallSite(I)) { // Call sites that throw have implicit non-local control flow. If (!CS.doesNotThrow()) return false; … } 77
  • 78. LICM Loop Versioning • Дублирование циклов с проверкой на пересечение адресов и применение LICM к копии цикла 78 runtime memcheck LOOP LOOP COPY
  • 79. LICM Loop Versioning bool LoopVersioningLICM::instructionSafeForVersioning(Instruction *I) { … // Avoid loops with possiblity of throw if (I->mayThrow()) return false; … } 79
  • 81. Выводы • zero-cost далеко не всегда нулевой, даже если исключение не бросается 81
  • 82. Выводы • zero-cost далеко не всегда нулевой, даже если исключение не бросается: • современные компиляторы имеют специальные оптимизации для обработки исключений 82
  • 83. Выводы • zero-cost далеко не всегда нулевой, даже если исключение не бросается: • современные компиляторы имеют специальные оптимизации для обработки исключений • если вы разрабатываете библиотеку, стоит подумать об отказе от исключений 83
  • 84. Выводы • zero-cost далеко не всегда нулевой, даже если исключение не бросается: • современные компиляторы имеют специальные оптимизации для обработки исключений • если вы разрабатываете библиотеку, стоит подумать об отказе от исключений • noexcept везде, где можно 84
  • 85. Выводы • zero-cost далеко не всегда нулевой, даже если исключение не бросается: • современные компиляторы имеют специальные оптимизации для обработки исключений • если вы разрабатываете библиотеку, стоит подумать об отказе от исключений • noexcept везде, где можно: • аккуратно, т.к. это часть интерфейса 85
  • 86. Q & A Роман Русяев Samsung R&D сompilers developer rusyaev.rm@gmail.com 86
  • 88. setjmp/longjmp (sjlj) • setjmp – запомнить, куда нужно прыгнуть • longjmp – эмулирует throw Очень непроизводительно: • много дополнительных структур данных • много дополнительного выполняемого кода вне зависимости от факта бросания исключения • Нарушение главного принципа C++ - “you only pay for what you use” 88
  • 89. Библиотека поддержки исключений • LLVM: • libcxxabi • libunwind • GCC: • libsupc++ • libgcc 89
  • 90. Библиотека поддержки исключений: throw • _cxa_allocate_exception • __cxa_throw: • _Unwind_RaiseException • … 90
  • 91. Библиотека поддержки исключений: catch • __cxa_begin_catch • __cxa_end_catch 91
  • 92. Что такое LLVM • инфраструктура для разработки компиляторов • компилятор и инструменты, основанные на LLVM IR http://llvm.org 92
  • 93. Введение в LLVM • LLVM IR • множество проектов, использующих инфраструктуру LLVM • компилятор на основе LLVM – clang • LLVM библиотеки (support library, command line library, …) • алгоритмы над LLVM IR (трансформации, оптимизации, …) • инструменты для работы с LLVM IR • … 93
  • 94. Введение в LLVM IR: основные концепции • Модули • Функции • Глобальные переменные • Метаданные • … 94
  • 96. Middle-end • Работает с IR • Выполняет: • анализы • трансформации • оптимизации • Проход компилятора (pass) – выполнение над IR анализа, трансформации или оптимизации 96