1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
open! Base
open! Import
module Job = Parallel_kernel1.Job
module Thunk = Parallel_kernel1.Thunk
include Parallel_kernel0.Runqueue
type 'a promoter =
'a Job.t @ once portable
-> ('a Promise.t -> (tokens:int -> unit) @ local once) @ local once
let[@loop] rec count : type l. l node @ local once -> int = function
| Cons1 _ -> 1
| ConsN { more; _ } -> 1 + count more
;;
let[@loop] rec promote_loop
: nodes @ local -> tokens:int -> promoted:int -> f:('a. 'a promoter) @ local
-> #(nodes * tokens:int * promoted:int)
=
fun (Q cursor) ~tokens ~promoted ~f ->
(Stack_pointer.use [@kind word & value & value]) cursor ~f:(function [@inline]
| Some node when tokens > 0 ->
exclave_
let node = Obj.magic_many node in
let count = count node in
let tokens = tokens - count in
promote_batch node ~tokens ~promoted ~each:(tokens / (count + 1)) ~f
| _ -> #(Q cursor, ~tokens, ~promoted))
[@nontail]
and[@loop] promote_batch
: type l.
l node @ local once
-> tokens:int
-> promoted:int
-> each:int
-> f:('a. 'a promoter) @ local
-> #(nodes * tokens:int * promoted:int)
=
fun node ~tokens ~promoted ~each ~f ->
match node with
| Cons1 t ->
let promise = Promise.start () in
t.promise <- This promise;
f t.job promise ~tokens:each;
promote_loop t.down ~tokens:(tokens - each) ~promoted:(promoted + 1) ~f
| ConsN t ->
let promise = Promise.start () in
t.promise <- This promise;
f t.job promise ~tokens:each;
promote_batch t.more ~tokens:(tokens - each) ~promoted:(promoted + 1) ~each ~f
;;
let[@inline never] add_tokens queue tokens = queue.tokens <- queue.tokens + tokens
let promotions queue ~(f : 'a. 'a promoter) =
let start = queue.tokens in
let #(cursor, ~tokens:stop, ~promoted) =
promote_loop queue.cursor ~tokens:start ~promoted:0 ~f
in
let used = start - stop in
add_tokens queue ~-used;
queue.cursor <- cursor;
promoted
;;
let promote queue ~(scheduler : Parallel_kernel0.Scheduler.t) =
let promoted =
promotions queue ~f:(fun job promise ~tokens ->
scheduler.#subtask (Promise.try_fiber promise job ~scheduler ~tokens))
in
if promoted > 0 then scheduler.#try_wake ~n:promoted
;;
let[@inline] [@loop] rec node
: type a l.
jobs:(a * l) Hlist.Gen(Thunk).t @ contended once portable -> (a * l) node @ local once
=
fun ~jobs -> exclave_
match jobs with
| [ job ] ->
Cons1 { job = Job.wrap job; promise = Null; down = Q (Stack_pointer.null ()) }
| job :: (_ :: _ as jobs) ->
ConsN { job = Job.wrap job; promise = Null; more = node ~jobs }
;;
let[@inline] push_head t ~new_head =
let new_head = Q new_head in
let[@inline] [@loop] rec link : type l. l node @ local once -> unit = function
| Cons1 node -> node.down <- new_head
| ConsN node -> link node.more
in
let (Q head as old_head) = t.head in
Stack_pointer.use head ~f:(function [@inline]
| None -> ()
| Some head -> link head);
t.head <- new_head;
let (Q cursor) = t.cursor in
Stack_pointer.use cursor ~f:(function [@inline]
| None -> t.cursor <- new_head
| Some _ -> ());
old_head
;;
let[@inline] pop_head t ~old_head =
let null = Q (Stack_pointer.null ()) in
let[@inline] [@loop] rec unlink : type l. l node @ local once -> unit = function
| Cons1 node -> node.down <- null
| ConsN node -> unlink node.more
in
let (Q old_head) = old_head in
Stack_pointer.use old_head ~f:(function [@inline]
| None -> ()
| Some old_head -> unlink old_head);
let (Q new_head) = t.head in
let (Q new_cursor) = t.cursor in
if Stack_pointer.equal new_cursor new_head then t.cursor <- null;
t.head <- Q old_head
;;
let[@inline] value_exn = function
| This a -> a
| Null -> failwith "Null"
;;
let[@inline never] [@loop] rec await
: type a l.
(a * l) node @ local once
-> Parallel_kernel1.t @ local
-> #((a * l) Hlist.Gen(Result.Capsule).t * tokens:int)
@ forkable local portable unique
=
fun node parallel -> exclave_
match node with
| Cons1 { job; promise; _ } ->
let #(a, ~tokens) = Promise.await_or_run (value_exn promise) job parallel in
#([ a ], ~tokens)
| ConsN { job; promise; more } ->
let #(a, ~tokens:a_tokens) = Promise.await_or_run (value_exn promise) job parallel in
let #(more, ~tokens:more_tokens) = await more parallel in
#(a :: more, ~tokens:(a_tokens + more_tokens))
;;
let[@inline] [@loop] rec apply
: type a l.
(a * l) Hlist.Gen(Thunk).t @ contended once portable
-> Parallel_kernel1.t @ local
-> (a * l) Hlist.Gen(Result.Capsule).t @ forkable local portable unique
=
fun node parallel -> exclave_
match node with
| [ job ] ->
let a = Thunk.encapsulate job parallel in
[ a ]
| job :: (_ :: _ as rest) ->
let a = Thunk.encapsulate job parallel in
a :: apply rest parallel
;;
let[@inline] [@loop] rec with_node
: type (a : value mod portable) b l.
t @ local
-> (Parallel_kernel1.t @ local -> a @ forkable local unique) @ local once
-> (b * l) Hlist.Gen(Thunk).t @ contended once portable
-> (b * l) node @ local once
-> Parallel_kernel1.t @ local
-> a * (b * l) Hlist.Gen(Result.Capsule).t @ forkable local portable unique
=
fun t first rest node parallel -> exclave_
(Stack_pointer.unsafe_with_value node ~f:(fun [@inline] new_head -> exclave_
let old_head = push_head t ~new_head in
let a = first parallel in
pop_head t ~old_head;
Stack_pointer.use new_head ~f:(function [@inline]
| None -> assert false
| Some node ->
exclave_
(match node with
| Cons1 { promise = This _; _ } | ConsN { promise = This _; _ } ->
let #(rest, ~tokens) = await node parallel in
if tokens <> 0 then add_tokens t tokens;
{ forkable = a, rest }
| Cons1 { promise = Null; _ } -> { forkable = a, apply rest parallel }
| ConsN { promise = Null; more; _ } ->
let (first :: rest) = rest in
let[@inline] first parallel = exclave_ Thunk.encapsulate first parallel in
let b, rest = with_node t first rest more parallel in
{ forkable = a, b :: rest }))
[@nontail]))
.forkable
;;
let[@inline] with_jobs
: ('a : value mod portable) 'b.
t @ local
-> 'a Thunk.t @ local once
-> ('b * 'l) Hlist.Gen(Thunk).t @ contended once portable
-> Parallel_kernel1.t @ local
-> 'a Result.t * ('b * 'l) Hlist.Gen(Result.Capsule).t @ forkable local portable unique
=
fun t first rest parallel -> exclave_
let[@inline] first parallel = exclave_ Thunk.apply first parallel in
let rest = (Obj.magic_many [@mode contended portable]) rest in
let node = node ~jobs:rest in
with_node t first rest node parallel [@nontail]
;;
module For_testing = struct
let create () = exclave_
{ tokens = 0; head = Q (Stack_pointer.null ()); cursor = Q (Stack_pointer.null ()) }
;;
let tokens t = t.tokens
let promote t ~n ~f =
t.tokens <- n;
promotions t ~f:(fun _ _ ~tokens -> f ~tokens) |> (ignore : int -> unit)
;;
let with_jobs t jobs ~f =
let node = node ~jobs in
Stack_pointer.unsafe_with_value node ~f:(fun new_head : unit ->
let old_head = push_head t ~new_head in
f t;
pop_head t ~old_head)
[@nontail]
;;
end