@@ -297,18 +297,19 @@ function withtypename(f, io, t)
297
297
end
298
298
end
299
299
300
- function bindata (sorteddata, nbins, min, max )
301
- Δ = (max - min) / nbins
302
- bins = zeros (nbins )
303
- lastpos = 0
304
- for i ∈ 1 : nbins
305
- pos = searchsortedlast (sorteddata, min + i * Δ)
306
- bins[i] = pos - lastpos
307
- lastpos = pos
300
+ function bindata (data, fences :: AbstractRange )
301
+ @assert step (fences) > 0
302
+ bins = zeros (Int, length (fences) )
303
+ for t in data
304
+ i = searchsortedlast (fences, t)
305
+ # Any data to the left of the leftmost divider is ignored:
306
+ iszero (i) && continue
307
+ bins[i] += 1
308
308
end
309
309
bins
310
310
end
311
- bindata (sorteddata, nbins) = bindata (sorteddata, nbins, first (sorteddata), last (sorteddata))
311
+
312
+ # @test bindata([1.1, 3.1, 99, -99], 1:3) == [1,0,2]
312
313
313
314
function asciihist (bins, height= 1 )
314
315
histbars = [' ▁' , ' ▂' , ' ▃' , ' ▄' , ' ▅' , ' ▆' , ' ▇' , ' █' ]
@@ -347,11 +348,13 @@ Base.show(io::IO, t::TrialJudgement) = _show(io, t)
347
348
348
349
function Base. show (io:: IO , :: MIME"text/plain" , t:: Trial )
349
350
pad = get (io, :pad , " " )
351
+
350
352
boxcolor = :light_black
351
353
boxspace = " "
352
354
modulestr = " " # "BenchmarkTools."
353
355
avgcolor = :green
354
356
medcolor = :blue
357
+ captioncolor = :light_black
355
358
showpercentile = 99 # used both for display time, and to set right cutoff of histogram
356
359
357
360
allocsstr = if allocs (t) == 0
@@ -406,90 +409,74 @@ function Base.show(io::IO, ::MIME"text/plain", t::Trial)
406
409
407
410
printstyled (io, pad, " │" , boxspace; color= boxcolor)
408
411
printstyled (io, " min " ; color= :default )
409
- printstyled (io, prettytime (minimum (t. times)); color= :default , bold= true )
412
+ mintime = minimum (t. times)
413
+ printstyled (io, prettytime (mintime); color= :default , bold= true )
410
414
print (io, " , " )
411
415
printstyled (io, " median " ; color= medcolor)
412
- printstyled (io, prettytime (median (t. times)); color= medcolor, bold= true )
416
+ medtime = median (t. times)
417
+ printstyled (io, prettytime (medtime); color= medcolor, bold= true )
413
418
# printstyled(io, " (½)"; color=medcolor)
414
419
print (io, " , " )
415
420
printstyled (io, " mean " ; color= avgcolor)
416
- printstyled (io, prettytime (mean (t. times)); color= avgcolor, bold= true )
421
+ avgtime = mean (t. times)
422
+ printstyled (io, prettytime (avgtime); color= avgcolor, bold= true )
417
423
# printstyled(io, " (*)"; color=avgcolor)
418
424
print (io, " , " )
419
425
print (io, showpercentile, " ᵗʰ " )
420
- printstyled (prettytime (quantile (t. times, showpercentile/ 100 )); bold= true )
426
+ quantime = quantile (t. times, showpercentile/ 100 )
427
+ printstyled (prettytime (quantime); bold= true )
421
428
println (io)
422
429
423
430
printstyled (io, pad, " │" , boxspace; color= boxcolor)
424
431
println (io, allocsstr)
425
432
426
433
if ! all (iszero, t. gctimes)
427
434
# Mean GC time is just that; then we take the percentage of the mean time
428
- avggctime = prettytime (mean (t. gctimes))
429
- avegcpercent = prettypercent (mean (t. gctimes) / mean (t. times))
435
+ printstyled (io, pad, " │" , boxspace; color= boxcolor)
436
+ _avgc = mean (t. gctimes)
437
+ print (io, " GC time: mean " , prettytime (_avgc))
438
+ printstyled (io, " (" , prettypercent (_avgc / avgtime), " )" ; color= avgcolor)
430
439
431
440
# Maximum GC time is _not_ taken as the GC time of the slowst run, maximum(t).
432
441
# The percentage shown is of the same max-GC run, again not the percentage of longest time.
433
442
# Of course, very often the slowest run is due to GC, and these distinctions won't matter.
434
443
_t, _i = findmax (t. gctimes)
435
- maxgctime = prettytime (_t)
436
- maxgcpercent = prettypercent (_t / t. times[_i])
437
-
438
- printstyled (io, pad, " │" , boxspace; color= boxcolor)
439
- print (io, " GC time: mean " , avggctime)
440
- printstyled (io, " (" , avegcpercent, " )" ; color= avgcolor)
441
- println (io, " , max " , maxgctime, " (" , maxgcpercent, " )" )
444
+ println (io, " , max " , prettytime (_t), " (" , prettypercent (_t / t. times[_i]), " )" )
442
445
end
443
446
444
447
# Histogram
445
448
446
- # Axis ends at this quantile, same as displayed time, ideally:
447
- histquantile = showpercentile/ 100
449
+ logbins = get (io, :logbins , nothing ) == true
450
+ caption = logbins ? (" log(counts) from " * samplesstr) : samplesstr
451
+
448
452
# The height and width of the printed histogram in characters:
449
- histheight = 2
450
- histwidth = max (min (90 , displaysize (io)[2 ]), length (samplesstr) + 24 ) - 5 - length (boxspace)
453
+ histheight = 2
454
+ _nonhistwidth = 5 + length (boxspace)
455
+ _minhistwidth = 18 + length (caption)
456
+ histwidth = max (_minhistwidth, min (90 , displaysize (io)[2 ]) - _nonhistwidth)
451
457
# This should fit it within your terminal, but stops growing at 90 columns. Below about
452
458
# 55 columns it will stop shrinking, by which point the first line has already wrapped.
453
459
454
- times = sort (t. times)
455
-
456
- # This needs sorted times:
457
- histtimes = times[1 : round (Int, histquantile* end )]
458
- histmin = get (io, :histmin , low_edge (histtimes))
459
- histmax = get (io, :histmax , high_edge (histtimes))
460
-
461
- logbins = get (io, :logbins , nothing )
462
- bins = bindata (histtimes, histwidth - 1 , histmin, histmax)
463
- append! (bins, [1 , floor ((1 - histquantile) * length (times))])
464
- if logbins === true
465
- bins, logbins = log .(1 .+ bins), true
466
- else
467
- logbins = false
460
+ histmin = get (io, :histmin , low_edge (t. times, mintime, avgtime))
461
+ histmax = get (io, :histmax , high_edge (t. times, avgtime, quantime))
462
+
463
+ # Here nextfloat() ensures both endpoints included, will only matter for
464
+ # artificial cases such as: Trial(Parameters(), [3,4,5], [0,0,0], 0, 0)
465
+ fences = range (histmin, nextfloat (histmax), length= histwidth)
466
+ bins = bindata (t. times, fences)
467
+ # Last bin is everything right of last fence, introduce a gap for printing:
468
+ _lastbin = pop! (bins)
469
+ push! (bins, 0 , _lastbin)
470
+ if logbins
471
+ bins = log .(1 .+ bins)
468
472
end
469
473
hist = asciihist (bins, histheight)
470
- hist[:,end - 1 ] .= ' '
471
- maxbin = maximum (bins)
472
-
473
- # delta1 = (histmax - histmin) / (histwidth - 1)
474
- # if delta1 > 0
475
- # medpos = 1 + round(Int, (histtimes[length(times) ÷ 2] - histmin) / delta1)
476
- # avgpos = 1 + round(Int, (mean(times) - histmin) / delta1)
477
- # # TODO replace with searchsortedfirst & range?
478
- # q25pos = 1 + round(Int, (histtimes[max(1,length(times) ÷ 4)] - histmin) / delta1)
479
- # q75pos = 1 + round(Int, (histtimes[3*length(times) ÷ 4] - histmin) / delta1)
480
- # else
481
- # medpos, avgpos = 1, 1
482
- # q25pos, q75pos = 1, 1
483
- # end
484
- _centres = range (histmin, histmax, length= length (bins)- 2 )
485
- fences = _centres .+ step (_centres)/ 2
486
- avgpos = searchsortedfirst (fences, mean (times))
487
- medpos = searchsortedfirst (fences, median (times))
488
- # @show medpos medpos2
489
- q25pos = searchsortedfirst (fences, quantile (times, 0.25 ))
490
- q75pos = searchsortedfirst (fences, quantile (times, 0.75 ))
491
- # @show q25pos q25pos2
492
- # @show q75pos q75pos2
474
+ hist[:, end - 1 ] .= ' '
475
+
476
+ avgpos = searchsortedlast (fences, avgtime)
477
+ medpos = searchsortedlast (fences, medtime)
478
+ q25pos = searchsortedlast (fences, quantile (t. times, 0.25 )) # might be 0, that's OK
479
+ q75pos = searchsortedlast (fences, quantile (t. times, 0.75 ))
493
480
494
481
# Above the histogram bars, print markers for special ones:
495
482
printstyled (io, pad, " │" , boxspace; color= boxcolor)
@@ -499,12 +486,13 @@ function Base.show(io::IO, ::MIME"text/plain", t::Trial)
499
486
if i == avgpos
500
487
printstyled (io, " *" , color= avgcolor, bold= true )
501
488
elseif i == medpos ||
502
- (medpos== avgpos && i== medpos- 1 && median (times) <= mean (times) ) ||
503
- (medpos== avgpos && i== medpos+ 1 && median (times) > mean (times) )
504
- # marker for "median" is moved one to the left if they collide exactly
489
+ (medpos== avgpos && i== medpos- 1 && medtime <= avgtime ) ||
490
+ (medpos== avgpos && i== medpos+ 1 && medtime > avgtime )
491
+ # Marker for "median" is moved one to the left if they collide exactly
505
492
# printstyled(io, "½", color=medcolor)
506
493
printstyled (io, " ◑" , color= medcolor)
507
494
elseif i == q25pos
495
+ # Quartile markers exist partly to explain the median marker, without needing a legend
508
496
# printstyled(io, "¼", color=:light_black)
509
497
printstyled (io, " ◔" , color= :light_black )
510
498
elseif i == q75pos
@@ -521,46 +509,42 @@ function Base.show(io::IO, ::MIME"text/plain", t::Trial)
521
509
istop = findlast (!= (' ' ), view (hist, r, :))
522
510
for (i, bar) in enumerate (view (hist, r, :))
523
511
i > istop && break # don't print trailing spaces, as they waste space when line-wrapped
524
- color = :default
525
512
if i == avgpos
526
- color = avgcolor
527
- elseif i == medpos # if the bars co-incide, colour the mean? matches labels
528
- color = medcolor
513
+ printstyled (io, bar; color= avgcolor)
514
+ # If mean & median bars co-incide, colour the mean. Matches markers above.
515
+ elseif i == medpos
516
+ printstyled (io, bar; color= medcolor)
529
517
elseif bins[i] == 0
530
- color = :light_black
518
+ printstyled (io, bar; color= :light_black )
519
+ else
520
+ printstyled (io, bar; color= :default )
531
521
end
532
- printstyled (io, bar; color = color)
522
+
533
523
end
534
524
end
535
525
536
- remtrailingzeros (timestr) = replace (timestr, r" \. ?0+ " => " " )
537
- minhisttime, maxhisttime = remtrailingzeros .(prettytime .(round .([histmin, histmax], sigdigits= 3 )))
526
+ # Strings for axis labels, rounded again in case you supply :histmin => 123.456
527
+ minhisttime = replace (prettytime (round (histmin, sigdigits= 3 )), r" \. ?0+ " => " " )
528
+ maxhisttime = replace (prettytime (round (histmax, sigdigits= 3 )), r" \. ?0+ " => " " )
538
529
539
530
println (io)
540
531
printstyled (io, pad, " └" , boxspace; color= boxcolor)
541
532
print (io, minhisttime)
542
- # Caption is only printed if logbins has been selected:
543
- caption = logbins ? (" log(counts) from " * samplesstr) : samplesstr
544
- printstyled (io, " " ^ ((histwidth - length (caption)) ÷ 2 - length (minhisttime)), caption; color= :light_black )
533
+ printstyled (io, " " ^ ((histwidth - length (caption)) ÷ 2 - length (minhisttime)), caption; color= captioncolor)
545
534
print (io, lpad (maxhisttime, ceil (Int, (histwidth - length (caption)) / 2 ) - 1 ), " " )
546
535
print (io, " +" )
547
- # printstyled(io, "●", color=:light_black) # other options...
548
- # print(io, "⋯")
549
- # printstyled(io, "¹⁰⁰", color=:light_black)
536
+ # printstyled(io, "●", color=:light_black) # other options "⋯" "¹⁰⁰"
550
537
end
551
538
552
539
# I wondered about rounding to a few steps per decade. This looks good in 1:10, not great outside:
553
540
# round.(0:0.01:10, sigdigits=3, base=2) |> unique
554
- function low_edge (times)
555
- # _min = minimum(times)
556
- _min = min (minimum (times), mean (times) / 1.03 ) # demand low edge 3% below mean
541
+ function low_edge (times, lo= minimum (times), av= mean (times))
542
+ _min = min (lo, av / 1.03 ) # demand low edge 3% from mean, or further
557
543
return round (_min, RoundDown; sigdigits = 2 )
558
544
559
545
end
560
- function high_edge (times)
561
- # _max = maximum(times)
562
- # _max = Base.max(maximum(times), 1.07 * low_edge(times)) # demand total width at least 7%
563
- _max = max (maximum (times), 1.03 * mean (times)) # demand high edge 3% above mean
546
+ function high_edge (times, av= mean (times), hi= quantile (times, 0.99 ))
547
+ _max = max (1 , hi, 1.03 * av) # demand high edge 3% above mean, and at least 1ns
564
548
return round (_max, RoundUp; sigdigits = 2 )
565
549
end
566
550
@@ -592,3 +576,41 @@ function Base.show(io::IO, ::MIME"text/plain", t::TrialJudgement)
592
576
printmemoryjudge (io, t)
593
577
println (io, " (" , prettypercent (params (t). memory_tolerance), " tolerance)" )
594
578
end
579
+
580
+
581
+ #=
582
+ # Some visual checks, designed so that mean/median should hit a bar
583
+
584
+ using BenchmarkTools: Trial, Parameters
585
+ Trial(Parameters(), [pi * 10^9], [0], 0, 0) # one sample
586
+
587
+ # mean == median, one bar
588
+ Trial(Parameters(), [pi, pi], [0, 0], 0, 0)
589
+ Trial(Parameters(), fill(101, 33), vcat(zeros(32), 50), 0, 0)
590
+
591
+ # mean == median, three bars -- wrong highlighting before
592
+ Trial(Parameters(), [3,4,5], [0,0,0], 0, 0)
593
+
594
+ # three bars, including mean not median -- wrong highlighting before
595
+ Trial(Parameters(), pi * [1,3,4,4], [0,0,0,100], 1, 1)
596
+
597
+ # three bars, including median & both quartiles, not mean -- wrong before
598
+ Trial(Parameters(), 99.9 * [1,1,3,14,16], [0,0,99,0,0], 222, 2)
599
+
600
+ # same, but smaller range. Note also max GC is not max
601
+ Trial(Parameters(), 999 .+ [1,1,3,14,16], [0,0,123,0,0], 45e6, 7)
602
+
603
+
604
+ # Check that auto-sizing stops on very small widths:
605
+ io = IOContext(stdout, :displaysize => (25,30))
606
+ show(io, MIME("text/plain"), Trial(Parameters(), [3,4,5], [0,0,0], 0, 0))
607
+ show(io, MIME("text/plain"), Trial(Parameters(), repeat(100 * [3,4,5], 10^6), zeros(3*10^6), 0, 0))
608
+
609
+ io = IOContext(stdout, :displaysize => (25,50), :logbins => true) # this is wider
610
+ show(io, MIME("text/plain"), Trial(Parameters(), 100 * [3,4,5], [0,0,0], 0, 0))
611
+
612
+ # Check that data off the left is OK, and median still highlighted:
613
+ io = IOContext(stdout, :histmin => 200.123)
614
+ show(io, MIME("text/plain"), Trial(Parameters(), 99.9 * [1,1,3,14,16], [0,0,99,0,0], 222, 2))
615
+
616
+ =#
0 commit comments