Unfortunately, the built-in profiler works only in online charts, where inefficient parts of code are not so easy to spot. It would be much more convenient to profile code in the tester (during single tests or even batch optimization of an expert adviser). For that purpose we can implement a profiler ourselves as a set of classes. It will be simple yet sufficient enough to perform main profiler tasks.
- Profiler - implements the main functionality;
- ProfilerLocator - is intended for pointing program contexts or parts (such as functions or sequences of code lines), where we want to measure performance;
- ProfilerObject - is a helper class, which allows for automating starts/stops of profiling process in accordance to its objects lifetime cycles; for example, you can declare such an object in a function (specifically, create an automatic instance on the stack), and then - no matter how many exit points the function has - every time control flow leaves the scope of the function, the object will be destroyed, which in turn will stop profiling (until the next time the function is entered again, which in turn will start profiling automatically again).
The Profiler object is already declared in the header. By default it's initialized with 100 points for measurements, that is one can evaluate duration of execution of 100 code fragments. Every point is basically backed with an element in internal arrays which store time lapse and count of times of code execution. If you need more points, adjust the initialization: the required number of measurement points is passed as the parameter of the Profiler constructor.
The simpliest, but slowest (and hence not recommended) method is:
... // code for profiling
profiler.StopProfiler(pid);
The fragment above is identified by a unique name, specified in the StartProfiler method. There are more efficient forms of the method StartProfiler, for example the one which identifies code fragments by integer numbers. For this variant to use, one could first register all code fragments in the profiler (for example, in OnInit):
profiler.SetName(2, "Second");
Then you can start and stop profiling by the following calls:
... // code fragment 1
profiler.StopProfiler(1);
profiler.StartProfiler(2);
... // code fragment 2
profiler.StopProfiler(2);
As a convenient way of defining profiling points one may use ProfilerLocator objects inline - just before corresponding code fragments:
profiler.StartProfiler(locator3);
... // code
profiler.StopProfiler(locator3);
The fragment above is identified by the locator object, which generates a unique integer identifier internally. Please note that the locator object is declared static, because it should be created only once. This guarantees that every execution of the code fragment will use the same profiling element accumulating statistics. If the object would not be static, every single pass would generate (reserve) new measurement point, and everyone would have the same name "CalculateProfit".
ProfilerObject obj(locator);
Automatic creation of the ProfilerObject object starts profiling of the entire function scope automatically, and its deletion upon function termination stops profiling automatically as well. A shorthand macro _PROFILEFUNC_ is defined for this in the header file.
Here is an example.
#define CALC_PROFILE_ID 10
_PROFILESCRIPT_;
void subfunc()
{
_PROFILEFUNC_;
for(int i = 0; i < 1000; i++)
{
profiler.StartProfiler(CALC_PROFILE_ID);
for(int j = 0; j < 10000; j++)
{
MathSqrt(MathPow(i, 3));
}
profiler.StopProfiler(CALC_PROFILE_ID);
}
Sleep(250);
}
void OnStart()
{
profiler.SetName(CALC_PROFILE_ID, "calculation");
Sleep(500);
subfunc();
profiler.PrintAllStats();
}
This script will produce something like this:
profilertest 1 1 1607 100.00 WARNING! Keeps running
subfunc 1 1 1107 68.89
calculation 1000 0 858 53.39