-
-
Save PhDuck/a8e8a6d7cd2f68255ed454777cbafea6 to your computer and use it in GitHub Desktop.
Template benchmark code for AL
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
codeunit 50102 Benchmark | |
{ | |
trigger OnRun() | |
begin | |
BenchmarkTemplate(20000); | |
end; | |
// TODO: Replace with logic to benchmark, argument handling is manual :(. | |
local procedure Target(input: Text): Boolean | |
var | |
i: Integer; | |
limit: Integer; | |
c: Char; | |
begin | |
// NOTE: Code has not been tested, use with caution. | |
// The code below doesn't check the first hour char in hh:mm since it doesn't matter, a2:22 is anyways valid. | |
limit := StrLen(input); | |
for i := 2 to limit - 2 do begin | |
c := input[i]; | |
if c = ':' then begin | |
// AL doesn't short-circuit so this check needs to be in here to avoid index out of bounds. | |
c := input[i - 1]; | |
if (c >= '0') and (c <= '9') then begin | |
c := input[i + 1]; | |
if (c >= '0') and (c <= '5') then begin | |
c := input[i + 2]; | |
if (c >= '0') and (c <= '9') then begin | |
exit(true); | |
end; | |
end; | |
end; | |
end; | |
end; | |
exit(false); | |
end; | |
/// <summary> | |
/// Run the benchmark with dynamic adjustments to resolve resolution issues and tries to run roughly (very rough) the time specified in <paramref name="timeToRunInMs"/>. | |
/// Removes some common pitfalls and prints the resulting per benchmark call measurment. | |
/// </summary> | |
/// <param name="timeToRunInMs">Rougly how long to run the benchmark, must be strictly larger than zero.</param> | |
local procedure BenchmarkTemplate(timeToRunInMs: Integer) | |
var | |
outStartT: Time; | |
startT: Time; | |
dur: Duration; | |
total: Duration; | |
timings: List of [Duration]; | |
limit: Integer; | |
innerLimit: Integer; // Amount of times to run benchmark method to ensure duration is larger than time resolution of BC's Time data type. | |
i: Integer; | |
j: Integer; | |
begin | |
// Code is heavily inspired by BenchmarkDotNet - https://github.com/dotnet/BenchmarkDotNet | |
if (timeToRunInMs <= 0) then | |
Error('Benchmark must run for more than zero ms. Aim for 20 seconds (20 000).'); | |
outStartT := Time; | |
// TODO: Replace with your benchmark here once to ensure one-time overhead is removed. | |
Target('23:59'); | |
// Dynamically determine the inner limit, i.e., how many times to run the code before it takes longer than 1ms | |
// which is the resolution of time. | |
// This isn't stable if the benchmark time is too varied i.e. a few long calls take more than 5 ms, | |
// but later the same amount of calls suddenly takes < 1ms. Using 5 ms makes this less likely to happen. | |
startT := Time; | |
i := 0; | |
repeat | |
Target('23:59'); | |
dur := Time - startT; | |
i := i + 1; | |
until (dur > 5); // Five ms to ensure overhead of calling doesn't impact it too much, this isn't necessary for macro-benchmarks. | |
innerLimit := i; | |
// We dynamically adjust the call count to make take ~timeToRunInMs. | |
limit := System.Round(timeToRunInMs / dur, 1); | |
for i := 0 to limit do begin | |
startT := Time; | |
for j := 0 to innerLimit do begin | |
Target('23:59'); | |
end; | |
dur := Time - startT; | |
timings.Add(dur); | |
end; | |
//i := 0; | |
foreach dur in timings do begin | |
total := dur + total; | |
// Consider uncommenting the below lines to inspect individual timings and look for large variance. | |
//Message('Timing %1 took: %2 ms per operation.', i, dur / innerLimit); | |
//i += 1; | |
end; | |
Message('Benchmark took: %1 ms. Executed benchmark %2 times. Mean execution time per operation: %3 ms', Format(Time - outStartT), innerLimit * limit, total / (innerLimit * limit)); | |
end; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment