A function alias that takes a pointer to the value accumulated so far, and the next ZoneData to accumulate. It returns the resulting accumulated value. The first parameter will be null on the first call.
Must be pure nothrow @nogc.
A function alias that takes two const(char) arrays and returns a bool. If true is returned, two zones with whose info strings were passed to match() are considered the same zone and will be merged and accumulated.
Must be pure nothrow @nogc.
An example use-case for a custom match() function is to accumulate related zones with slightly different names (e.g. numbered draw batches), or conversely, to prevent merging zones with identical names (e.g. to see each individual draw as a separate zone).
Array to use for temporary storage during accumulation as well as storage in the returned range. Must be long enough to hold zones from all passed zone ranges, i.e. the sum of their walkLengths. To determine this length, use import std.range; zoneRange.walkLength;.
One or more zone ranges to accumulate.
A ForwardRange of AccumulatedZoneData. Each element contails ZoneData plus the return value of the accumulate function.
Note: The current implementation is likely to be slow for large inputs. It's probably too slow for real-time usage except if the inputs are very small.
Example of an accumulate function:
// Increments the accumulated value when called. Useful to determine the // number of times a Zone was entered. size_t accum(size_t* aPtr, ref const ZoneData z) pure nothrow @nogc { return aPtr is null ? 1 : *aPtr + 1; }
Can be used e.g. to get a 'total' of all recorded frames. If each frame has one top-level zone with matching info strings, the top-level zones are merged, then matching children of these zones, and so on. The result is a zone range representing a single tree. The accumulate function can be used, for example, to calculate max duration of matching zones, getting a 'worst case frame scenario', to calculate the number of times each zone was entered, or even multiple things at the same time.
// Count the number of times each zone was entered. import tharsis.prof; auto storage = new ubyte[Profiler.maxEventBytes + 128]; auto profiler = new Profiler(storage); foreach(i; 0 .. 3) { import std.datetime; auto startTime = Clock.currStdTime(); // Wait long enough so the time gap is represented by >2 bytes. while(Clock.currStdTime() - startTime <= 65536) { continue; } auto zone1 = Zone(profiler, "zone1"); { auto zone11 = Zone(profiler, "zone11"); } startTime = Clock.currStdTime(); // Wait long enough so the time gap is represented by >1 bytes. while(Clock.currStdTime() - startTime <= 255) { continue; } { auto zone12 = Zone(profiler, "zone12"); } } // Count the number of instances of each zone. size_t accum(size_t* aPtr, ref const ZoneData z) pure nothrow @nogc { return aPtr is null ? 1 : *aPtr + 1; } auto zones = profiler.profileData.zoneRange; auto accumStorage = new AccumulatedZoneData!accum[zones.walkLength]; auto accumulated = accumulatedZoneRange!accum(accumStorage, zones.save); assert(accumulated.walkLength == 3); import std.stdio; foreach(zone; accumulated) { writeln(zone); }
1 // Accumulate minimum, maximum, average duration and more simultaneously. 2 3 // This example also uses C malloc/free, std.typecons.scoped and std.container.Array 4 // to show how to do this without using the GC. 5 6 import tharsis.prof; 7 8 const storageLength = Profiler.maxEventBytes + 2048; 9 10 import core.stdc.stdlib; 11 // A simple typed-slice malloc wrapper function would avoid the ugly cast/slicing. 12 ubyte[] storage = (cast(ubyte*)malloc(storageLength))[0 .. storageLength]; 13 scope(exit) { free(storage.ptr); } 14 15 import std.typecons; 16 // std.typecons.scoped! stores the Profiler on the stack. 17 auto profiler = scoped!Profiler(storage); 18 19 // Simulate 16 'frames' 20 foreach(frame; 0 .. 16) 21 { 22 Zone topLevel = Zone(profiler, "frame"); 23 24 // Simulate frame overhead. Replace this with your frame code. 25 { 26 Zone nested1 = Zone(profiler, "frameStart"); 27 foreach(i; 0 .. 1000) { continue; } 28 } 29 { 30 Zone nested2 = Zone(profiler, "frameCore"); 31 foreach(i; 0 .. 10000) { continue; } 32 } 33 } 34 35 // Accumulate data into this struct. 36 struct ZoneStats 37 { 38 ulong minDuration; 39 ulong maxDuration; 40 // Needed to calculate average duration. 41 size_t instanceCount; 42 43 // We also need the total duration to calculate average, but that is accumulated 44 // by default in AccumulatedZoneData. 45 } 46 47 // Gets min, max, total duration as well as the number of times the zone was entered. 48 ZoneStats accum(ZoneStats* aPtr, ref const ZoneData z) pure nothrow @nogc 49 { 50 if(aPtr is null) { return ZoneStats(z.duration, z.duration, 1); } 51 52 import std.algorithm: min, max; 53 return ZoneStats(min(aPtr.minDuration, z.duration), 54 max(aPtr.maxDuration, z.duration), 55 aPtr.instanceCount + 1); 56 } 57 58 auto zones = profiler.profileData.zoneRange; 59 // Allocate storage to accumulate in with malloc. 60 const zoneCount = zones.walkLength; 61 alias Data = AccumulatedZoneData!accum; 62 auto accumStorage = (cast(Data*)malloc(zoneCount * Data.sizeof))[0 .. zoneCount]; 63 scope(exit) { free(accumStorage.ptr); } 64 65 auto accumulated = accumulatedZoneRange!accum(accumStorage, zones.save); 66 67 // Write out the results. 68 foreach(zone; accumulated) with(zone.accumulated) 69 { 70 import std.stdio; 71 writefln("id: %s, min: %s, max: %s, avg: %s, total: %s, count: %s", 72 zone.id, minDuration, maxDuration, 73 zone.duration / cast(double)instanceCount, zone.duration, instanceCount); 74 }
1 // Get the average duration of a top-level zone. This is a good way to determine 2 // average frame duration as the top-level zone often encapsulates a frame. 3 4 // This example also uses C malloc/free, std.typecons.scoped and std.container.Array 5 // to show how to do this without using the GC. 6 7 import tharsis.prof; 8 9 const storageLength = Profiler.maxEventBytes + 2048; 10 11 import core.stdc.stdlib; 12 // A simple typed-slice malloc wrapper function would avoid the ugly cast/slicing. 13 ubyte[] storage = (cast(ubyte*)malloc(storageLength))[0 .. storageLength]; 14 scope(exit) { free(storage.ptr); } 15 16 import std.typecons; 17 // std.typecons.scoped! stores the Profiler on the stack. 18 auto profiler = scoped!Profiler(storage); 19 20 // Simulate 16 'frames' 21 foreach(frame; 0 .. 16) 22 { 23 Zone topLevel = Zone(profiler, "frame"); 24 25 // Simulate frame overhead. Replace this with your frame code. 26 { 27 Zone nested1 = Zone(profiler, "frameStart"); 28 foreach(i; 0 .. 1000) { continue; } 29 } 30 { 31 Zone nested2 = Zone(profiler, "frameCore"); 32 foreach(i; 0 .. 10000) { continue; } 33 } 34 } 35 36 // Count the number of instances of each zone. 37 size_t accum(size_t* aPtr, ref const ZoneData z) pure nothrow @nogc 38 { 39 return aPtr is null ? 1 : *aPtr + 1; 40 } 41 42 import std.algorithm; 43 // Top-level zones are level 1. 44 // 45 // Filtering zones before accumulating allows us to decrease memory space needed for 46 // accumulation, as well as speed up the accumulation, which is relatively expensive. 47 auto zones = profiler.profileData.zoneRange.filter!(z => z.nestLevel == 1); 48 // Allocate storage to accumulate in with malloc. 49 const zoneCount = zones.walkLength; 50 alias Data = AccumulatedZoneData!accum; 51 auto accumStorage = (cast(Data*)malloc(zoneCount * Data.sizeof))[0 .. zoneCount]; 52 scope(exit) { free(accumStorage.ptr); } 53 54 auto accumulated = accumulatedZoneRange!accum(accumStorage, zones.save); 55 56 // If there is just one top-level zone, and it always has the same info ("frame" in 57 // this case), accumulatedZoneRange with the default match function will have exactly 58 // 1 element; with the accumulated result for all instances of the zone. Also here, 59 // we use $(D duration), which is accumulated by default. 60 import std.stdio; 61 writeln(accumulated.front.duration / cast(real)accumulated.front.accumulated);
Returns a range that accumulates (merges) matching zones from one or more zone ranges.
On each nesting level from top to bottom, finds zones that match based on given match function and merges them into one zone, accumulating data from merged zone using the accumulate function. Merged zones contain summed durations and start times. The default match function compares info strings of two zones for equality.