I spent Friday afternoon coding a small profiler, may it be useful in reducing CPU usage for those that run time intensive backtests, e.g. with ML.
I've attached a backtest containing the file Profiler.cs with example of how to use it by using one of the QC University examples.
At the end of the backtest, it prints a log like this:
2017-08-26 00:00:00 Stopwatch at 145540 ms and 1455408662 ticks
2017-08-26 00:00:00 OnData: 100.00 % (87.78 %)
2017-08-26 00:00:00 ObjectiveScore: 0.11 % (0.11 %)
2017-08-26 00:00:00 SetPositions: 12.11 % (12.10 %)
2017-08-26 00:00:00 ObjectiveScore: 0.01 % (0.01 %)
In this case, it means that functions that wered profiled called by OnData took quite quite little time compared to other calls made by OnData itself (87.78% of time spent out of profiled sub-calls - probably it's the logging taking time).
In one of my real complicated projects, it can be useful to find out what's taking time, for instance from this log I can find out that warming up symbols after they're added by universe selection is the culprit:
2017-07-01 00:00:00 Stopwatch at 188665 ms and 1886654482 ticks
2017-07-01 00:00:00 OnData: 99.54 % (4.04 %)
2017-07-01 00:00:00 CheckTradables: 73.35 % (1.72 %)
2017-07-01 00:00:00 WarmUpFromHistory: 71.63 % (56.57 %)
2017-07-01 00:00:00 CheckSampleResolutionQueue: 0.79 % (0.73 %)
2017-07-01 00:00:00 ResolveSample: 0.06 % (0.06 %)
2017-07-01 00:00:00 Update: 14.01 % (14.01 %)
2017-07-01 00:00:00 CalcRank: 0.27 % (0.27 %)
2017-07-01 00:00:00 CheckSampleResolutionQueue: 3.05 % (1.10 %)
2017-07-01 00:00:00 ResolveSample: 1.95 % (1.95 %)
2017-07-01 00:00:00 EnterPositions: 0.61 % (0.59 %)
2017-07-01 00:00:00 Enter: 0.02 % (0.02 %)
2017-07-01 00:00:00 Update: 18.25 % (18.25 %)
2017-07-01 00:00:00 CalcRank: 0.20 % (0.20 %)
2017-07-01 00:00:00 Exit: 0.04 % (0.04 %)
2017-07-01 00:00:00 OnOrderEvent: 0.00 % (0.00 %)
2017-07-01 00:00:00 DisableAtEndOfDay: 0.46 % (0.06 %)
2017-07-01 00:00:00 CheckSampleResolutionQueue: 0.39 % (0.00 %)
2017-07-01 00:00:00 ResolveSample: 0.39 % (0.39 %)
2017-07-01 00:00:00 OnOrderEvent: 0.00 % (0.00 %)
2017-07-01 00:00:00 OnOrderEvent: 0.00 % (0.00 %)
Again, this is nothing fancy, but simple enough to copy paste into QC cloud environment. If you're running locally in Lean, it's probably better to use a real profiler.
Jared Broad
That's amazing Petter Hansson! Awesome project. Its an interesting idea to manually add it to the methods like that.
If there was enough demand a similar implementation could probably be done in the core behind the algorithm. Its a complex feature though! Pretty cool you've been able to get it working like that!
Maybe you could make a "ProfiledAlgorithm : QCAlgorithm" and submit it to Github? This way people would just need to specify the ProfiledAlgorithm base class. I *think* that would help wrap all the methods under the surface?
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Petter Hansson
I think having ProfiledAlgorithm as a base class would be problematic, because you need to be able to call the user code between the Begin and End calls. So it's basically inverted - ProfiledAlgorithm should be calling base methods. Maybe it's possible to write a ProfiledAlgorithm<AlgoT> : AlgoT where AlgoT : QCAlgorithm to intercept calls, I don't know if C# generics semantics are this flexible. I can certainly try it out. Nonetheless, it wouldn't catch reflection callbacks.
An advanced alternative would be to A) look up all callbacks via reflection, B) generate a class deriving QCAlgoithm on the fly that calls user callbacks via its own wrapping callbacks. Runtime code generation seems overkill for this project, though, and probably would take more time than I'm willing to spend on it.
The most straightforward solution would be to put such code in QCAlgorithm and friends directly. However, that implies putting a large number of try-finallys in the code. It's doable, the C# libaries I've worked on in the past have used this (as they were forced to run in Unity), but it certainly isn't pretty. It's however limit to a few methods, and you can also do stuff like the following when not overly concerned about the performance of the call:
void MakeProfiledCall(string method, params object[] pars) { Profile.Begin(method); //try-finally wrapper (forgot) //call method via reflection Profile.End(method); } //or void MakeProfiledCall(string method, Action func) { Profile.Begin(method); //try-finally func(); Profile.End(method); }
It's a shame C# doesn't support something similar to C++ RAI (using statement is about as heavy as try-finally).
Patrick Star
Great work, Petter! I wish I had this a couple of months ago when I was struggling with OutOfMemoryErrors.
In Java we have Spring and Aspects. For example you can inject a before or after advice to a method. Don't we have something like that in C# to inject your profiles through configuration?
Andrestone
Beautiful, artful work. Two thumbs up!
Petter Hansson
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!