Измерение и снижение использования процессора в SQLite
Содержание
1. Обзор
На графике ниже показано количество тактов процессора, используемых SQLite при стандартной рабочей нагрузке для версий SQLite, начиная примерно с последних 10 лет. Последние версии SQLite используют примерно в три раза меньше тактов процессора по сравнению со старыми версиями.
В этой статье описывается, как разработчики SQLite измеряют использование процессора, что означают эти измерения и какие техники используют разработчики SQLite для дальнейшего снижения использования процессора библиотекой SQLite.
Измеренно с помощью cachegrind на Ubuntu 16.04 на x64 с gcc 5.4.0 и -Os.
2. Измерение производительности
Вкратце, производительность процессора SQLite измеряется следующим образом:
- Скомпилировать SQLite в конфигурации по умолчанию без каких-либо специальных параметров телеметрии или отладки.
- Связать SQLite с тестовой программой, которая выполняет примерно 30 000 SQL-запросов, представляющих типичную рабочую нагрузку.
- Подсчитать количество тактов процессора, используемых с помощью cachegrind.
2.1. Параметры компиляции
Для измерения производительности SQLite компилируется примерно так же, как и для использования в производственных системах. Конфигурация во время компиляции является «приблизительной» в том смысле, что каждое практическое использование SQLite отличается. Параметры компиляции, используемые одной системой, не обязательно совпадают с параметрами, используемыми другими. Важно избегать параметров, которые существенно влияют на сгенерированный машинный код. Например, параметр -DSQLITE_DEBUG опушен, поскольку этот параметр вставляет тысячи утверждений assert() в середине критических для производительности участков библиотеки SQLite. Параметр -pg (в GCC) также опушен, поскольку он заставляет компилятор генерировать дополнительный вероятностный код измерения производительности, который мешает фактическим измерениям производительности.
Для измерения производительности используется параметр -Os (оптимизация для размера), а не -O2, так как параметр -O2 приводит к значительному перемещению кода, что затрудняет сопоставление конкретных инструкций процессора с строками исходного кода C.
2.2. Рабочая нагрузка
«Типичная» рабочая нагрузка генерируется программой speedtest1.c в стандартном дереве исходного кода SQLite. Эта программа пытается использовать библиотеку SQLite так, как это обычно делают реальные приложения. Конечно, каждое приложение отличается, и поэтому ни одна тестовая программа не может точно отражать поведение всех приложений.
Программа speedtest1.c время от времени обновляется по мере того, как разработчики SQLite развивают свое понимание того, что представляет собой «типичное» использование.
Скрипт оболочки speed-check.sh, также находящийся в стандартном дереве исходного кода, используется для запуска программы speedtest1.c. Для воспроизведения измерений производительности поместите следующие файлы в одну папку:
- скрипт "speed-check.sh",
- тестовую программу "speedtest1.c", и
- файлы исходного кода «sqlite3.c» и «sqlite3.h» слияния SQLite
Затем запустите "sh speed-check.sh trunk".
2.3. Измерение производительности
Для измерения производительности используется Cachegrind, так как он дает повторяемые результаты с 7 или более значащими цифрами. Напротив, фактическое время выполнения (в реальном времени) редко повторяется с точностью более чем до одной значащей цифры.
2.4. Микрооптимизации
Высокая повторяемость cachegrind позволяет разработчикам SQLite внедрять и измерять «микрооптимизации». Микрооптимизация — это изменение кода, которое приводит к очень небольшому увеличению производительности. Типичные микрооптимизации снижают количество тактов процессора на 0,1% или 0,05% или даже меньше. Такие улучшения невозможно измерить с помощью реальных временных замеров. Однако сотни или тысячи микрооптимизаций суммируются, что приводит к измеримому увеличению производительности в реальных условиях.
3. Поток работы по измерению производительности
По мере редактирования исходного кода SQLite разработчики запускают скрипт оболочки speed-check.sh, чтобы отслеживать влияние изменений на производительность. Этот скрипт компилирует программу speedtest1.c, запускает её с cachegrind, обрабатывает вывод cachegrind с помощью сценария TCL cg_anno.tcl, затем сохраняет результаты в ряде текстовых файлов. Типичный вывод скрипта speed-check.sh выглядит так:
==8683== ==8683== I refs: 1,060,925,768 ==8683== I1 misses: 23,731,246 ==8683== LLi misses: 5,176 ==8683== I1 miss rate: 2.24% ==8683== LLi miss rate: 0.00% ==8683== ==8683== D refs: 557,686,925 (361,828,925 rd + 195,858,000 wr) ==8683== D1 misses: 5,067,063 ( 3,544,278 rd + 1,522,785 wr) ==8683== LLd misses: 57,958 ( 16,067 rd + 41,891 wr) ==8683== D1 miss rate: 0.9% ( 1.0% + 0.8% ) ==8683== LLd miss rate: 0.0% ( 0.0% + 0.0% ) ==8683== ==8683== LL refs: 28,798,309 ( 27,275,524 rd + 1,522,785 wr) ==8683== LL misses: 63,134 ( 21,243 rd + 41,891 wr) ==8683== LL miss rate: 0.0% ( 0.0% + 0.0% ) text data bss dec hex filename 523044 8240 1976 533260 8230c sqlite3.o 220507 1007870 7769352 sqlite3.c
Важные части вывода (части, на которые разработчики обращают наибольшее внимание), показаны красным цветом. В основном, разработчики хотят знать размер скомпилированной библиотеки SQLite и количество тактов процессора, необходимых для выполнения теста производительности.
Вывод сценария cg_anno.tcl показывает количество тактов процессора, потраченных на каждую строку кода. Отчет содержит приблизительно 80 000 строк. Следующий фрагмент взят из середины отчета, чтобы показать, как он выглядит:
. SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor *pCur, int *pRes){
. MemPage *pPage;
. assert( cursorOwnsBtShared(pCur) );
. assert( pRes!=0 );
. assert( *pRes==0 || *pRes==1 );
. assert( pCur->skipNext==0 || pCur->eState!=CURSOR_VALID );
369,648 pCur->info.nSize = 0;
369,648 pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl);
369,648 *pRes = 0;
739,296 if( pCur->eState!=CURSOR_VALID ) return btreeNext(pCur, pRes);
1,473,580 pPage = pCur->apPage[pCur->iPage];
1,841,975 if( (++pCur->aiIdx[pCur->iPage])>=pPage->nCell ){
4,340 pCur->aiIdx[pCur->iPage]--;
5,593 return btreeNext(pCur, pRes);
. }
728,110 if( pPage->leaf ){
. return SQLITE_OK;
. }else{
3,117 return moveToLeftmost(pCur);
. }
721,876 }
Числа слева, конечно, представляют собой количество тактов процессора для этой строки кода.
Сценарий cg_anno.tcl удаляет лишние детали из стандартного вывода аннотаций cachegrind, чтобы отчеты до и после можно было сравнивать с помощью сравнения diff, чтобы увидеть конкретные детали того, как попытка микрооптимизации повлияла на производительность.
4. Ограничения
Использование стандартной рабочей нагрузки speedtest1.c и cachegrind позволило значительно улучшить производительность. Однако важно признать ограничения этого подхода:
Измерения производительности выполняются с использованием одного компилятора (gcc 5.4.0), одного параметра оптимизации (-Os) и одной платформы (Ubuntu 16.04 LTS на x64). Производительность других компиляторов и процессоров может отличаться.
Рабочая нагрузка speedtest1.c, которая измеряется, пытается быть представительной для широкого круга типичных применений SQLite. Но каждое приложение отличается. Рабочая нагрузка speedtest1.c может не быть хорошим приближением для видов действий, выполняемых некоторыми приложениями. Разработчики SQLite постоянно работают над улучшением программы speedtest1.c, чтобы сделать её лучшим приближением к фактическому использованию SQLite. Обратная связь сообщества приветствуется.
Количество тактов, предоставленных cachegrind, является хорошим приближением к фактической производительности, но оно не является 100% точным.
Здесь измеряются только такты процессора. Такты процессора являются хорошим приближением для расхода энергии, но не обязательно хорошо коррелируют с реальными временными параметрами. Время, затрачиваемое на ввод-вывод, не отражается в тактах процессора, и время ввода-вывода преобладает во многих сценариях использования SQLite.
Эта страница в последний раз была изменена 08.01.2022 05:02:57 по UTC
SQLite is in the Public Domain.
https://sqlite.org/cpu.html