Spec-Zone.ru › SQLite

Измерение и снижение использования процессора в SQLite

Содержание
1. Обзор
2. Измерение производительности
2.1. Параметры компиляции
2.2. Рабочая нагрузка
2.3. Измерение производительности
2.4. Микрооптимизации
3. Поток работы по измерению производительности
4. Ограничения

1. Обзор

На графике ниже показано количество тактов процессора, используемых SQLite при стандартной рабочей нагрузке для версий SQLite, начиная примерно с последних 10 лет. Последние версии SQLite используют примерно в три раза меньше тактов процессора по сравнению со старыми версиями.

В этой статье описывается, как разработчики SQLite измеряют использование процессора, что означают эти измерения и какие техники используют разработчики SQLite для дальнейшего снижения использования процессора библиотекой SQLite.


Измеренно с помощью cachegrind на Ubuntu 16.04 на x64 с gcc 5.4.0 и -Os.

2. Измерение производительности

Вкратце, производительность процессора SQLite измеряется следующим образом:

  1. Скомпилировать SQLite в конфигурации по умолчанию без каких-либо специальных параметров телеметрии или отладки.
  2. Связать SQLite с тестовой программой, которая выполняет примерно 30 000 SQL-запросов, представляющих типичную рабочую нагрузку.
  3. Подсчитать количество тактов процессора, используемых с помощью 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API