Как вы оцениваете функцию? Глядя на результаты Callgrind, я обнаружил, что моя программа проводит много времени в pow
, Поскольку мне не нужна полная рабочая точность, я подумал, что мог бы создать справочную таблицу и использовать линейную интерполяцию между точками в таблице. Чтобы иметь возможность оценить подход с использованием таблицы поиска, мне нужно измерить время. Итак, я сделал это:
#ifdef __WAND__
target[name[test2.exe] type[application] platform[;Windows]]
target[name[test2] type[application]]
#endif
#include <herbs/main/main.h>
#include <herbs/tictoc/tictoc.h>
#include <herbs/array_fixedsize/array_fixedsize.h>
#include <random>
#include <cstdio>
#include <cmath>
class GetRand
{
public:
GetRand(double min,double max):U(min,max){}
bool operator()(double* val,size_t n,size_t N)
{
*val=U(randsource);
return 1;
}
private:
std::mt19937 randsource;
std::uniform_real_distribution<double> U;
};
int MAIN(int argc,charsys_t* argv[])
{
Herbs::ArrayFixedsize<double> vals(1024*1024*128,GetRand(-4,4));
const size_t N=16;
auto n=N;
while(n)
{
double start=0;
auto ptr=vals.begin();
{
Herbs::TicToc timestamp(start);
while(ptr!=vals.end())
{
pow(2,*ptr);
++ptr;
}
}
// I have set cpu-freq to 1.6 GHz using cpufreq-set
printf("%.15g\t",1.6e9*start/vals.length());
--n;
}
return 0;
}
При запуске этой программы выход составляет около 2,25 циклов за итерацию. Это кажется очень низким, так как реализация pow
кажется (это callgrind
дал мне __ieee754_pow
).
Цикл тестирования в сборке выглядит так при компиляции для GNU / Linux на x86-64:
call _ZN5Herbs6TicTocC1ERd@PLT
movq %r14, %rbx
.p2align 4,,10
.p2align 3
.L28:
vmovsd (%rbx), %xmm1
vucomisd .LC6(%rip), %xmm1
jb .L25
vmovsd .LC7(%rip), %xmm0
call pow@PLT
.L25:
addq $8, %rbx
cmpq %r12, %rbx
jne .L28
movq %rbp, %rdi
call _ZN5Herbs6TicTocD1Ev@PLT
По крайней мере pow
называется. Могу ли я доверять выводу или есть какая-то черная магия, которая устраняет вещи?
Есть несколько вещей, которые вы должны учитывать при тестировании функций.
1) Убедитесь, что отсутствие кеша не оказывает существенного влияния на результаты. В вашем случае вы перебираете большой массив данных, где вы получаете тонны промахов кеша. Вместо этого используйте меньший массив, который легко помещается в кэш L1 и проходит по нему несколько раз.
2) Убедитесь, что у вас есть побочные эффекты от вызовов функций, которые вы профилируете, что компилятор не может оптимизировать эти вызовы. В вашем случае компилятор не выполняет хорошую работу по оптимизации, так как pow()
звонки не оптимизируются, даже если побочные эффекты отсутствуют. Предпочитайте использовать целочисленные побочные эффекты, чтобы избежать аномалий в производительности с плавающей запятой (например, необработанный метод float для uint32 и добавление их вместо того, чтобы выполнять сложение с помощью float).
3) Разверните ваши циклы несколько раз, чтобы уменьшить накладные расходы. В настоящее время вы выполняете только один pow на цикл, где цикл добавляет относительно большие накладные расходы для этого простого вызова функции.
4) Профиль с полной оптимизацией и включенным встраиванием.
5) Выполните профилирование несколько раз, чтобы другие процессы не влияли на ваши результаты. Выберите лучший результат для сравнения (то есть наименьшее количество помех от других процессов).