|
| 1 | +%% -*- erlang -*- |
| 2 | +%% |
| 3 | +%% Sieve of Erastothenes in Erlang, implemented as a benchmark for |
| 4 | +%% https://github.com/PlummersSoftwareLLC/Primes. |
| 5 | +%% |
| 6 | +%% @author Jesper Eskilson <jesper@eskilson.se> |
| 7 | +%% |
| 8 | +-module('PrimeErlang'). |
| 9 | + |
| 10 | +-export([main/1, run_benchmark/1]). |
| 11 | + |
| 12 | +-include_lib("eunit/include/eunit.hrl"). |
| 13 | + |
| 14 | +%% ============================================================ |
| 15 | +%% Types |
| 16 | +%% ============================================================ |
| 17 | + |
| 18 | +-record(sieve, {q :: float(), size :: integer(), bits :: atomics:atomics_ref()}). |
| 19 | + |
| 20 | +-type sieve() :: #sieve{}. |
| 21 | +-type prime_idx() :: pos_integer(). |
| 22 | + |
| 23 | +%% ============================================================ |
| 24 | +%% Exported API |
| 25 | +%% ============================================================ |
| 26 | + |
| 27 | +%% @doc The escript entry point |
| 28 | +main([]) -> |
| 29 | + main(["1000000"]); |
| 30 | +main([Arg]) -> |
| 31 | + N = list_to_integer(Arg), |
| 32 | + run_benchmark(N). |
| 33 | + |
| 34 | +%% @doc Run the benchmark for a sieve of the given size. |
| 35 | +run_benchmark(N) -> |
| 36 | + ExpectedNumPrimes = num_primes(N), |
| 37 | + LimitSecs = 5, |
| 38 | + |
| 39 | + BenchFun = |
| 40 | + fun() -> |
| 41 | + NumPrimes = run_sieve(N), |
| 42 | + ?assertEqual(NumPrimes, ExpectedNumPrimes) |
| 43 | + end, |
| 44 | + |
| 45 | + {TimeUsecs, Iterations} = timer:tc(fun() -> repeat_until(BenchFun, LimitSecs) end), |
| 46 | + |
| 47 | + TotalTime = TimeUsecs / 1_000_000.0, |
| 48 | + NumThreads = 1, |
| 49 | + Label = "jesperes", |
| 50 | + Tags = |
| 51 | + #{algorithm => base, |
| 52 | + faithful => yes, |
| 53 | + bits => 64}, |
| 54 | + |
| 55 | + print_result(Label, Iterations, TotalTime, NumThreads, Tags). |
| 56 | + |
| 57 | +%% ============================================================ |
| 58 | +%% Implementation |
| 59 | +%% ============================================================ |
| 60 | + |
| 61 | +%% @doc Run the sieve for the given size once, and return the number |
| 62 | +%% of primes found. |
| 63 | +-spec run_sieve(Size :: integer()) -> NumPrimes :: integer(). |
| 64 | +run_sieve(Size) -> |
| 65 | + Sieve = sieve_new(Size), |
| 66 | + Sieve0 = run_sieve_loop(2, Sieve), |
| 67 | + sieve_count_primes(Sieve0). |
| 68 | + |
| 69 | +-spec run_sieve_loop(I :: prime_idx(), Sieve :: sieve()) -> sieve(). |
| 70 | +run_sieve_loop(I, #sieve{q = Q} = Sieve) when I > Q -> |
| 71 | + Sieve; |
| 72 | +run_sieve_loop(I, Sieve) -> |
| 73 | + Sieve0 = |
| 74 | + case sieve_is_prime(I, Sieve) of |
| 75 | + true -> |
| 76 | + run_sieve_inner_loop(I * I, I, Sieve); |
| 77 | + false -> |
| 78 | + Sieve |
| 79 | + end, |
| 80 | + run_sieve_loop(I + 1, Sieve0). |
| 81 | + |
| 82 | +-spec run_sieve_inner_loop(J :: prime_idx(), Incr :: integer(), Sieve :: sieve()) -> |
| 83 | + sieve(). |
| 84 | +run_sieve_inner_loop(J, _, #sieve{size = Size} = Sieve) when J > Size -> |
| 85 | + Sieve; |
| 86 | +run_sieve_inner_loop(J, Incr, Sieve) -> |
| 87 | + Sieve0 = sieve_mark_not_prime(J, Sieve), |
| 88 | + run_sieve_inner_loop(J + Incr, Incr, Sieve0). |
| 89 | + |
| 90 | +%% ============================================================ |
| 91 | +%% Sieve |
| 92 | +%% ============================================================ |
| 93 | + |
| 94 | +-spec sieve_new(Size :: integer()) -> sieve(). |
| 95 | +sieve_new(Size) -> |
| 96 | + #sieve{q = math:sqrt(Size), |
| 97 | + size = Size, |
| 98 | + bits = atomics:new(Size, [])}. |
| 99 | + |
| 100 | +-spec sieve_is_prime(N :: prime_idx(), Sieve :: sieve()) -> boolean(). |
| 101 | +sieve_is_prime(N, Sieve) -> |
| 102 | + atomics:get(Sieve#sieve.bits, N) == 0. |
| 103 | + |
| 104 | +-spec sieve_mark_not_prime(N :: prime_idx(), Sieve :: sieve()) -> sieve(). |
| 105 | +sieve_mark_not_prime(N, Sieve) -> |
| 106 | + ok = atomics:put(Sieve#sieve.bits, N, 1), |
| 107 | + Sieve. |
| 108 | + |
| 109 | +-spec sieve_count_primes(Sieve :: sieve()) -> integer(). |
| 110 | +sieve_count_primes(Sieve) -> |
| 111 | + sieve_count_primes(2, 0, Sieve). |
| 112 | + |
| 113 | +sieve_count_primes(I, Acc, #sieve{size = Size}) when I > Size -> |
| 114 | + Acc; |
| 115 | +sieve_count_primes(I, Acc, Sieve) -> |
| 116 | + case atomics:get(Sieve#sieve.bits, I) of |
| 117 | + 0 -> |
| 118 | + sieve_count_primes(I + 1, 1 + Acc, Sieve); |
| 119 | + 1 -> |
| 120 | + sieve_count_primes(I + 1, Acc, Sieve) |
| 121 | + end. |
| 122 | + |
| 123 | +%% ============================================================ |
| 124 | +%% Helpers |
| 125 | +%% ============================================================ |
| 126 | + |
| 127 | +%% @doc Used to verify the result |
| 128 | +num_primes(10) -> |
| 129 | + 4; |
| 130 | +num_primes(100) -> |
| 131 | + 25; |
| 132 | +num_primes(1_000) -> |
| 133 | + 168; |
| 134 | +num_primes(10_000) -> |
| 135 | + 1229; |
| 136 | +num_primes(100_000) -> |
| 137 | + 9592; |
| 138 | +num_primes(1_000_000) -> |
| 139 | + 78498; |
| 140 | +num_primes(10_000_000) -> |
| 141 | + 664579; |
| 142 | +num_primes(100_000_000) -> |
| 143 | + 5761455; |
| 144 | +num_primes(1_000_000_000) -> |
| 145 | + 50847534; |
| 146 | +num_primes(10_000_000_000) -> |
| 147 | + 455052511. |
| 148 | + |
| 149 | +%% @doc Print the result |
| 150 | +print_result(Label, Iterations, TotalTime, NumThreads, Tags) -> |
| 151 | + io:format("~s;~w;~g;~w;~s~n", |
| 152 | + [Label, Iterations, TotalTime, NumThreads, tags_to_str(Tags)]). |
| 153 | + |
| 154 | +%% @doc Convert tags on map-format to a comma-separated k=v string |
| 155 | +tags_to_str(Tags) -> |
| 156 | + lists:join(",", |
| 157 | + lists:map(fun({K, V}) -> io_lib:format("~w=~w", [K, V]) end, maps:to_list(Tags))). |
| 158 | + |
| 159 | +%% @doc Run a function repeatedly for a given number of |
| 160 | +%% seconds. Return the number of times the function was run. |
| 161 | +-spec repeat_until(Fun :: fun(), LimitSecs :: pos_integer()) -> |
| 162 | + Iterations :: pos_integer(). |
| 163 | +repeat_until(Fun, LimitSecs) -> |
| 164 | + Start = os:system_time(), |
| 165 | + Limit = erlang:convert_time_unit(LimitSecs, second, native), |
| 166 | + repeat_until(Fun, Start, Limit, 0). |
| 167 | + |
| 168 | +repeat_until(Fun, Start, Limit, NIters) -> |
| 169 | + Elapsed = os:system_time() - Start, |
| 170 | + if Elapsed >= Limit -> |
| 171 | + NIters; |
| 172 | + true -> |
| 173 | + Fun(), |
| 174 | + repeat_until(Fun, Start, Limit, NIters + 1) |
| 175 | + end. |
0 commit comments