Skip to content

Commit f852e42

Browse files
authoredApr 12, 2023
Erlang solution (PlummersSoftwareLLC#909)
1 parent 07d55a4 commit f852e42

File tree

8 files changed

+251
-0
lines changed

8 files changed

+251
-0
lines changed
 

‎PrimeErlang/solution_1/.gitignore

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
.rebar3
2+
_*
3+
.eunit
4+
*.o
5+
*.beam
6+
*.plt
7+
*.swp
8+
*.swo
9+
.erlang.cookie
10+
ebin
11+
log
12+
erl_crash.dump
13+
.rebar
14+
logs
15+
_build
16+
.idea
17+
*.iml
18+
rebar3.crashdump
19+
*~

‎PrimeErlang/solution_1/Dockerfile

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM erlang:26
2+
3+
WORKDIR /home/primes
4+
5+
COPY rebar.config run.sh ./
6+
COPY src/*.erl src/*.src ./src/
7+
ENTRYPOINT ["./run.sh"]

‎PrimeErlang/solution_1/README.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Erlang solution by jesperes
2+
3+
Straight-forward single-threaded Erlang solution using `atomics` to
4+
track the prime numbers.
5+
6+
![Algorithm](https://img.shields.io/badge/Algorithm-base-green)
7+
![Faithfulness](https://img.shields.io/badge/Faithful-yes-green)
8+
![Parallelism](https://img.shields.io/badge/Parallel-no-green)
9+
![Bit count](https://img.shields.io/badge/Bits-64-yellowgreen)
10+
11+
## Run instructions
12+
13+
```shell
14+
$ rebar3 escriptize && _build/default/bin/PrimeErlang
15+
```
16+
17+
## Output
18+
19+
```shell
20+
> rebar3 escriptize && _build/default/bin/PrimeErlang
21+
===> Verifying dependencies...
22+
===> Analyzing applications...
23+
===> Compiling PrimeErlang
24+
===> Building escript for PrimeErlang...
25+
jesperes;46;5.02126;1;algorithm=base,bits=64,faithful=yes
26+
```

‎PrimeErlang/solution_1/rebar.config

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{deps, []}.
2+
3+
{escript_incl_apps, ['PrimeErlang']}.
4+
5+
{escript_main_app, 'PrimeErlang'}.
6+
7+
{escript_name, 'PrimeErlang'}.
8+
9+
%% Profiles
10+
{profiles, [{test, [{erl_opts, [debug_info]}]}]}.

‎PrimeErlang/solution_1/rebar.lock

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[].

‎PrimeErlang/solution_1/run.sh

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
QUIET=t rebar3 escriptize >/dev/null
3+
exec _build/default/bin/PrimeErlang
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{application,
2+
'PrimeErlang',
3+
[{description, "Sieve of Erasthotenes"},
4+
{vsn, "0.1.0"},
5+
{registered, []},
6+
{applications, [kernel, stdlib]},
7+
{env, []},
8+
{modules, []},
9+
{licenses, ["Apache-2.0"]},
10+
{links, []}]}.
+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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

Comments
 (0)
Please sign in to comment.