moduleBenchmarkmoduleIPS# Benchmark jobs.classJob# Microseconds per 100 millisecond.MICROSECONDS_PER_100MS=100_000# Microseconds per second.MICROSECONDS_PER_SECOND=1_000_000# The percentage of the expected runtime to allow# before reporting a weird runtimeMAX_TIME_SKEW=0.05# Entries in Benchmark Jobs.classEntry# Instantiate the Benchmark::IPS::Job::Entry.# @param label [String] Label of Benchmarked code.# @param action [String, Proc] Code to be benchmarked.# @raise [ArgumentError] Raises when action is not String or not responding to +call+.definitialize(label,action)@label=labelifaction.kind_of?Stringcompileaction@action=self@as_action=trueelseunlessaction.respond_to?:callraiseArgumentError,"invalid action, must respond to #call"end@action=actionifaction.respond_to?:arityandaction.arity>0@call_loop=trueelse@call_loop=falseend@as_action=falseendend# The label of benchmarking action.# @return [String] Label of action.attr_reader:label# The benchmarking action.# @return [String, Proc] Code to be called, could be String / Proc.attr_reader:action# Add padding to label's right if label's length < 20,# Otherwise add a new line and 20 whitespaces.# @return [String] Right justified label.deflabel_rjustif@label.size>20"#{@label}\n#{' '*20}"else@label.rjust(20)endend# Call action by given times, return if +@call_loop+ is present.# @param times [Integer] Times to call +@action+.# @return [Integer] Number of times the +@action+ has been called.defcall_times(times)return@action.call(times)if@call_loopact=@actioni=0whilei<timesact.calli+=1endend# Compile code into +call_times+ method.# @param str [String] Code to be compiled.# @return [Symbol] :call_times.defcompile(str)m=(class<<self;self;end)code=<<-CODE
def call_times(__total);
__i = 0
while __i < __total
#{str};
__i += 1
end
end
CODEm.class_evalcodeendend# End of Entry# class Job# Two-element arrays, consisting of label and block pairs.# @return [Array<Entry>] list of entriesattr_reader:list# Determining whether to run comparison utility.# @return [Boolean] true if needs to run compare.attr_reader:compare# Report object containing information about the run.# @return [Report] the report object.attr_reader:full_report# Storing Iterations in time period.# @return [Hash]attr_reader:timing# Warmup time setter and getter (in seconds).# @return [Integer]attr_accessor:warmup# Calculation time setter and getter (in seconds).# @return [Integer]attr_accessor:time# Instantiate the Benchmark::IPS::Job.# @option opts [Benchmark::Suite] (nil) :suite Specify Benchmark::Suite.# @option opts [Boolean] (false) :quiet Suppress the printing of information.definitializeopts={}@suite=opts[:suite]||nil@quiet=opts[:quiet]||false@list=[]@compare=false@timing={}@full_report=Report.new# Default warmup and calculation time in seconds.@warmup=2@time=5end# Job configuration options, set +@warmup+ and +@time+.# @option opts [Integer] :warmup Warmup time.# @option opts [Integer] :time Calculation time.defconfigopts@warmup=opts[:warmup]ifopts[:warmup]@time=opts[:time]ifopts[:time]end# Return true if job needs to be compared.# @return [Boolean] Need to compare?defcompare?@compareend# Set @compare to true.defcompare!@compare=trueend# Registers the given label and block pair in the job list.# @param label [String] Label of benchmarked code.# @param str [String] Code to be benchamrked.# @param blk [Proc] Code to be benchamrked.# @raise [ArgumentError] Raises if str and blk are both present.# @raise [ArgumentError] Raises if str and blk are both absent.defitem(label="",str=nil,&blk)# :yield:ifblkandstrraiseArgumentError,"specify a block and a str, but not both"endaction=str||blkraiseArgumentError,"no block or string"unlessaction@list.pushEntry.new(label,action)selfendalias_method:report,:item# Calculate the cycles needed to run for approx 100ms,# given the number of iterations to run the given time.# @param [Float] time_msec Each iteration's time in ms.# @param [Integer] iters Iterations.# @return [Integer] Cycles per 100ms.defcycles_per_100mstime_msec,iterscycles=((MICROSECONDS_PER_100MS/time_msec)*iters).to_icycles=1ifcycles<=0cyclesend# Calculate the time difference of before and after in microseconds.# @param [Time] before time.# @param [Time] after time.# @return [Float] Time difference of before and after.deftime_usbefore,after(after.to_f-before.to_f)*MICROSECONDS_PER_SECONDend# Calculate the interations per second given the number# of cycles run and the time in microseconds that elapsed.# @param [Integer] cycles Cycles.# @param [Integer] time_us Time in microsecond.# @return [Float] Iteration per second.defiterations_per_seccycles,time_usMICROSECONDS_PER_SECOND*(cycles.to_f/time_us.to_f)end# Run warmup.defrun_warmup@list.eachdo|item|@suite.warmingitem.label,@warmupif@suiteunless@quiet$stdout.printfitem.label_rjustendTiming.clean_envbefore=Time.nowtarget=Time.now+@warmupwarmup_iter=0whileTime.now<targetitem.call_times(1)warmup_iter+=1endafter=Time.nowwarmup_time_us=time_usbefore,after@timing[item]=cycles_per_100mswarmup_time_us,warmup_itercaseBenchmark::IPS.options[:format]when:human$stdout.printf"%s i/100ms\n",Helpers.scale(@timing[item])unless@quietelse$stdout.printf"%10d i/100ms\n",@timing[item]unless@quietend@suite.warmup_statswarmup_time_us,@timing[item]if@suiteendend# Run calculation.defrun@list.eachdo|item|@suite.runningitem.label,@timeif@suiteunless@quiet$stdout.printitem.label_rjustendTiming.clean_enviter=0target=Time.now+@timemeasurements_us=[]# Running this number of cycles should take around 100ms.cycles=@timing[item]whileTime.now<targetbefore=Time.nowitem.call_timescyclesafter=Time.now# If for some reason the timing said this took no time (O_o)# then ignore the iteration entirely and start another.iter_us=time_usbefore,afternextifiter_us<=0.0iter+=cyclesmeasurements_us<<iter_usendfinal_time=Time.nowmeasured_us=measurements_us.inject(0){|a,i|a+i}all_ips=measurements_us.map{|time_us|iterations_per_seccycles,time_us}avg_ips=Timing.mean(all_ips)sd_ips=Timing.stddev(all_ips).roundrep=create_report(item,measured_us,iter,avg_ips,sd_ips,cycles)if(final_time-target).abs>=(@time.to_f*MAX_TIME_SKEW)rep.show_total_time!end$stdout.puts" #{rep.body}"unless@quiet@suite.add_reportrep,caller(1).firstif@suiteendend# Run comparison of entries in +@full_report+.defrun_comparison@full_report.run_comparisonend# Create report by add entry to +@full_report+.# @param item [Benchmark::IPS::Job::Entry] Report item.# @param measured_us [Integer] Measured time in microsecond.# @param iter [Integer] Iterations.# @param avg_ips [Float] Average iterations per second.# @param sd_ips [Float] Standard deviation iterations per second.# @param cycles [Integer] Number of Cycles.defcreate_report(item,measured_us,iter,avg_ips,sd_ips,cycles)@full_report.add_entryitem.label,measured_us,iter,avg_ips,sd_ips,cyclesendendendend