|
| 1 | +defmodule Benchee.FunctionOverheadAndOutliersTest do |
| 2 | + use ExUnit.Case, async: false |
| 3 | + |
| 4 | + import ExUnit.CaptureIO |
| 5 | + import Benchee.IntegrationHelpers |
| 6 | + import Benchee.TestHelpers |
| 7 | + @test_configuration Benchee.IntegrationHelpers.test_configuration() |
| 8 | + |
| 9 | + describe "function call overhead measurement" do |
| 10 | + @overhead_output_regex ~r/function call overhead.*\d+/i |
| 11 | + test "by default it is off" do |
| 12 | + output = |
| 13 | + capture_io(fn -> |
| 14 | + Benchee.run( |
| 15 | + %{"sleeps" => fn -> sleep_safe_time() end}, |
| 16 | + @test_configuration |
| 17 | + ) |
| 18 | + end) |
| 19 | + |
| 20 | + refute output =~ @overhead_output_regex |
| 21 | + end |
| 22 | + |
| 23 | + test "can be turned on" do |
| 24 | + output = |
| 25 | + capture_io(fn -> |
| 26 | + Benchee.run( |
| 27 | + %{"sleeps" => fn -> sleep_safe_time() end}, |
| 28 | + Keyword.merge(@test_configuration, measure_function_call_overhead: true) |
| 29 | + ) |
| 30 | + end) |
| 31 | + |
| 32 | + assert output =~ @overhead_output_regex |
| 33 | + end |
| 34 | + end |
| 35 | + |
| 36 | + describe "exclude_outliers" do |
| 37 | + test "even with it the high level README example still passes its asserts" do |
| 38 | + output = |
| 39 | + capture_io(fn -> |
| 40 | + list = Enum.to_list(1..10_000) |
| 41 | + map_fun = fn i -> [i, i * i] end |
| 42 | + |
| 43 | + Benchee.run( |
| 44 | + %{ |
| 45 | + "flat_map" => fn -> Enum.flat_map(list, map_fun) end, |
| 46 | + "map.flatten" => fn -> list |> Enum.map(map_fun) |> List.flatten() end |
| 47 | + }, |
| 48 | + Keyword.merge(@test_configuration, exclude_outliers: true) |
| 49 | + ) |
| 50 | + end) |
| 51 | + |
| 52 | + readme_sample_asserts(output) |
| 53 | + end |
| 54 | + |
| 55 | + # The easiest way to create an outlier is to just run something once |
| 56 | + # and then take a "stable" measurement like reductions or memory |
| 57 | + test "removes outliers" do |
| 58 | + {:ok, agent} = Agent.start(fn -> 0 end) |
| 59 | + |
| 60 | + output = |
| 61 | + capture_io(fn -> |
| 62 | + suite = |
| 63 | + Benchee.run( |
| 64 | + %{ |
| 65 | + "flawed" => fn -> |
| 66 | + # Produce some garbage but only once |
| 67 | + # can't use process dictionary as it's a different process every time |
| 68 | + if Agent.get(agent, & &1) < 1 do |
| 69 | + Enum.reduce(1..10_000, 0, &+/2) |
| 70 | + Agent.update(agent, &(&1 + 1)) |
| 71 | + end |
| 72 | + end |
| 73 | + }, |
| 74 | + time: 0, |
| 75 | + warmup: 0, |
| 76 | + reduction_time: 0.05, |
| 77 | + exclude_outliers: true |
| 78 | + ) |
| 79 | + |
| 80 | + %{scenarios: [%{reductions_data: %{samples: samples, statistics: stats}}]} = suite |
| 81 | + |
| 82 | + assert [outlier] = stats.outliers |
| 83 | + assert outlier >= stats.upper_outlier_bound |
| 84 | + # since the outlier is removed, all values are the same |
| 85 | + assert stats.std_dev == 0 |
| 86 | + assert stats.minimum == stats.maximum |
| 87 | + |
| 88 | + # It's a big outlier! |
| 89 | + assert 10 * stats.average < outlier |
| 90 | + |
| 91 | + # The outlier is only removed from the stats, but not from the samples |
| 92 | + assert outlier in samples |
| 93 | + end) |
| 94 | + |
| 95 | + # As the outlier is removed, all measurements are the same |
| 96 | + assert output =~ ~r/all.*reduction.*same/i |
| 97 | + assert output =~ ~r/exclud.*outlier.*true/i |
| 98 | + end |
| 99 | + end |
| 100 | +end |
0 commit comments