Context
Whenever an API-call is made frequently, but ideally it should be called conditionally. The conditional call construct adds performance, but since it is optional, it is not done in major parts of the code.Context Example - Logging
A logging call can be made as follows:For performance reasons the call should be
The log.Info call is more CPU and memory expensive than the log.InfoEnabled call.
We can expect that the first construct is cheapest, if info level is enabled, but the later construct is fastest and cheapest, if info level is disabled.
Should you now feel biased towards the first construct, just think the example with logging level debug or trace. Furthermore, consider the following run-time measurements if you are not convinced about the above performance arguments:
If info level is disabled the 2nd construct is over twice as fast as the 1st construct and the GC collects generation 0 objects about 5 times as seldom with the 2nd construct as the 1st construct.
If info level is enabled they perform almost the same.
Problem
The design problem at hand is that the conditional API-call can be omitted. By design it is optional, so the simple construct will continue to be added to the code base; it is simple and works. A design change that will force programmers to make the ideal construct is desired.
Forces
- The expensive and slowest construct is easiest.
- The performance gain of the conditional construct might not be obvious.
- The usage of the API-call might be high and scattered around in the code base.
- The API-call might be secondary to the problem being solved in the context; focus is not on performance.
Solution
The solution is to expect an evaluation API-call before the desired API-call can be made:
- Create an evaluation API-method that stores a caller argument and returns true, if the matching API-method is expected to be called by the same caller, and
- Create the matching API-method to take a caller argument that is checked against the stored caller argument.
The evaluation API-method serves to establish a contract between the API and a specific caller. If the evaluation API-method returns false, no contract exists. Otherwise, the caller is contracted to call a matching API-method to get released from the contract.
If a caller tries to use the API without an evaluation API-call that returns true, the API can throw an exception.
If a caller does not free himself via a matching API-call, the API can throw an exception on next call and name the caller that did not fulfill a contract.
Solution Example - Logging
The conditional logging construct can be turned into a Spot Contractor as follows:
The API simply requires the addition caller argument:
Performance of this API compared with the prior 2 mentioned constructs can be discussed from the following test results:
If info level is enabled the 3 constructs perform similar, but if info level is disabled the Spot Contractor construct performs as good as the conditional construct.
In summary the Spot Contractor pattern can be used to force logging usages into performance optimized constructs.
As a side note it is since C# language v. 3.0 possible to turn InfoPrepare() and Info() into a single method using lambda expressions. The Common.Logging framework has extended its API to do so - you can read about it here.
Solution Example - Assert/FailFast
Another case where strings are allocated even though not used are in Assert or FailFast calls where error messages are passed as arguments. In these cases the Spot Contractor pattern can be used to improve performance.
Solution Example - Measurement Start/Stop
A common way of measuring performance is to use Start + Stop calls with a measurement name argument. In this case the Spot Contractor pattern can be used to force in a check whether performance measurement is enabled at all.