2014-11-15 4 views
3

Я пишу интерфейс Delphi для инструкций SSE. Это класс (ради видимости и т. Д.) TSimdCpu с методами класса N (по одному на каждую инструкцию SSE, очевидная накладная производительность не является проблемой сейчас).Имитация виртуальных методов в Delphi

Теперь я хочу сравнить производительность моего кода (медленного, как есть) с чистым паскальным кодом, выполняющим то же самое. Мое первое предположение было бы написать аналогичный класс TGenericCpu с теми же именами методов. Но без общего базового класса и виртуальных методов у меня не может быть только один кусок кода тестирования, который бы вызывал методы любого класса, на котором он должен запускать тесты. В идеале, я хотел бы что-то вроде

TestOn(TSimdCpu); 
TestOn(TGenericCpu); 

Но я потерял о том, как реализовать эту без с использованием виртуальных методов Дельфи. Я не хочу, чтобы возвращался к виртуальным методам по двум причинам: один - это производительность, а другой - то, что он будет использоваться только для тестирования и для практического использования он будет добавлять бессмысленную сложность.

Могут ли быть полезны дженерики? Что-то вроде

TTest<T> = class 
... 
T.AddVector(v); 
... 
TTest<TSimdCpu>.Test; 
TTest<TGenericCpu>.Test; 
+0

Re "* performance *": Пожалуйста, подтвердите, что ваш код (после завершения) действительно быстрее, чем простой вызов виртуального метода. Re "* бессмысленная сложность *": Как бы вы хотели назвать то, что вы здесь делаете, и сколько времени вы хотите потратить на проект, который «будет использоваться только для тестирования»? – JensG

+0

Я бы назвал это «достижением пределов дельфи». Любое количество времени, которое я не делаю «кодирование трактора», прекрасное. –

ответ

2

Вы хотите реализовать что-то, что похоже на виртуальные методы, но не использует виртуальные методы или интерфейсы по соображениям производительности.

Вам нужно добавить какое-либо косвенное указание. Создайте запись, содержащую процедурные переменные. Для иллюстрации:

type 
    TAddFunc = function(a, b: Double): Double; 

    TMyRecord = record 
    AddFunc: TAddFunc; 
    end; 

Затем объявите два экземпляра записи. Один из них заполнен функциями SSE, а один заполняется справочными функциями, отличными от SSE.

На данный момент у вас есть то, что вам нужно. Вы можете передавать эти записи и использовать указанную им косвенность для написания общего тестового кода.

Это косвенное покрытие будет стоить. В конце концов, у вас есть ручная реализация интерфейсов. Ожидайте аналогичные эксплуатационные издержки для вызова функции, как и для интерфейсов.

Я ожидаю, что, если ваши операнды не являются массивными массивами, стоимость косвенного искажения будет искажать ваши контрольные показатели. Я знаю, что вы спросили, как реализовать тесты с использованием косвенности, но я лично хотел бы протестировать, используя как можно ближе к реальному коду. Это означает тестирование прямых вызовов функций.


Вы спрашиваете про дженерики. Они вам не подходят. Чтобы создать универсальный класс, который был параметризован в тестируемом классе, вам нужно, чтобы тестируемый класс был получен из общего базового класса или реализовал общий интерфейс. И тогда вы вернулись туда, где вы начали.

+0

Может ли какая-нибудь помощь? –

+1

К сожалению нет. Я не думаю, что вы делаете это правильно. У меня было бы условное, что бы переключал компиляцию между вариантами. Я считаю, что вам нужно удалить перенаправление во время выполнения. –

+0

Это просто повторное создание VMT. Таким образом, ускорение IMHO от обычного виртуального метода было бы только для одного поиска. И если вы определяете свои методы как 'class procedure' вместо' procedure', виртуальные методы класса и трюк VMT-записи будут работать точно так же. –

0

Идея Дэвида Хеффернана сейчас кажется единственным способом. Я сделал быстрый тест - вот результаты:

