Скорость выполнения функций DLL, написанных на Delphi

 

Решил написать для себя небольшую библиотеку математических и научных функций, которых нет в MQL4, но которые бывают очень нужны мне. Встал вопрос, как лучше всего, с точки зрения быстродействия реализовать это. Возможны два основных варианта: 1 - переписать всё что нужно на MQL (это время на перевод функций с Дельфи, Фортрана или Ассемблера на С-подобный MQL, плюс время на тестирование ), 2 - использование DLL (но тут неизвестно, какие накладные расходы на вызовы). Написал простенький Dll, на Дельфи, что бы проверить насколько сильно замедляеца выполнение элементарных функций при вызове их из ДЛЛ.


Способ очень прост. Тестировал функию Max, которая возвращает из двух значений то которое больше. Делается 50'000'000 вызовов функции, замеряется время выполнения. Рассмотрел 5 различных вариантов:

1. Вызов в цикле стандартной функции MQL .

2. Вызов в цикле из ДЛЛ стандартной функции Дельфи Max.

3. Вызов в цикле из ДЛЛ функции Max, переписанной на Ассемблере, очень быстрая функция.

4. Вызов цикла внутри ДЛЛ с обращением к стандартной функции Дельфи Max.

4. Вызов цикла внутри ДЛЛ с обращением к функции Max, переписанной на Ассемблере, очень быстрая функция.


Тестовый пример прикреплен во вложении, результаты следующие:

1. Время работы стандартной функции MQL4 MathMax, единичные вызовы = 1453 миллисекунд
2. Время работы стандартной функции Delphi Max, единичные вызовы к DLL = 2672 миллисекунд
3. Время работы функции Delphi Max, быстрый вариант на ASM, единичные вызовы к DLL = 2093 миллисекунд
4. Время работы стандартной функции Delphi Max, цикл обращений в DLL = 719 миллисекунд
5. Время работы функции Delphi Max, быстрый вариант на ASM, цикл обращений в DLL = 516 миллисекунд


Таким образом, нет никакого смысла обращаться к элементарным функциям в ДЛЛ, так как накладные расходы очень велики, но зато есть смысл делать сложные алгоритмы во внешней ДЛЛ, так как можно использовать особенности сторонних компиляторов (что и было сделано в примерах, кто знает об особенности оптимизатора Delphi7, тот поймет) и делать вставки на Ассемблере. Фактически, получилось, что стандартная функция Дельфи, которая работает вдвое быстрее, при обращении через ДЛЛ, становится вдвое медленнее. При этом, функция на ассемблере почти втрое быстрее, но все эти преимущества теряются, опять же на вызовах в ДЛЛ, да так что очень быстрый алгоритм проигрывает стандартной функции MQL.


Мой вывод прост, буду делать сложные алгоритмы в ДЛЛ, а простые вещи на MQL. Это печально, потому что придется поддерживать два проекта на двух разных языках. Надеюсь, в MQL5, ситуация будет получше.


Ну и, если кому не лень, у кого есть время и умение, по-шаманьте с приведенным примером, может быть на MQL на самом деле можно написать гораздо более быстный вариант, чем тот который я использовал. Всё таки MQL, и С для меня не родные языки, возможны грубые ошибки.


PS. если кто то решит вдруг посоветовать использовать вместо Дельфи какой нибудь из компиляторов С++, - сообщаю, проверено много раз, компилятор Дельфи, 7 (не апгрейженный вариант) или 10 в грамотных руках немного уступает по скорости компилированных программ только Intel C++, самому быстрому из существующих компиляторов С.

Файлы:
speedup_1.zip  27 kb
 

Да, вызовы DLL функций из MQL программы весьма медленные. Хотя если происходит вызов на каждом тике, то замедление не заметите особо и доли секнуды не существенны.

А я делал тест на делфи последнем и на вижуал си и последний выигрывал в скорости исполнения особенно при работе с типом double и его эквивалентом в делфи по размеру.

Если использвать запуск вирутальной машины джава в серверном режиме то начиная с 4 итерации где то скорость выполнения Java программы начинает выигрывать в скорости исполнения про сравнению с делфи и немного обыгрывает даже вижуал си. Просто сан реализовала оптимизацию кода во время исполнения и получается выигрышь в вызове повторяющегося кода.

 

