Gauge - Iterative polling
use v6.d;
use Gauge;
#|[ Jury-rigged benchmark that keys it/s counts of an evaluated code block. ]
sub MAIN(
**@code,
#|[ The number of threads to dedicate to benchmarking. ]
UInt:D :j(:$jobs) = $*KERNEL.cpu-cores.pred,
#|[ The duration in seconds over which timestamps will be aggregated. ]
Real:D :p(:$period) = 1,
#|[ The cooldown in seconds performed between each individual benchmark. ]
Real:D :c(:$cooldown) = (try 2 / 3 * $jobs * $period) // (2 / 3 * $*KERNEL.cpu-cores.pred),
#|[ Whether or not ANSI 24-bit SGR escape sequences should be suppressed.
These highlight blocks of it/s counts opening with any new maximum. ]
Bool:D :m(:$mono) = False,
--> Nil) {
use MONKEY-SEE-NO-EVAL;
map $mono ?? &mono !! &poly,
Gauge(EVAL Qa[-> { @code.join(' ') }])
.poll($period)
.pledge($jobs)
.throttle($cooldown);
}
#=[ Benchmark threads are run once an iteration of any existing threads has
been exhausted. This is staggered by the cooldown, and by default, allows
for multiple benchmarks to be taken with a brief overlap of threaded work,
reducing the time needed to collect results while keeping low overhead. ]
sub poly(Int:D $next --> Empty) {
my constant @mark = «\e[48;5;198m \e[48;5;202m \e[48;5;178m \e[48;5;41m \e[48;5;25m \e[48;5;129m»;
state $mark is default(-1);
state $peak is default(-1);
my $jump := $peak < $next;
$mark += $jump;
$mark %= @mark;
$peak max= $next;
my $note = @mark[$mark];
$note ~= $jump ?? '⊛' !! '∗';
$note ~= " \e[m";
$note ~= $next;
put $note
}
sub mono(Int:D $next --> Empty) {
state $peak is default(-1);
my $jump := $peak < $next;
$peak max= $next;
my $note = $jump ?? '⊛' !! '∗';
$note ~= ' ';
$note ~= $next;
put $note
}
class Gauge is Seq { ... }
Gauge
, in general, provides an interface for a collection of temporal, lazy, non-deterministic iterators. At its base, it attempts to measure durations of calls to a block with as little overhead as possible in order to avoid unnecessary influence over results. This can stack operations mapping its iterator until it decays to a Seq
via Iterable
or the iterator
itself.
method CALL-ME(::?CLASS:_: Block:D $block --> ::?CLASS:D)
Produces a new Gauge
sequence of native uint64
durations of a call to the given block. Measurements of each duration are not monotonic, thus leap seconds and hardware errors will skew results.
method poll(::?CLASS:D: Real:D $seconds --> ::?CLASS:D)
Returns a new Gauge
sequence that produces an Int:D
count of iterations of the former totalling a duration of $seconds
. This will take longer than the given argument to complete due to the overhead of iteration, which may be measured by Gauge
itself in combination with head
.
method throttle(::?CLASS:D: Real:D $seconds --> ::?CLASS:D)
Returns a new Gauge
sequence that will sleep $seconds
between iterations of its former. If a poll
is applied later, this will incorporate the time waited into the time it takes to complete an iteration, but not any steps in between required to caculated. The total time throttled is allowed to exceed any poll applied to a Gauge
, but never by any more than one iteration.
method pledge(::?CLASS:D: UInt:D $length --> ::?CLASS:D)
Returns a new Gauge
sequence that, across an array of threads of breadth $length
, pledges that a form of iteration will be continuously invoked across a threaded repetition of this iterator's component iterators in the process. When an empty length is given, this will not change the iteration.
When a length of 1
is given, this will produce a covenant wrapping the iterator. This is a thread that upon receiving a query to perform an iteration, will produce its result, then go ahead and perform another iteration while waiting for the next query. This means switching from skip
to head
may perform an extra skip
whose result is discarded while waiting for head
. Such a thread will finish in a sink context, but is allowed to wait until the end of a program after decaying to another Seq
when it doesn't play nice.
When a length of 2
or more is given, a contract wrapping this number of covenants will be formed. This carries a particular cycle followed to accomodate any throttling performed beforehand. A thread must be spawned in order to work, but any prior threads may complete an iteration during this timespan, and will be waited on in reverse as each covenant is run in its original ordering.
Ben Davies (Kaiepi)
Copyright 2023 Ben Davies
This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.