Вы хотите выполнить микробенчмарк очень дешевой операции. Вам необходимо:
- Сделайте петлю вокруг дешевых операций; тот, который занимает достаточно много времени, чтобы разумно; например около секунды.
- Убедитесь, что вы используете результат одной итерации цикла в следующей , чтобы компилятор не пропускал полностью тело.
- Обернуть весь цикл в функции, пометить функцию как неотключаемую атрибутом, специфичным для компилятора (опять же, чтобы компилятор не просто пропустил вызов), и вызвать эта функция от вашей функции синхронизации. В качестве альтернативы , верните значение в зависимости от всех итераций цикла и фактически используйте это возвращаемое значение (например, напечатайте его или сохраните в переменной
volatile
) в вашей основной программе, чтобы обеспечить компилятор не могу просто оптимизировать программу и удалить ее.
- Кроме того, вы должны использовать таймеры высокого разрешения , а не
clock()
. В Windows это будет QueryPerformanceCounter(&tick_count)
, в Unix clock_gettime(CLOCK_PROCESS_CPUTIME_ID, ×pec_var)
, а в Macos посмотрите mach_absolute_time()
. Другое преимущество (некоторых из) этих методов состоит в том, что они измеряют время процессора, а не время настенных часов, и, таким образом, немного менее изменчивы перед лицом других действий в системе.
Опять же, абсолютно важно , чтобы убедиться, что вы на самом деле используете значения, вычисленные либо путем сохранения их в переменной volatile
, их печати или возврата из не встроенная функция, чтобы компилятор не мог просто оптимизировать их. И вы не хотите пометить ваш основной метод как не встраиваемый, так как накладные расходы на вызов функции вполне могут затопить такие микробенчмарки; по тем же причинам вам следует избегать random
. Вот почему вы должны тестировать функцию, содержащую цикл, вызывающий (встроенную) функцию, которая вас действительно интересует.
Например:
#include <iostream>
#include <time.h>
typedef unsigned __int64 uint64;
inline uint64 popcount_1(uint64 x)// etc...
template<typename TF>
uint64 bench_intfunc_helper(TF functor, size_t runs){//benchmark this
uint64 retval = 0;
for(size_t i=0; i<runs; ++i) retval += functor(i);
// note that i may not have a representative distribution like this
return retval;//depends on all loop iterations!
}
template<typename TF>
double bench_intfunc(TF functor, size_t runs){
clock_t start=clock();//hi-res timers would be better
volatile auto force_evalution = bench_intfunc_helper(functor,runs);
clock_t end=clock();
return (end-start)/1000.0;
}
#define BENCH(f) do {std::cout<<"Elapsed time for "<< RUNS <<" runs of " #f \
": " << bench_intfunc([](uint64 x) {return f(x);},RUNS) <<"s\n"; } while(0)
int main() {
BENCH(popcount_1);
BENCH(popcount_2);
BENCH(popcount_3);
BENCH(popcount_4);
return 0;
}
Например, простое пропускание volatile
приводит к тому, что GCC 4.6.3 и MSC 10.0 на моем компьютере сообщают о потраченных 0с. Я использую лямбду, так как указатели на функции не указываются этими компиляторами, а лямбда -.
На моей машине вывод этого теста на GCC:
Elapsed time for 1073741824 runs of popcount_1: 3.7s
Elapsed time for 1073741824 runs of popcount_2: 3.822s
Elapsed time for 1073741824 runs of popcount_3: 4.091s
Elapsed time for 1073741824 runs of popcount_4: 23.821s
и на MSC:
Elapsed time for 1073741824 runs of popcount_1: 7.508s
Elapsed time for 1073741824 runs of popcount_2: 5.864s
Elapsed time for 1073741824 runs of popcount_3: 3.705s
Elapsed time for 1073741824 runs of popcount_4: 19.353s