Skip to content

Commit e5ee87b

Browse files
authored
Merge pull request #376 from ocaml-multicore/thread_joingraph_unification
Thread joingraph unification (chore)
2 parents de0d268 + 9252190 commit e5ee87b

File tree

1 file changed

+94
-68
lines changed

1 file changed

+94
-68
lines changed

src/thread/thread_joingraph.ml

Lines changed: 94 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,137 @@
11
(**
22
Generate direct tests of the Thread module's create/join primitives.
3-
Like [src/domainslib/tast_one_dep.ml]([src/domainslib/tast_one_dep.ml) it does so by generating
4-
a random, acyclic dependency graph of [create]d [Thread.t]s each waiting
5-
to [join] with its dependency.
3+
It does so by generating a random, acyclic dependency graph of
4+
[create]d [Thread.t]s each waiting to [join] with its dependency.
65
*)
76

87
open QCheck
98

109
(* Generates a sparse DAG of join dependencies *)
11-
(* Each thread is represented by an array index w/at most 1 dep. each *)
10+
(* Each thread is represented by record with an optional array index
11+
to model at most 1 dependency each *)
1212
(* This example DAG
1313
1414
A/0 <--- B/1
1515
^.
1616
\
1717
`- C/2 <--- D/3
1818
19-
is represented as: [| None; Some 0; Some 0; Some 2 |]
19+
is represented as:
20+
[| {dep=None ...}; {dep=Some 0 ...}; {dep=Some 0 ...}; {dep=Some 2 ...} |]
2021
2122
Since each thread can only be joined once, A/0 is joined by B/1 (not C/2)
2223
*)
23-
let gen_deps n st =
24-
let a = Array.make n None in
25-
for i=1 to n-1 do
26-
if Gen.bool st then a.(i) <- Some (Gen.int_bound (i-1) st)
27-
done;
28-
a
24+
25+
type work_kind = Atomic_incr | Tak | Gc_minor
26+
27+
type node =
28+
{
29+
dep : int option;
30+
work : work_kind
31+
}
2932

3033
type test_input =
3134
{
3235
num_threads : int;
33-
dependencies : int option array
36+
dependencies : node array
3437
}
3538

39+
40+
let gen_deps gen_work n st =
41+
Array.init n
42+
(fun i ->
43+
let dep = if i<>0 && Gen.bool st then Some (Gen.int_bound (i-1) st) else None in
44+
let work = gen_work st in
45+
{ dep; work })
46+
47+
let show_work_kind w = match w with
48+
| Atomic_incr -> "Atomic_incr"
49+
| Tak -> "Tak"
50+
| Gc_minor -> "Gc_minor"
51+
52+
let pp_work_kind = Util.Pp.of_show show_work_kind
53+
54+
let pp_node par fmt {dep;work} =
55+
let open Util.Pp in
56+
pp_record par fmt
57+
[
58+
pp_field "dep" (pp_option pp_int) dep;
59+
pp_field "work" pp_work_kind work;
60+
]
61+
3662
let pp_test_input par fmt { num_threads; dependencies } =
3763
let open Util.Pp in
3864
pp_record par fmt
3965
[
4066
pp_field "num_threads" pp_int num_threads;
41-
pp_field "dependencies" (pp_array (pp_option pp_int)) dependencies;
67+
pp_field "dependencies" (pp_array pp_node) dependencies;
4268
]
4369

4470
let show_test_input = Util.Pp.to_show pp_test_input
4571

72+
let shrink_node n = Iter.map (fun opt -> { n with dep = opt}) (Shrink.(option nil) n.dep)
4673
let shrink_deps test_input =
4774
let ls = Array.to_list test_input.dependencies in
48-
let is = Shrink.list ~shrink:Shrink.(option nil) ls in
75+
let is = Shrink.list ~shrink:shrink_node ls in
4976
Iter.map
5077
(fun deps ->
5178
let len = List.length deps in
5279
let arr = Array.of_list deps in
53-
let deps = Array.mapi (fun i j_opt -> match i,j_opt with
54-
| 0, _
55-
| _,None -> None
56-
| _,Some 0 -> Some 0
57-
| _, Some j ->
58-
if j<0 || j>=len || j>=i (* ensure reduced dep is valid *)
59-
then Some ((j + i) mod i)
60-
else Some j) arr in
80+
let deps = Array.mapi (fun i j_node ->
81+
let dep = match i,j_node.dep with
82+
| 0, _
83+
| _,None -> None
84+
| _,Some 0 -> Some 0
85+
| _, Some j ->
86+
if j<0 || j>=len || j>=i (* ensure reduced dep is valid *)
87+
then Some ((j + i) mod i)
88+
else Some j in
89+
{ j_node with dep }) arr in
6190
{ num_threads=len; dependencies=deps }) is
6291

63-
let arb_deps thread_bound =
92+
let arb_deps gen_work thread_bound =
6493
let gen_deps =
6594
Gen.(int_bound (thread_bound-1) >>= fun num_threads ->
6695
let num_threads = succ num_threads in
67-
gen_deps num_threads >>= fun dependencies -> return { num_threads; dependencies }) in
96+
gen_deps gen_work num_threads >>= fun dependencies -> return { num_threads; dependencies }) in
6897
make ~print:show_test_input ~shrink:shrink_deps gen_deps
6998

7099
(*let thread_id id i = Printf.sprintf "(thread %i, index %i)" id i*)
71100

72101
let is_first_with_dep i dep deps =
73-
[] = List.filteri (fun j opt -> j < i && opt = Some dep) (Array.to_list deps)
102+
[] = List.filteri (fun j node -> j < i && node.dep = Some dep) (Array.to_list deps)
103+
104+
(* a simple work item, from ocaml/testsuite/tests/misc/takc.ml *)
105+
let rec tak x y z =
106+
if x > y then tak (tak (x-1) y z) (tak (y-1) z x) (tak (z-1) x y)
107+
else z
108+
109+
let work () =
110+
for _ = 1 to 100 do
111+
assert (7 = tak 18 12 6);
112+
done
113+
114+
let a = Atomic.make 0
74115

75-
let build_dep_graph test_input f =
116+
let interp_work w = match w with
117+
| Atomic_incr -> Atomic.incr a
118+
| Tak -> work ()
119+
| Gc_minor -> Gc.minor ()
120+
121+
let build_dep_graph test_input =
76122
let rec build i thread_acc =
77123
if i=test_input.num_threads
78124
then List.rev thread_acc
79125
else
80-
let p = (match test_input.dependencies.(i) with
126+
let p = (match test_input.dependencies.(i).dep with
81127
| None ->
82-
Thread.create f ()
128+
Thread.create (fun () ->
129+
interp_work test_input.dependencies.(i).work
130+
) ()
83131
| Some dep ->
84132
Thread.create (fun () ->
85-
f();
133+
interp_work test_input.dependencies.(i).work;
134+
(*f();*)
86135
if is_first_with_dep i dep test_input.dependencies
87136
then
88137
let p' = List.nth thread_acc (i-1-dep) in
@@ -92,46 +141,23 @@ let build_dep_graph test_input f =
92141
in
93142
build 0 []
94143

95-
(** In this first test each created thread calls [work] - and then optionally join. *)
96-
(* a simple work item, from ocaml/testsuite/tests/misc/takc.ml *)
97-
let rec tak x y z =
98-
if x > y then tak (tak (x-1) y z) (tak (y-1) z x) (tak (z-1) x y)
99-
else z
100-
101-
let work () =
102-
for _ = 1 to 100 do
103-
assert (7 = tak 18 12 6);
104-
done
105-
106-
let test_tak_work ~thread_bound =
107-
Test.make ~name:"Thread.create/join - tak work" ~count:100
108-
(arb_deps thread_bound)
109-
((*Util.fork_prop_with_timeout 30*)
144+
let test_arb_work ~thread_bound =
145+
Test.make ~name:"Thread.create/join" ~count:100
146+
(arb_deps (Gen.frequencyl [(10,Atomic_incr);
147+
(10,Tak);
148+
(1,Gc_minor)]) thread_bound)
110149
(fun test_input ->
111-
(*Printf.printf "%s\n%!" (show_test_input test_input);*)
112-
let ps = build_dep_graph test_input work in
113-
List.iteri (fun i p -> if not (Array.mem (Some i) test_input.dependencies) then Thread.join p) ps;
114-
true))
115-
116-
(** In this test each created thread calls [Atomic.incr] - and then optionally join. *)
117-
let test_atomic_work ~thread_bound =
118-
Test.make ~name:"Thread.create/join - atomic" ~count:500
119-
(arb_deps thread_bound)
120-
(fun test_input ->
121-
let a = Atomic.make 0 in
122-
let ps = build_dep_graph test_input (fun () -> Atomic.incr a) in
123-
List.iteri (fun i p ->
124-
if not (Array.mem (Some i) test_input.dependencies)
125-
then
126-
Thread.join p;
127-
) ps;
128-
Atomic.get a = test_input.num_threads)
129-
130-
let bound_tak = if Sys.word_size == 64 then 100 else 16
131-
let bound_atomic = if Sys.word_size == 64 then 250 else 16
150+
Atomic.set a 0;
151+
let ps = build_dep_graph test_input in
152+
List.iteri
153+
(fun i p ->
154+
if not (Array.exists (fun n -> n.dep = Some i) test_input.dependencies)
155+
then Thread.join p) ps;
156+
Atomic.get a
157+
= Array.fold_left (fun a n -> if n.work = Atomic_incr then 1+a else a) 0 test_input.dependencies)
158+
159+
let bound_arb = if Sys.word_size == 64 then 100 else 16
132160

133161
;;
134162
QCheck_base_runner.run_tests_main
135-
[test_tak_work ~thread_bound:bound_tak;
136-
test_atomic_work ~thread_bound:bound_atomic
137-
]
163+
[test_arb_work ~thread_bound:bound_arb; ]

0 commit comments

Comments
 (0)