simd 516 ms (pointer to a function, asm) 
JensG 1187 ms (virtual method, asm) 
generic 2797 ms (pointer to a function, pascal) 
generic virtual 3360 ms (virtual method, pascal) 

Разница между нормальными и виртуальными вызовами функций может быть относительно небольшой для паскаль коды, но не для ассемблере

if cpu = nil then 
    if test.name = 'JensG' then 
     for i := 1 to N do begin 
     form1.JensGAdd(v1^); 
     form1.JensGMul(v2^); 
     end 
    else 
     for i := 1 to N do begin 
     form1.GenericAdd(v1^); 
     form1.GenericMul(v2^); 
     end 
    else 
    for i := 1 to N do begin 
     cpu.AddVector(v1^); 
     cpu.MulVector(v2^); 
    end; 
+0

Фактически, он мог бы включить каждый класс в свой собственный, а затем проверить отдельно. IOW, просто полиморфизм копирования и вставки. В конце концов, это просто для простого теста. –

+0

Зачем беспокоиться о том, когда вы можете просто изменить название устройства в предложении uses –

+0

@ Антон Chaning - это то, что я бы сделал. Я бы использовал единичный псевдоним. –

1

В вашем коде, основная разница в скорости не будет между вызовами функций.

Если вы посмотрите на ассемблере, вызов виртуального метода является то, что, как

mov eax,object 
mov ebx,[eax] // get the the class info VMT 
call dword ptr [ebx+##] // where ## is the virtual method offset 

Принимая во внимание, отсутствия виртуального метода является

mov eax,object 
call SomeAbsoluteAddress 

И для указателя на функцию (на стек)

mov eax,object 
call dword ptr [ebp+##] // where ## is the pointer in the stack 

Вы просто получаете один или два поиска в информации о классе VMT.

Я подозреваю, что ваш тест чрезмерно оптимизирован для указателя на функцию, поскольку указатель, вероятно, находится в стеке. На реальном коде вам нужно будет где-то сохранить указатель, чтобы вы ничего не получили по сравнению с вызовом виртуального метода.

И если вы определяете методы, как class procedure вместо procedure, я подозреваю, что виртуальные методы класса, а функция перенаправления будет выполнять точно так же:

mov eax,classinfo 
call dword ptr [eax+##] // where ## is the virtual method offset 

Для такого расчета, что бы действительно ускорить процесс может вообще не вызывать функции, но создает какой-то простой JIT. Создайте поток двоичного кода операции перед запуском функции, посмотрев на opcodes asm, затем создайте буфер, содержащий поток выполнения, и выполните его непосредственно. Здесь мы поговорим о производительности. Он похож на inlining вызов функций.

Я знаю как минимум два (недавних и поддерживаемых) проектов с такой компиляцией JIT, написанной в Delphi: Besen JavaScript engine и Delphi Web Script. Besen копирует asm-заглушки для создания JIT-буфера, тогда как DWS вычисляет коды операций с помощью набора методов генератора.

Также рассмотрите возможность использования языка с настроенным и оптимизированным JIT, если вам нужна производительность с плавающей запятой. Вы можете использовать, например. наш Открытый исходный код SpiderMonkey library for Delphi. Вы можете написать свой код на простом JavaScript, а затем оптимизированный JIT выполнит свою работу. Вы можете быть поражены результирующей скоростью: результатом является usually faster than Delphi x87 native code, для плавающей запятой. Вы получите много времени на разработку.

+0

Немного несправедливо сравнивать что-либо против древнего 32-битного компилятора. Как насчет сравнения с современным, теперь зрелым 64-битным компилятором. –

+0

Почему бы вам не проверить скорость вызова виртуальных функций. Ваши предположения о «одном или двух поисках» неверны. Мой код здесь. Там нечего подозревать. –

+0

Я проверил asm. Вы пытались использовать метод класса? Это то же самое, что и указатель функции в записи. Вы не представили никакого кода, который, я полагаю, несправедлив. –

Смежные вопросы