Не так давно я писал о C++ библиотеках для микробенчмаркинга. Я рассказал о трех библиотеках: Nonius, Hayai и Celero. Но в действительности я хотел поговорить о четвертой. Мой Windows тогда не поддерживал Google Benchmark library, так что я не мог ее протестировать. К счастью, из комментариев к прошлому посту я узнал, что теперь библиотека доступна в Visual Studio!
Давайте посмотрим, как можно ее использовать.
Библиотека
Основной github репозиторий: github/google/benchmark
Обсуждение: groups.google/forum/benchmark-discuss
Благодаря коммиту KindDragon: Support MSVC on appveyor, мы можем собрать библиотеку в Visual Studio. Я без затруднений скачал последний код из репозитория, сгенерировал solution-файлы с помощью CMake и собрал нужную версию. Чтобы использовать библиотеку в вашем проекте, остается только подключить саму библиотеку и один заголовочный файл.
Простой пример
В исходной статье я проводил два эксперимента:
IntToStringConversionTest(count)
— конвертирует целые числа из диапазона 0…count-1 в строки и возвращает вектор этих строк.DoubleToStringConversionTest(count)
— конвертирует 0.12345… count-1+0.12345 в строки и возвращает вектор строк.
Пример бенчмарков целиком:
#include "benchmark/benchmark_api.h" #include "../commonTest.h" void IntToString(benchmark::State& state) { while (state.KeepRunning()) { benchmark::DoNotOptimize( IntToStringConversionTest(state.range_x()) ); } } BENCHMARK(IntToString)->Arg(TEST_NUM_COUNT1000); void DoubleToString(benchmark::State& state) { while (state.KeepRunning()) { benchmark::DoNotOptimize( DoubleToStringConversionTest(state.range_x()) ); } } BENCHMARK(DoubleToString)->Arg(TEST_NUM_COUNT1000); BENCHMARK_MAIN()
Красиво и просто! Макрос BENCHMARK
используется для определения бенчмарка, затем можно добавить параметры вызова. В примере выше я использовал метод Arg
. Параметр в этом методе передается в объект state, доступный функции бенчмарка. В нашем примере мы получаем это значение с помощью state.range_x()
. Затем оно используется как размер выходного вектора строк.
Внутри функции бенчмарка в while-цикле выполняется основной код. Количество итераций библиотека выберет автоматически.
Обычно приложение выполняется в консоли и выводит следующий результат:
Вывод очень прост: название бенчмарка, время в наносекундах (можно изменить с помощью метода Unit()
), время CPU, количество выполненных итераций.
Чем же так хороша эта библиотека?
- Можно легко изменять параметры: Arg, ArgPair, Range, RangePair, Apply.
- Значения можно получить с помощью
state.get_x()
,state.get_y()
- Так что можно создавать бенчмарки для задач в одно- и двухмерном пространстве.
- Значения можно получить с помощью
- Fixture
- Измерения в несколько потоков
- Ручное управление временем: полезно, когда код выполняется на графическом процессоре или другом устройстве, где стандартное время CPU не применимо.
- Форматы вывода: в виде таблицы, CSV, Json
- Возможность добавлять свои метки с помощью
state.SetLabel()
- Метки для обработанных объектов и обработанных байтов, благодаря
state.SetItemsProcessed()
иstate.SetBytesProcessed()
Вот так выглядит вывод бенчмарка с байтами в секунду, объектами в секунду, метками и измененными единицами времени.
Усложненный пример
В другом посте о библиотеках для микробенчмаркинга я тестировал библиотеки на немного более сложном примере. Это мой обычный бенчмарк — вектор указателей против вектора объектов. Посмотрим, сможем ли реализовать этот пример с помощью Google Benchmark.
Настройка
Вот, что мы собираемся протестировать:
- Класс Particle (частица) — содержит 18 атрибутов типа float: 4 для обозначения перемещения (pos), 4 для обозначения скорости (vel), 4 для ускорения (acceleration), 4 для цвета (color), 1 для времени (time), 1 для поворота (rotation). Еще у нас будет буфер, также типа float, с переменным количеством элементов в нем.
- Стандартная частица составляет 76 байт
- Увеличенная частица — 160 байт
- Хотим измерить скорость работы метода Update на векторе частиц.
- Будем использовать пять видов контейнеров:
-
vector<Particle>
-
vector<shared_ptr<Particle>>
— с рандомизацией размещения в памяти -
vector<shared_ptr<Particle>>
— без рандомизации размещения в памяти -
vector<unique_ptr<Particle>>
— с рандомизацией размещения в памяти -
vector<unique_ptr<Particle>>
— без рандомизации размещения в памяти
-
Немного кода
Пример кода для vector<Particle>
:
template <class Part> class ParticlesObjVectorFixture : public ::benchmark::Fixture { public: void SetUp(const ::benchmark::State& st) { particles = std::vector<Part>(st.range_x()); for (auto &p : particles) p.generate(); } void TearDown(const ::benchmark::State&) { particles.clear(); } std::vector<Part> particles; };
А вот бенчмарк:
using P76Fix = ParticlesObjVectorFixture<Particle>; BENCHMARK_DEFINE_F(P76Fix, Obj)(benchmark::State& state) { while (state.KeepRunning()) { UpdateParticlesObj(particles); } } BENCHMARK_REGISTER_F(P76Fix, Obj)->Apply(CustomArguments); using P160Fix = ParticlesObjVectorFixture<Particle160>; BENCHMARK_DEFINE_F(P160Fix, Obj)(benchmark::State& state) { while (state.KeepRunning()) { UpdateParticlesObj(particles); } } BENCHMARK_REGISTER_F(P160Fix, Obj)->Apply(CustomArguments);
С помощью этого кода мы протестируем два вида частиц: маленькие — 76 байт и побольше — 160 байт. Метод CustomArguments генерирует количество частиц для каждой итерации бенчмарка: 1k, 3k, 5k, 7k, 9k, 11k.
Результаты
В этом посте мы уделили основное внимание самой библиотеке, но я хотел бы ответить на вопрос, который мне задавали раньше — вопрос о различных размерах частиц. Пока я использовал только два типа: 76-байтные и 160-байтные.
Результаты для 76 байт:
Рандомизированные указатели почти на 76% медленнее, чем векторы объектов.
Результаты для 160 байт:
Почти прямые линии в случае больших частиц! Рандомизированные указатели медленнее только на 17%…. Ну хорошо, пускай не совсем прямые 🙂
Кроме того, мы протестировали и unique_ptr
. Как видите, с точки зрения update (доступа к данным), скорость почти такая же, как и у shared_ptr
. Таким образом, косвенное обращение — это проблема умного указателя, а не неизбежные накладные расходы.
Итог
Репозиторий с примерами кода: github/fenbf/benchmarkLibsTest
У меня не было трудностей с использованием Google Benchmark library. Вы сможете освоить основные принципы написания бенчмарков за несколько минут. Многопоточные бенчмарки, fixture, автоматический подбор количества итераций, вывод в формате CSV или Json — вот и все базовые функции. Лично мне больше всего нравится гибкость передачи параметров в код бенчмарка. У остальных проверенных мной библиотек имелись проблемы с тем, чтобы передать бенчмарку параметры проблемной области. Самой простой с этой точки зрения была Celero.
Не хватает, пожалуй, только расширенного отображения результатов. Библиотека показывает нам только среднее время выполнения итераций. Хотя в большинстве случаев этого достаточно.
С точки зрения эксперимента, я получил интересные результаты для частиц разного размера. Это может стать основой для будущего финального теста. Я попробую переписать мои примеры с большим разнообразием размеров объектов. Ожидаю увидеть огромную разницу в результатах для маленьких объектов и незначительную — для больших.
О, а приходите к нам работать? 🙂wunderfund.io — молодой фонд, который занимается высокочастотной алготорговлей. Высокочастотная торговля — это непрерывное соревнование лучших программистов и математиков всего мира. Присоединившись к нам, вы станете частью этой увлекательной схватки.Мы предлагаем интересные и сложные задачи по анализу данных и low latency разработке для увлеченных исследователей и программистов. Гибкий график и никакой бюрократии, решения быстро принимаются и воплощаются в жизнь.
Присоединяйтесь к нашей команде: wunderfund.io
ссылка на оригинал статьи https://habrahabr.ru/post/325634/
Добавить комментарий