Это правда, что использование указателя на функцию может иметь некоторые небольшие дополнительные затраты, но большую часть времени снижение производительности происходит из-за того, что компилятор больше не может встроить вызываемую функцию и выполнять оптимизации, которые, где это возможно, после
Я хотел бы продемонстрировать это на следующем примере, который несколько меньше вашего:
int f(int i){
return i;
}
int sum_with_fun(){
int sum=0;
for(int i=0;i<1000;i++){
sum+=f(i);
}
return sum;
}
typedef int(*fun_ptr)(int);
int sum_with_ptr(fun_ptr ptr){
int sum=0;
for(int i=0;i<1000;i++){
sum+=ptr(i);
}
return sum;
}
Итак, есть две версии для вычисления sum f(i) for i=0...999
: с указателем на функцию и напрямую.
При компиляции с -fno-inline
(т. Е. Отключением встраивания для выравнивания земли) они создают почти идентичный ассемблер (здесь на godbolt.org ) - небольшая разница в том, как работает функциязвонил:
callq 4004d0 <_Z1fi> //direct call
...
callq *%r12 //via ptr
производительность, это не будет иметь большого значения.
Но когда мы отбрасываем -fno-inline
, компилятор может светить для прямой версии, как это и происходит (здесь на godbolt.org )
_Z12sum_with_funv:
movl $499500, %eax
ret
, то естьВесь цикл оценивается во время компиляции, по сравнению с неизменной косвенной версией, которой необходимо выполнить цикл во время выполнения:
_Z12sum_with_ptrPFiiE:
pushq %r12
movq %rdi, %r12
pushq %rbp
xorl %ebp, %ebp
pushq %rbx
xorl %ebx, %ebx
.L5:
movl %ebx, %edi
addl $1, %ebx
call *%r12
addl %eax, %ebp
cmpl $1000, %ebx
jne .L5
movl %ebp, %eax
popq %rbx
popq %rbp
popq %r12
ret
Так где же он вас покинет?Вы могли бы обернуть косвенную функцию известными указателями, и велика вероятность того, что компилятор сможет выполнить вышеописанные оптимизации, см., Например:
...
int sum_with_f(){
return sum_with_ptr(&f);
}
приводит к (здесь на godbolt.org ):
_Z10sum_with_fv:
movl $499500, %eax
ret
При вышеуказанном подходе вы зависите от компилятора (но современный компилятор милостив), чтобы выполнить вставку.
Есть такжедругие варианты, в зависимости от того, что вы на самом деле используете:
- В C ++ есть шаблоны для устранения такого рода повторяющихся работ без снижения производительности.
- В C можно использовать макросы с такими жеэффект.
- Numpy использует препроцессор для генерации повторяющегося кода, см., например, этот src-файл , из которого c-файл будет сгенерирован на этапе предварительной обработки.
- pandas использует подход, похожий на numpy, для кода на Cython, см., например, hashtable_func_helper.pxi.in-file .