четверг, 15 октября 2009 г.

Abstraction penalty

В порыве философствования на тему «throw – это по сути то же goto» поймал себя на мысли о том, что думаю об исключениях как о дорогостоящей операции. А вдруг это premature optimization? Решил написать быстрый и простой сферический тест в вакууме.

void makeExceptions(int times)
{
for (int i = 0; i < times; ++i)
{
try
{
throw std::exception();
int a = 0;
a++;
}
catch (std::exception&)
{
}
}
}
_Winnie C++ Colorizer


Начав писать такую ерунду, я уже не мог остановиться и решил заодно проверить производительность конструкций BOOST_FOREACH и Q_FOREACH, от просмотра реализации которых мне хочется рвать волосы на заднице.
Начал я с простого for:
void makeForeachFor(int times)
{
const int size = 10;
int a[size];
for (int i = 0; i < times; ++i)
{
for (int j = 0; j < size; ++j)
{
a[j] = 0;
}
}
}
_Winnie C++ Colorizer


И BOOST_FOREACH:
void makeForeachBoost(int times)
{
const int size = 10;
int a[size];
for (int I = 0; I < times; ++i)
{
BOOST_FOREACH(int x, a)
{
x = 0;
}
}
}
_Winnie C++ Colorizer


Я специально помести его в innermost loop, чтобы сильнее проявили себя накладные расходы на создание и выход из цикла.
Сам массив я выбрал маленьким умышленно, чтобы он влез в любой кэш и не влиял на производительность.
Также есть свой foreach и в Qt. В отличие от BOOST_FOREACH, он не умеет работать с обычными массивами например и копирует контейнер перед использованием. Для Qt-контейнеров это не страшно, так как они все copy-on-write, но для обычных это просто недопустимо. Поэтому я также протестировал эти две реализации foreach на QVector.
void makeForeachBoostQt(int times)
{
const int size = 10;
QVector<int> a(size);
for (int i = 0; i < times; ++i)
{
BOOST_FOREACH(int x, a)
{
x = 0;
}
}
}

void makeForeachQt(int times)
{
const int size = 10;
QVector<int> a(size);
for (int i = 0; i < times; ++i)
{
Q_FOREACH(int x, a)
{
x = 0;
}
}
}
_Winnie C++ Colorizer



Исходный код файла
Собирал я его под Windows при помощи MinGW и компилятора от 2005 студии:
gcc version 3.4.5 (mingw-vista special r3)
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86
и под Linux:
gcc version 4.3.2 [gcc-4_3-branch revision 141291] (SUSE Linux)


Результаты замеров (среднее время в миллисекундах). Использовался 64битный Linux и 32битная Windows XP, процессор - Intel Core2 6420:


























































Cборка

Exceptions

for

Boost

Boost(QVector)

Q_FOREACH
linux-64-O0 3.590 0.0350 0.4330 0.4590 0.1360
linux-64-O2 3.600 0 0 0.0400 0.5700
win32-cl 2.828 0.0312 0.6719 0.6656 0.5844
win32-cl-O2 2.812 0 0.0125 0.0141 0.0734
win32-mingw-O0 0.843 0.0532 0.5672 0.6656 0.1937
win32-mingw-O2 0.844 0.0109 0.0125 0.0266 0.0546


Да, всё как и ожидалось. 3микросекунды на исключение это конечно немало, но если обрабатывать ими действительно ошибочные ситуации, а не строить на них логику программы (файла нет? Исключение! Пользователь нажал "отмена"? Исключение!), то вобщем-то не так уж и много. Хотя goto быстрее, да.
С другой стороны оказалось что оптимизаторы довольно агрессивно оптимизируют реализации foreach. Конечно, их ни в коем случае нельзя использовать в innermost loop, а для более высокого уровня вложенности они действительно упростят код (и усложнять хождение по шагам в отладчике).
Конечно полученные цифры не являются какой либо истиной в какой либо истанции, это всего лишь сферический тест в вакууме. Однако я для себя выводы сделал - и то и то можно без опаски применять, когда пишется например GUI или другой не библиотечный и не критичный ко времени выполнения код (98% кода). Что и требовалось доказать.