123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188openCoreletstabilize_gc()=letrecloopfailsafelast_heap_live_words=iffailsafe<=0thenfailwith"unable to stabilize the number of live words in the major heap";Gc.compact();letstat=Gc.stat()inifstat.Gc.Stat.live_words<>last_heap_live_wordsthenloop(failsafe-1)stat.Gc.Stat.live_wordsinloop100(* The main benchmarking function *)letmeasure=letmoduleRC=Run_configinletmoduleM=Measurement_sampleinfunrun_configtest->(* test function *)letTest.Basic_test.Tf=test.Test.Basic_test.finletf=f`initin(* the samples *)letmax_samples=3_000inletresults=Array.initmax_samples~f:(fun_->M.create())in(* counters *)letindex=ref0inletruns=ref0inlettotal_runs=ref0in(* get the old Gc settings *)letold_gc=Gc.get()in(* THE MAIN TEST LOOP *)letinit_t1=Time.now()inletquota=RC.quotarun_configinletquota_max_count=Quota.max_countquotainwhilenot(Quota.fulfilledquota~start:init_t1~num_calls:!total_runs)&&!index<Array.lengthresultsdoletcurrent_runs=!runsinletcurrent_index=!indexin(* Stabilize gc if required.
We stabilize the gc through the first pass through this loop anyway. If we don't do
this the incoming GC state (some data may be on the minor heap that is partly full)
will cause an early collection or two which will not happen subsequently. These
early collections are just noise.
While benchmarking functions that do not allocate any memory this early noise is
the only significant input. In these cases, these spurious early collections will
give the allocation stats (major and promoted words) a slight negative value. *)if(RC.stabilize_gc_between_runsrun_config)||current_runs=0thenstabilize_gc();(* make any Gc changes required. *)if(RC.no_compactionsrun_config)thenGc.set{(Gc.get())withGc.Control.max_overhead=1_000_000};(* pre-run measurements *)letgc1=Gc.quick_stat()inlett1=Time.now()inletc1=Time_stamp_counter.now()in(* MEASURE A SINGLE SAMPLE *)for_=1tocurrent_runsdoignore(f());done;(* END OF MEASUREMENT *)(* post-run measurements *)letc2=Time_stamp_counter.now()inlett2=Time.now()inletgc2=Gc.quick_stat()intotal_runs:=!total_runs+current_runs;(* reset the old Gc now that we are done with measurements *)Gc.setold_gc;(* save measurements *)lets=results.(current_index)ins.M.runs<-current_runs;s.M.cycles<-Time_stamp_counter.Span.to_int_exn(Time_stamp_counter.diffc2c1);s.M.nanos<-(Float.iround_towards_zero_exn(Time.Span.to_ns(Time.difft2t1)));s.M.minor_allocated<-Float.iround_towards_zero_exn(gc2.Gc.Stat.minor_words-.gc1.Gc.Stat.minor_words);s.M.major_allocated<-Float.iround_towards_zero_exn(gc2.Gc.Stat.major_words-.gc1.Gc.Stat.major_words);s.M.promoted<-Float.iround_towards_zero_exn(gc2.Gc.Stat.promoted_words-.gc1.Gc.Stat.promoted_words);s.M.compactions<-(gc2.Gc.Stat.compactions-gc1.Gc.Stat.compactions);s.M.major_collections<-(gc2.Gc.Stat.major_collections-gc1.Gc.Stat.major_collections);s.M.minor_collections<-(gc2.Gc.Stat.minor_collections-gc1.Gc.Stat.minor_collections);incrindex;(* determine the next number of runs *)letnext=match(RC.sampling_typerun_config)with|`Lineark->current_runs+k|`Geometricscale->letnext_geometric=Float.iround_towards_zero_exn((Float.of_intcurrent_runs)*.scale)inInt.maxnext_geometric(current_runs+1)in(* if [next] would put us over the quota, we decrease as necessary *)letnext=Int.minnext(quota_max_count-!total_runs)inassert(next>=0);(* otherwise the loop guard is broken *)runs:=next;done;letend_time=Time.now()in(* END OF MAIN TEST LOOP *)lettotal_samples=!indexinletlargest_run=!runsinletmeasurement=Measurement.create~name:(Test.Basic_test.nametest)~test_name:(Test.Basic_test.test_nametest)~file_name:(Test.Basic_test.file_nametest)~module_name:(Test.Basic_test.module_nametest)~largest_run~sample_count:total_samples~samples:resultsinVerbosity.print_high"%s: Total time taken %s (%d samples, max runs %d).\n%!"(Test.Basic_test.nametest)(Time.Span.to_string(Time.diffend_timeinit_t1))total_sampleslargest_run;(* if (RC.save_sample_data run_config)
* then M.save test ~results total_samples; *)measurement(* Run multiple benchmarks and aggregate the results. If forking is enabled then this
function will fork and run each benchmark in a new child process. *)letmeasure_allrun_configtests=Random.self_init();letmoduleRC=Run_configinVerbosity.set_verbosity(RC.verbosityrun_config);beginmatchRC.quotarun_configwith|Num_callstrials->Verbosity.print_low"Estimated testing time unknown (%d benchmarks x %d trials). \
Change using '-quota'.\n%!"(List.lengthtests)trials|Spanspan->letest_time=Time.Span.scalespan(Float.of_int(List.lengthtests))inVerbosity.print_low"Estimated testing time %s (%d benchmarks x %s). Change using '-quota'.\n%!"(Time.Span.to_stringest_time)(List.lengthtests)(Time.Span.to_stringspan);end;if(RC.fork_each_benchmarkrun_config)thenletfds=List.maptests~f:(fun_->Unix.pipe())inlet()=Caml.List.iter2(funtest(_fdr,fdw)->matchCaml.Unix.fork()with|0->letx=measurerun_configtestinletopenCamlinletoc=Unix.out_channel_of_descrfdwinMarshal.to_channelocx[];exit0|pid->ignore(Caml.Unix.waitpid[]pid))testsfdsinList.mapfds~f:(fun(fdr,_fdw)->letopenCamlinletic=Unix.in_channel_of_descrfdrinMarshal.from_channelic)elseList.maptests~f:(measurerun_config)