require "./benchmark/**" # The Benchmark module provides methods for benchmarking Crystal code, giving # detailed reports on the time and memory taken for each task. # # NOTE: To use `Benchmark`, you must explicitly import it with `require "benchmark"` # # ### Measure the number of iterations per second of each task # # ``` # require "benchmark" # # Benchmark.ips do |x| # x.report("short sleep") { sleep 10.milliseconds } # x.report("shorter sleep") { sleep 1.millisecond } # end # ``` # # This generates the following output showing the mean iterations per second, # the mean times per iteration, the standard deviation relative to the mean, and a comparison: # # ```text # short sleep 88.7 ( 11.27ms) (± 3.33%) 8.90× slower # shorter sleep 789.7 ( 1.27ms) (± 3.02%) fastest # ``` # # `Benchmark::IPS` defaults to 2 seconds of warmup time and 5 seconds of # calculation time. This can be configured: # # ``` # require "benchmark" # # Benchmark.ips(warmup: 4, calculation: 10) do |x| # x.report("sleep") { sleep 10.milliseconds } # end # ``` # # ### Measure the time to construct the string given by the expression: `"a"*1_000_000_000` # # ``` # require "benchmark" # # puts Benchmark.measure { "a"*1_000_000_000 } # ``` # # This generates the following output: # # ```text # 0.190000 0.220000 0.410000 ( 0.420185) # ``` # # This report shows the user CPU time, system CPU time, the sum of # the user and system CPU times, and the elapsed real time. The unit # of time is seconds. # # ### Do some experiments sequentially using the `#bm` method: # # ``` # require "benchmark" # # n = 5000000 # Benchmark.bm do |x| # x.report("times:") do # n.times do # a = "1" # end # end # x.report("upto:") do # 1.upto(n) do # a = "1" # end # end # end # ``` # # The result: # # ```text # user system total real # times: 0.010000 0.000000 0.010000 ( 0.008976) # upto: 0.010000 0.000000 0.010000 ( 0.010466) # ``` # # NOTE: Make sure to always benchmark code by compiling with the `--release` flag. module Benchmark extend self # Main interface of the `Benchmark` module. Yields a `Job` to which # one can report the benchmarks. See the module's description. def bm(&) {% if !flag?(:release) %} puts "Warning: benchmarking without the `--release` flag won't yield useful results" {% end %} report = BM::Job.new yield report report.execute report end # Instruction per second interface of the `Benchmark` module. Yields a `Job` # to which one can report the benchmarks. See the module's description. # # The optional parameters *calculation* and *warmup* set the duration of # those stages. For more detail on these stages see # `Benchmark::IPS`. When the *interactive* parameter is `true`, results are # displayed and updated as they are calculated, otherwise all at once after they finished. def ips(calculation : Time::Span = 5.seconds, warmup : Time::Span = 2.seconds, interactive : Bool = STDOUT.tty?, &) {% if !flag?(:release) %} puts "Warning: benchmarking without the `--release` flag won't yield useful results" {% end %} job = IPS::Job.new(calculation, warmup, interactive) yield job job.execute job.report job end # Instruction per second interface of the `Benchmark` module. Yields a `Job` # to which one can report the benchmarks. See the module's description. # # The optional parameters *calculation* and *warmup* set the duration of # those stages in seconds. For more detail on these stages see # `Benchmark::IPS`. When the *interactive* parameter is `true`, results are # displayed and updated as they are calculated, otherwise all at once after they finished. @[Deprecated("Use `#ips(Time::Span, Time::Span, Bool, &)` instead.")] def ips(calculation = 5, warmup = 2, interactive = STDOUT.tty?, &) ips(calculation.seconds, warmup.seconds, !!interactive) { |job| yield job } end # Returns the time used to execute the given block. def measure(label = "", &) : BM::Tms t0, r0 = Process.times, Time.instant yield t1, r1 = Process.times, Time.instant BM::Tms.new(t1.utime - t0.utime, t1.stime - t0.stime, t1.cutime - t0.cutime, t1.cstime - t0.cstime, (r1 - r0).total_seconds, label) end # Returns the elapsed real time used to execute the given block. # # ``` # Benchmark.realtime { "a" * 100_000 } # => 00:00:00.0005840 # ``` def realtime(&) : Time::Span Time.measure { yield } end # Returns the memory in bytes that the given block consumes. # # ``` # Benchmark.memory { Array(Int32).new } # => 32 # ``` def memory(&) bytes_before_measure = GC.stats.total_bytes yield (GC.stats.total_bytes - bytes_before_measure).to_i64 end end