Речь не о тиках, которые приходят выполняются всего лишь несколько раз в секунду. Речь о ресурсоемких алгоритмах.


И если у вас код на жаве оказался быстрее - это всего лишь значит что вы на дельфи не учли его глюки в оптимизаторе. Есть сравнения одного и того же алгоритма перестановок на разных версиях Си, Жавах и Дельфях - там всё хорошо видно. Интел Си - самый быстрый, и это не удивительно. На Дельфи можно написать так, что будет почти как на Интел, но нужно знать особенности глюков (Борманы после шестерки забили на оптимизацию, по этому скорость откомпилированных прог снижается от увеличения версии, вплоть до 10, там они вроде опять вспомнили, что нужно заниматься не только херней). На Си то же можно прекрасно писать. В сущности, хорошая программа на Си и на Дельфи и должны быть вровень по скорости. А вот Жава оказалась в аутсайдерах, и тут я то же не вижу ничего удивительного. Жава она вообще то не для скорости, а для удобства. И в принципе, если не смущает разница в первые десятки процентов - разницы вообще нет.


Добавлю. Ещё один интересный момент, связанный с вычислениями чисел с плавающей запятой. Дело в том, что сопроцессор всегда оперирует типом Extended. Но может вычисления с этим Extended производить с точностью как с Single, Double или Extended. Естественно это разная скорость вычисления за счет потери точности. Вполне возможна ситуация, когда сопроцессору подаётся число Double, он его преобразует в Extended, но вычисления проводит как с Single, а ответ возвращает как Double. Подробнее можно посмотреть на пример в: Неочевидные особенности вещественных чисел.

 

Время работы стандартной функции MQL4 MathMax, циклом по 2 и циклом по 1 тоже отличается.

Не намного но Вызов процедуры циклом выигрывает перед вызовом явно (тоже количество раз).

В тесте приведено соотношение циклом по 2 и циклом по 1,

Причем 2 или 100 большой разници нет главное что не 1. (Что то здесь не то ???)

Файлы:
 
HideYourRichess писал(а) >>

Интел Си - самый быстрый, и это не удивительно. На Дельфи можно написать так, что будет почти как на Интел, но нужно знать особенности глюков (Борманы после шестерки забили на оптимизацию, по этому скорость откомпилированных прог снижается от увеличения версии, вплоть до 10, там они вроде опять вспомнили, что нужно заниматься не только херней). На Си то же можно прекрасно писать. В сущности, хорошая программа на Си и на Дельфи и должны быть вровень по скорости. А вот Жава оказалась в аутсайдерах, и тут я то же не вижу ничего удивительного. Жава она вообще то не для скорости, а для удобства. И в принципе, если не смущает разница в первые десятки процентов - разницы вообще нет.

Интересно было бы реально сравнить компиляторы Си и Дельфи. Жаль, но, как я понял, тестировался только Дельфи. Если бы была такая же функция, написанная на С, то я бы попробовал ее откомпилить не интеловским компилятором, и получили бы реальные данные для сравнения. К сожалению, с Джавой не сравнить, так как, насколько я понимаю, библиотеку на ней не напишешь... (или я отстал от жизни?)

 
Urain >>:

Время работы стандартной функции MQL4 MathMax, циклом по 2 и циклом по 1 тоже отличается.

Не намного но Вызов процедуры циклом выигрывает перед вызовом явно (тоже количество раз).

В тесте приведено соотношение циклом по 2 и циклом по 1,

Причем 2 или 100 большой разници нет главное что не 1. (Что то здесь не то ???)

Ни каких мыслей, почему это так у меня нет.

 

There is a library that makes Delphi compiled code as fast as code compiled in C++. There are also improvements for DLL/shared memory management. Additionally there is support for different processor types.

The Fastcode library consists of 3 or 4 units per challenge. Each unit contains the winner functions for one challenge. Each challenge at least spans these three units: Direct Calling, CPUID based function selection, conditional compilation. If the challenge function exist in the RTL or VCL there is also a library unit that supports the patching principle.

Presently the challenges target the following architectures:
Pentium 4 Prescott
Pentium 4 Northwood
Pentium M Dothan
Pentium M Banias
AMD 64
Athlon XP
Blended
RTL Replacement
Pascal

