functor.cpp:
#include "gtest/gtest.h"
#include <algorithm>
typedef double Type;
Type* array;
const int N = 100000000;
inline bool less(Type a1, Type a2) {
return a1 < a2;
}
class Less {
public:
inline bool operator()(Type a1, Type a2) {
return a1 < a2;
}
};
// Использование встроенной функции сравнения.
TEST(Callback, BuiltIn) {
std::sort(array, array + N);
}
// Использование свободной функции сравнения по указателю.
TEST(Callback, Function) {
std::sort(array, array + N, less);
}
// Использование функтора.
TEST(Callback, Functor) {
std::sort(array, array + N, Less());
}
int main(int argc, char* argv[]) {
// Создаем отсортированный массив.
array = new Type[N];
Type* p = array;
for (int i = 0; i < N; ++i) *p++ = i;
testing::InitGoogleTest(&argc, argv);
// Принудительно печатаем время работы тестов.
testing::GTEST_FLAG(print_time) = true;
return RUN_ALL_TESTS();
}
Компилируем и запускаем (Visual Studio 2008):cl /O2 /arch:SSE2 /EHsc /I. functor.cpp gtest\gtest-all.cc && functor
Результат:[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from Callback
[ RUN ] Callback.BuiltIn
[ OK ] Callback.BuiltIn (9547 ms)
[ RUN ] Callback.Function
[ OK ] Callback.Function (24391 ms)
[ RUN ] Callback.Functor
[ OK ] Callback.Functor (9578 ms)
[----------] 3 tests from Callback (43547 ms total)
[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (43547 ms total)
[ PASSED ] 3 tests.
Видно, что скорость функтора (9578 мс) практически равна встроенной функции (9547 мс) сравнения. А вот вызов свободной функции конкретно отстает (24391 мс), приблизительно в 2.5 раза.Такое поведение можно объяснить тем, что в данном случае при вызове обычной функции компилятор не может оптимизировать такой вызов встраиванием (inlining). Вне зависимости от того, что функция объявлена выстраиваемой, так как ее вызов производится по указателю, компилятор не может сделать предположений о значении этого указателя на стадии исполнения, а значит провести оптимизацию.
При использовании же функтора компилятору доступна информация о семантике вызываемого кода, поэтому все типы оптимизации возможны. Отсюда и скорость, близкая к встроенной функции сравнения.
Как вариант, при замене типа сВыводdouble
наint
и при условии, что опция/arch:SSE2
включена, тест с функтором работал даже быстрее встроенной функции.
Использование функторов предпочтительнее, чем свободных функций. С точки зрения проектирования и так все понятно (функтор, или функциональный объект, можно удобно тестировать, наследовать и т.д), но, как видно, и в плане производительности функтор тоже впереди.
Все необходимые файлы для компиляции примеров из этого поста можно посмотреть и скачать на сайте проекта Easy Сoding в соответствующем разделе.
Данный пост является экспериментом по использованию Google Code в качестве хостинга для размещения исходных текстов примеров и самих статей.
Головная страница проекта - http://code.google.com/p/easy-coding/.
Каждый пост будет по необходимости размещаться в отдельном каталоге и отдельной страницей Wiki.
Для интереса скомпилировал похожий бенчмарк в g++ 4.4.1 и icc 11.1:
ОтветитьУдалитьg++ functor.cc -O3
> Standart: 6.29
> Function: 6.26
> Functor: 6.32
icc functor.cc -fast
> Standart: 5.19
> Function: 5.23
> Functor: 5.23
Я к тому, что из подобных сравнений не стоит делать такие громкие выводы. Единственный вывод, который можно сделать, что, вероятно, cl (какой версии?) не умеет делать инлайнинг функций, переданных как указателей в std::sort.
Собрал конкретно этот пример в GCC 4.3.2 20080827 (beta) 2 на чуть более быстрой машине.
ОтветитьУдалитьg++ -O3 -o functor -I. functor.cpp gtest-all.cc
./functor
[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from Callback
[ RUN ] Callback.BuiltIn
[ OK ] Callback.BuiltIn (4392 ms)
[ RUN ] Callback.Function
[ OK ] Callback.Function (9719 ms)
[ RUN ] Callback.Functor
[ OK ] Callback.Functor (4391 ms)
[----------] 3 tests from Callback (18542 ms total)
[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran. (18545 ms total)
[ PASSED ] 3 tests.
Опять указатель на функцию проигрывает.
К сожалению на GCC 4.4.1 проверить не могу, но если оптимизатор в 4.4.1 так радикально шагнул вперед по сравнению с 4.3.2, то надо срочно обновляться. Хотя, как-то сомнительно.
Кстати, компилятор cl из Visual Studio 2008, версия 15.00.21022.08 for 80x86.
Просто интересно, как можно оптимизировать в inline вызов по указателю, если в runtime указатель может быть другой.
Может мы компилируем разные примеры? Можете привести ваш тест?
http://pastebin.com/f51bd326f
ОтветитьУдалитьЕсли скомпилируете в g++ с флагами -O3 -fdump-tree-optimized, то будет видно различие (точнее почти отсутствие различий) в генерируемых оптимизированных алгоритмах.
В первом примере генерируется вызов подставленной __introsort_loop без компаратора.
Во втором примере вызов генерированной функции, аналогичной __introsort_loop без компаратора. Функция по указателю снова инлайнится.
В третьем примере идёт вызов __introsort_loop с компаратором, при этом компаратор также подставляется в генерируемую функцию.
Хм, скомпилил ваш пример на GCC 4.3.2.
ОтветитьУдалитьg++ -O3 -fdump-tree-optimized functor.cpp
./functor
Standart: 0.562
Function: 0.969
Functor: 0.563
Странно. Опять функтор быстрее.
Может в платформе дело? Я компилю все на x86, а вы на какой?
А вот прогон вашего примера через cl, правда на другой машине.
ОтветитьУдалитьcl /O2 functor.cpp
functor.exe
Standart: 0.047
Function: 0.157
Functor: 0.078
Тенденция та же.
x86, Intel E8500.
ОтветитьУдалитьПрогнал ещё раз 10, различия только в случайных погрешностях.
Попробуйте http://www.mediafire.com/download.php?1jtjnzwwihm , может правда пора обновляться.
Lockal: Я тут разыскал GCC 4.4.0 и вот что вышло с вашим примером:
ОтветитьУдалитьg++ -o func -O3 func.cpp
Standart: 0.45
Function: 0.38
Functor: 0.41
g++ -o func -O2 func.cpp
Standart: 0.39
Function: 0.38
Functor: 0.38
Получается, что -O2 в данном случае лучше чем -O3 (повторял много раз, тенденция одна и та же), а опция -fdump-tree-optimized роли не играет.
Я не понятно объяснил =)
ОтветитьУдалить-fdump-tree-optimized не влияет на компиляцию, а только генерирует промежуточный си-подобный листинг в файле с расширением optimized. Там и можно посмотреть, что подставилось, а что нет.
Кстати, почему у вас так быстро отрабатывает? У меня после вызова srand в среднем по 5-7 секунд отрабатывает. Без srand что -O3, что -O2 отрабатывают за абсолютно одинаковое время.
Да, про -fdump-tree-optimize я уж понял, обнаружив интересный файл объяснениями что куда соптимизировалось ;-).
ОтветитьУдалитьА быстро, потому, что я уменьшил N на один нолик ;-). На которых моих машинах я ограничен в плане ресурсов, и программа падала с std::bad_alloc из-за нехватки памяти. Пришлось уменьшить размерность. Но в целом, показательность осталась.
Идиотские эксперименты.. вообще на ассемблере пишите и будет вам скорость...
ОтветитьУдалитьНекоторые вещи, связанные с тестированием эффективности в C++
ОтветитьУдалитьhttp://groups.google.co.il/group/perfo
http://groups.google.co.il/group/sources
http://groups.google.co.il/group/log-files