Skip to content

Commit

Permalink
Handle calculation of statistics when given 0s
Browse files Browse the repository at this point in the history
We hadn't handled the calculation of statistics when given a whole
bunch of 0s, which is a complication since we're doing a lot of
division in there. Technically if we're in a situation where we're
supposed to divide by 0 the answer is Infinity, but instead of
introducing that, for now I'm just returning 0 as well.

Addresses the first reported bug in #213.
  • Loading branch information
devonestes committed Apr 30, 2018
1 parent 4fb3e4f commit 781e05b
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 24 deletions.
53 changes: 29 additions & 24 deletions lib/benchee/statistics.ex
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,10 @@ defmodule Benchee.Statistics do
},
memory_usage_statistics: %Benchee.Statistics{
average: 500.0,
ips: 2000.0,
ips: 0.0,
std_dev: 200.0,
std_dev_ratio: 0.4,
std_dev_ips: 800.0,
std_dev_ips: 0.0,
median: 500.0,
percentiles: %{50 => 500.0, 99 => 900.0},
mode: [500, 400],
Expand All @@ -153,8 +153,8 @@ defmodule Benchee.Statistics do
def statistics(suite = %Suite{scenarios: scenarios}) do
scenarios_with_statistics =
Parallel.map(scenarios, fn scenario ->
run_time_stats = job_statistics(scenario.run_times)
memory_stats = job_statistics(scenario.memory_usages)
run_time_stats = job_statistics(scenario.run_times, :run_time)
memory_stats = job_statistics(scenario.memory_usages, :memory)

%Scenario{
scenario
Expand All @@ -173,7 +173,7 @@ defmodule Benchee.Statistics do
## Examples
iex> run_times = [200, 400, 400, 400, 500, 500, 500, 700, 900]
iex> Benchee.Statistics.job_statistics(run_times)
iex> Benchee.Statistics.job_statistics(run_times, :run_time)
%Benchee.Statistics{
average: 500.0,
ips: 2000.0,
Expand All @@ -188,7 +188,7 @@ defmodule Benchee.Statistics do
sample_size: 9
}
iex> Benchee.Statistics.job_statistics([100])
iex> Benchee.Statistics.job_statistics([100], :run_time)
%Benchee.Statistics{
average: 100.0,
ips: 10_000.0,
Expand All @@ -203,7 +203,7 @@ defmodule Benchee.Statistics do
sample_size: 1
}
iex> Benchee.Statistics.job_statistics([])
iex> Benchee.Statistics.job_statistics([], :run_time)
%Benchee.Statistics{
average: nil,
ips: nil,
Expand All @@ -219,24 +219,24 @@ defmodule Benchee.Statistics do
}
"""
@spec job_statistics(samples) :: __MODULE__.t()
def job_statistics([]) do
@spec job_statistics(samples, atom) :: __MODULE__.t()
def job_statistics([], _) do
%__MODULE__{sample_size: 0}
end

def job_statistics(run_times) do
total_time = Enum.sum(run_times)
iterations = Enum.count(run_times)
average = total_time / iterations
ips = iterations_per_second(average)
deviation = standard_deviation(run_times, average, iterations)
standard_dev_ratio = deviation / average
def job_statistics(measurements, run_time_or_memory) do
total = Enum.sum(measurements)
num_iterations = length(measurements)
average = total / num_iterations
ips = iterations_per_second(average, run_time_or_memory)
deviation = standard_deviation(measurements, average, num_iterations)
standard_dev_ratio = if average == 0, do: 0, else: deviation / average
standard_dev_ips = ips * standard_dev_ratio
percentiles = Percentile.percentiles(run_times, [50, 99])
percentiles = Percentile.percentiles(measurements, [50, 99])
median = Map.fetch!(percentiles, 50)
mode = Mode.mode(run_times)
minimum = Enum.min(run_times)
maximum = Enum.max(run_times)
mode = Mode.mode(measurements)
minimum = Enum.min(measurements)
maximum = Enum.max(measurements)

%__MODULE__{
average: average,
Expand All @@ -249,7 +249,7 @@ defmodule Benchee.Statistics do
mode: mode,
minimum: minimum,
maximum: maximum,
sample_size: iterations
sample_size: num_iterations
}
end

Expand Down Expand Up @@ -295,10 +295,10 @@ defmodule Benchee.Statistics do
},
memory_usage_statistics: %Benchee.Statistics{
average: 500.0,
ips: 2000.0,
ips: 0.0,
std_dev: 200.0,
std_dev_ratio: 0.4,
std_dev_ips: 800.0,
std_dev_ips: 0.0,
median: 500.0,
percentiles: %{50 => 500.0, 99 => 900.0},
mode: [500, 400],
Expand All @@ -322,11 +322,16 @@ defmodule Benchee.Statistics do
%Suite{suite | scenarios: new_scenarios}
end

defp iterations_per_second(average_microseconds) do
defp iterations_per_second(0.0, _), do: 0.0

defp iterations_per_second(_, :memory), do: 0.0

defp iterations_per_second(average_microseconds, :run_time) do
Duration.microseconds({1, :second}) / average_microseconds
end

defp standard_deviation(_samples, _average, 1), do: 0

defp standard_deviation(samples, average, sample_size) do
total_variance =
Enum.reduce(samples, 0, fn sample, total ->
Expand Down
13 changes: 13 additions & 0 deletions test/benchee/statistics_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ defmodule Benchee.StatistcsTest do
memory_usages: @standard_deviation_sample
}
]

suite = Statistics.statistics(%Suite{scenarios: scenarios})

[%Scenario{run_time_statistics: stats}] = suite.scenarios
Expand All @@ -95,6 +96,18 @@ defmodule Benchee.StatistcsTest do
assert %Suite{configuration: %{formatters: []}} = Statistics.statistics(suite)
end

@all_zeros [0, 0, 0, 0, 0]
test "doesn't blow up when all measurements are zeros (mostly memory measurement)" do
scenarios = [%Scenario{run_times: @all_zeros, memory_usages: @all_zeros}]
suite = Statistics.statistics(%Suite{scenarios: scenarios})

[%Scenario{run_time_statistics: run_time_stats, memory_usage_statistics: memory_stats}] =
suite.scenarios

assert run_time_stats.sample_size == 5
assert memory_stats.sample_size == 5
end

defp stats_for(suite, job_name, input_name) do
%Scenario{run_time_statistics: stats} =
Enum.find(suite.scenarios, fn scenario ->
Expand Down

0 comments on commit 781e05b

Please sign in to comment.