Direct calling

All functions can be called direcly via these function interfaces

function XXXFastcodeP4P;
function XXXFastcodeP4N;
function XXXFastcodePMD;
function XXXFastcodePMB;
function XXXFastcodeAMD64;
function XXXFastcodeXP;
function XXXFastcodeBlended;
function XXXFastcodeRTL;
function XXXFastcodePas;

Sometimes the same function will be called through two or more interfaces if it is optimal in more targets.

Conditional compilation

Conditional compilation is also supported. One of the 9 above mentioned functions will be compiled in as the implementation behind this function interface

function XXXFastcode;

Compiler directives are named:
P4N, P4P, PMD, PMB, ATHLONXP, AMD64, BLENDED, PASCAL, RTLREPLACEMENT

Only one of these can be set at the same time.

CPU id based function selection

On library initialization a function pointer is initialized to point at the fastest function for the given processor. Call via the function pointer

function XXXFastcodeCPUID

and the call will be redirected to one of the functions

function XXXFastcodeP4N;
function XXXFastcodeP4P;
function XXXFastcodePMD;
function XXXFastcodePMB;
function XXXFastcodeAthlonXP;
function XXXFastcodeAMD64;

If the processor is none of these, this function will be called

function XXXFastcodeBlended;

but only if the processor supports IA32 extensions and MMX. IA32 extensions concist of instructions such as CMOVcc, FCMOVcc, FCOMI.

Otherwise this function will be called

function XXXFastcodeRTLReplacement


The Patching Principle

Each unit contains the winner functions, just like the direct calling unit, but it also contains patching code. This code iterates through the executable image and patches all calls to the RTL function such that said calls are redirected to the Fastcode versions.

Patching, in its simplest form is relatively straightforward. It is just a matter of finding the address of the system function to be patched, and inserting a new jump instruction at that address to jump to the replacement function. There are however a few important things to take into account.

  1. If the function being patched is less than 5 bytes is size, a jump cannot be inserted without possibly overwriting another system function.
  2. If the system function to be patched is already small or fast, then unless packages are being used, inserting a jump to a replacement function is very unlikely to produce any performance gain (from the calling programs viewpoint, we would be unnecessarily calling a jump to another function). For this reason fastcode function like the MaxInt, Round, etc are unlikely to see any improvement by patching.
  3. When packages are being used, the inserted jump is simply a replacement for an existing jump.
  4. A few API calls (VirtualProtect, FlushInstructionCache) are needed while performing the actual patching.

The unofficial FastMove unit by John O’Harrow uses patching to select the IA32, MMX or SSE replacement, but with an additional performance tweak:- When not using packages, rather than just inserting a jump at the original system.move location, John actually patch 58 bytes (of the original 64 bytes used by move). Within these 58 bytes, He can handle all small moves (<36 bytes) more efficiently.

How to modify and recompile a RTL/VCL unit

Directly inserting the RTL replacement function in the Delphi/C++ Builder library is probably the best option.

Recompilation of the RTL units (apart from SYSTEM.PAS) is also very straightforward.

  1. Edit the source code (\program files\borland\delphiX\source\RTL\sys directory).
  2. Ensure that MAKE.EXE (make utility) and DCC.EXE (command line compiler) are in the search path.
  3. In a DOS shell, go to the RTL directory (\program files\borland\delphiX\source\RTL) and type MAKE. This will create new DCU files in a subdirectory called LIB (you may need to create this directory).
  4. Copy the required DCU file created to the real LIB directory (\program
    files\borland\delphiX\LIB).
  5. Run or Restart Delphi.

Patching SYSTEM.PAS can get more complicated. If you are just directly replacing a function in SYSTEM.PAS with another, then no problems should occur. If however you are adding code to detect the CPU type and assign a function pointer to replace a system function etc., then virtually all of the DCU's in the RTL (and in most cases, also the VCL) will need to be replaced.

http://fastcode.sourceforge.net/

 

Извините что влазию в дисскусию с вопросом вродебы не втему....но всёже.......Приведите пожалуйста пример ДЛЛ, на дельфи, просто машки......Только осваиваю использование ДЛЛ-ок, Кроме дельфи толком языки не знаю...... В особенности работа с указателями........

Заранее благодарен!!!!!!!

Причина обращения: