Так что это имеет очевидные ограничения и не должно использоваться постоянно, где бы у вас ни был интерфейс, но если у вас есть место, где перфом действительно нужно максимизировать, вы можете использовать дженерики:
public SomeObject<TMathFunction> where TMathFunction: struct, IMathFunction
{
private readonly TMathFunction mathFunction_;
public double SomeWork(double input, double step)
{
var f = mathFunction_.Calculate(input);
var dv = mathFunction_.Derivate(input);
return f - (dv * step);
}
}
Ивместо передачи интерфейса передайте свою реализацию как TMathFunction.Это позволит избежать поиска в vtable из-за интерфейса, а также позволит выполнять вставку.
Обратите внимание, что здесь важно использовать struct
, так как в общем случае дженерики будут обращаться к классу через интерфейс.
Некоторая реализация:
Я сделал простую реализацию IMathFunction для тестирования:
class SomeImplementationByRef : IMathFunction
{
public double Calculate(double input)
{
return input + input;
}
public double Derivate(double input)
{
return input * input;
}
}
... а также версию структуры и абстрактную версию.
Итак, вот что происходит с версией интерфейса.Вы можете видеть, что он относительно неэффективен, потому что выполняет два уровня косвенности:
return obj.SomeWork(input, step);
sub esp,40h
vzeroupper
vmovaps xmmword ptr [rsp+30h],xmm6
vmovaps xmmword ptr [rsp+20h],xmm7
mov rsi,rcx
vmovsd qword ptr [rsp+60h],xmm2
vmovaps xmm6,xmm1
mov rcx,qword ptr [rsi+8] ; load mathFunction_ into rcx.
vmovaps xmm1,xmm6
mov r11,7FFED7980020h ; load vtable address of the IMathFunction.Calculate function.
cmp dword ptr [rcx],ecx
call qword ptr [r11] ; call IMathFunction.Calculate function which will call the actual Calculate via vtable.
vmovaps xmm7,xmm0
mov rcx,qword ptr [rsi+8] ; load mathFunction_ into rcx.
vmovaps xmm1,xmm6
mov r11,7FFED7980028h ; load vtable address of the IMathFunction.Derivate function.
cmp dword ptr [rcx],ecx
call qword ptr [r11] ; call IMathFunction.Derivate function which will call the actual Derivate via vtable.
vmulsd xmm0,xmm0,mmword ptr [rsp+60h] ; dv * step
vsubsd xmm7,xmm7,xmm0 ; f - (dv * step)
vmovaps xmm0,xmm7
vmovaps xmm6,xmmword ptr [rsp+30h]
vmovaps xmm7,xmmword ptr [rsp+20h]
add rsp,40h
pop rsi
ret
Вот абстрактный класс.Это немного более эффективно, но незначительно:
return obj.SomeWork(input, step);
sub esp,40h
vzeroupper
vmovaps xmmword ptr [rsp+30h],xmm6
vmovaps xmmword ptr [rsp+20h],xmm7
mov rsi,rcx
vmovsd qword ptr [rsp+60h],xmm2
vmovaps xmm6,xmm1
mov rcx,qword ptr [rsi+8] ; load mathFunction_ into rcx.
vmovaps xmm1,xmm6
mov rax,qword ptr [rcx] ; load object type data from mathFunction_.
mov rax,qword ptr [rax+40h] ; load address of vtable into rax.
call qword ptr [rax+20h] ; call Calculate via offset 0x20 of vtable.
vmovaps xmm7,xmm0
mov rcx,qword ptr [rsi+8] ; load mathFunction_ into rcx.
vmovaps xmm1,xmm6
mov rax,qword ptr [rcx] ; load object type data from mathFunction_.
mov rax,qword ptr [rax+40h] ; load address of vtable into rax.
call qword ptr [rax+28h] ; call Derivate via offset 0x28 of vtable.
vmulsd xmm0,xmm0,mmword ptr [rsp+60h] ; dv * step
vsubsd xmm7,xmm7,xmm0 ; f - (dv * step)
vmovaps xmm0,xmm7
vmovaps xmm6,xmmword ptr [rsp+30h]
vmovaps xmm7,xmmword ptr [rsp+20h]
add rsp,40h
pop rsi
ret
Таким образом, и интерфейс, и абстрактный класс в значительной степени полагаются на предсказание цели перехода для достижения приемлемой производительности.Даже в этом случае вы можете увидеть, что в это дело входит гораздо больше, поэтому лучший вариант все еще относительно медленный, а худший случай - остановленный конвейер из-за неправильного прогноза.
И, наконец, вот общая версиясо структурой.Вы можете видеть, что он значительно эффективнее, потому что все полностью встроено, поэтому прогноз ветвления не задействован.У него также есть приятный побочный эффект - удаление большей части управления стека / параметров, которое было там, поэтому код становится очень компактным:
return obj.SomeWork(input, step);
push rax
vzeroupper
movsx rax,byte ptr [rcx+8]
vmovaps xmm0,xmm1
vaddsd xmm0,xmm0,xmm1 ; Calculate - got inlined
vmulsd xmm1,xmm1,xmm1 ; Derivate - got inlined
vmulsd xmm1,xmm1,xmm2 ; dv * step
vsubsd xmm0,xmm0,xmm1 ; f -
add rsp,8
ret