Share
Embed
Transcript
116 slides
-
• Adds native support for concurrency and parallelism to OCaml
Multicore OCaml -
• Adds native support for concurrency and parallelism to OCaml
Multicore OCaml Overlapped execution A B A C B Time -
• Adds native support for concurrency and parallelism to OCaml
Multicore OCaml Overlapped execution A B A C B Time Simultaneous execution A B C Time -
• Adds native support for concurrency and parallelism to OCaml
Multicore OCaml Overlapped execution A B A C B Time Simultaneous execution A B C Time Effect Handlers -
• Adds native support for concurrency and parallelism to OCaml
Multicore OCaml Overlapped execution A B A C B Time Simultaneous execution A B C Time Effect Handlers Domains -
• Adds native support for concurrency and parallelism to OCaml
Multicore OCaml Overlapped execution A B A C B Time Simultaneous -
Concurrency is not parallelism Parallelism is a performance hack whereas
concurrency is a program structuring mechanism -
Concurrency is not parallelism • OS threads give you parallelism
and concurrenc y ✦ Too heavy weight for concurrent programmin g ✦ Http server with 1 OS thread per request is a terrible idea Parallelism is a performance hack whereas concurrency is a program structuring mechanism -
Concurrency is not parallelism • OS threads give you parallelism
and concurrenc y ✦ Too heavy weight for concurrent programmin g ✦ Http server with 1 OS thread per request is a terrible idea • Programming languages provide concurrent programming mechanisms as primitives ✦ async/await, generators, coroutines, etc. Parallelism is a performance hack whereas concurrency is a program structuring mechanism -
Concurrency is not parallelism • OS threads give you parallelism
and concurrenc y ✦ Too heavy weight for concurrent programmin g ✦ Http server with 1 OS thread per request is a terrible idea • Programming languages provide concurrent programming mechanisms as primitives ✦ async/await, generators, coroutines, etc. • Often include different primitives for concurrent programmin g ✦ JavaScript has async/await, generators, promises, and callbacks!! Parallelism is a performance hack whereas concurrency is a program structuring mechanism -
Concurrent Programming in OCaml • OCaml does not have primitive
support for concurrent programming -
Concurrent Programming in OCaml • OCaml does not have primitive
support for concurrent programming • Lwt and Async - concurrent programming libraries in OCam l ✦ Callback-oriented programming with monad synta x ✦ But do not satisfy monad laws -
Concurrent Programming in OCaml • OCaml does not have primitive
support for concurrent programming • Lwt and Async - concurrent programming libraries in OCam l ✦ Callback-oriented programming with monad synta x ✦ But do not satisfy monad laws • Suffers many pitfalls of callback-oriented programmin g ✦ No backtraces, exceptions can’t be used, monadic syntax -
Concurrent Programming in OCaml • OCaml does not have primitive
support for concurrent programming • Lwt and Async - concurrent programming libraries in OCam l ✦ Callback-oriented programming with monad synta x ✦ But do not satisfy monad laws • Suffers many pitfalls of callback-oriented programmin g ✦ No backtraces, exceptions can’t be used, monadic syntax • Go (goroutines) and GHC Haskell (threads) have better abstractions — lightweight threads -
Concurrent Programming in OCaml • OCaml does not have primitive
support for concurrent programming • Lwt and Async - concurrent programming libraries in OCam l ✦ Callback-oriented programming with monad synta x ✦ But do not satisfy monad laws • Suffers many pitfalls of callback-oriented programmin g ✦ No backtraces, exceptions can’t be used, monadic syntax • Go (goroutines) and GHC Haskell (threads) have better abstractions — lightweight threads Should we add lightweight threads to OCaml? -
Effect Handlers • A mechanism for programming with user-de f
i ned effects • Modular basis of non-local control- f l ow mechanism s ✦ Exceptions, generators, lightweight threads, promises, asynchronous IO, coroutines -
Effect Handlers • A mechanism for programming with user-de f
i ned effects • Modular basis of non-local control- f l ow mechanism s ✦ Exceptions, generators, lightweight threads, promises, asynchronous IO, coroutines • Effect declaration separate from interpretation (c.f. exceptions) -
Effect Handlers • A mechanism for programming with user-de f
i ned effects • Modular basis of non-local control- f l ow mechanism s ✦ Exceptions, generators, lightweight threads, promises, asynchronous IO, coroutines • Effect declaration separate from interpretation (c.f. exceptions) effect E : string let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " -
Effect Handlers • A mechanism for programming with user-de f
i ned effects • Modular basis of non-local control- f l ow mechanism s ✦ Exceptions, generators, lightweight threads, promises, asynchronous IO, coroutines • Effect declaration separate from interpretation (c.f. exceptions) effect E : string let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " effect declaration -
Effect Handlers • A mechanism for programming with user-de f
i ned effects • Modular basis of non-local control- f l ow mechanism s ✦ Exceptions, generators, lightweight threads, promises, asynchronous IO, coroutines • Effect declaration separate from interpretation (c.f. exceptions) effect E : string let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " computation effect declaration -
Effect Handlers • A mechanism for programming with user-de f
i ned effects • Modular basis of non-local control- f l ow mechanism s ✦ Exceptions, generators, lightweight threads, promises, asynchronous IO, coroutines • Effect declaration separate from interpretation (c.f. exceptions) effect E : string let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " computation handler effect declaration -
Effect Handlers • A mechanism for programming with user-de f
i ned effects • Modular basis of non-local control- f l ow mechanism s ✦ Exceptions, generators, lightweight threads, promises, asynchronous IO, coroutines • Effect declaration separate from interpretation (c.f. exceptions) effect E : string let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " computation handler suspends current computation effect declaration -
Effect Handlers • A mechanism for programming with user-de f
i ned effects • Modular basis of non-local control- f l ow mechanism s ✦ Exceptions, generators, lightweight threads, promises, asynchronous IO, coroutines • Effect declaration separate from interpretation (c.f. exceptions) effect E : string let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " computation handler delimited continuation suspends current computation effect declaration -
Effect Handlers • A mechanism for programming with user-de f
i ned effects • Modular basis of non-local control- f l ow mechanism s ✦ Exceptions, generators, lightweight threads, promises, asynchronous IO, coroutines • Effect declaration separate from interpretation (c.f. exceptions) effect E : string let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " computation handler delimited continuation suspends current computation resume suspended computation effect declaration -
Stepping through the example effect E : string let comp
() = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp -
Stepping through the example effect E : string let comp
() = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp -
comp Stepping through the example effect E : string let
comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp parent Fiber: A piece of stack + effect handler -
comp comp Stepping through the example effect E : string
let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp parent 0 -
comp comp Stepping through the example effect E : string
let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp k 0 -
comp comp Stepping through the example effect E : string
let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp k 0 -
comp comp Stepping through the example effect E : string
let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp k 0 -
comp comp Stepping through the example effect E : string
let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp k 0 1 -
comp comp Stepping through the example effect E : string
let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp k 0 1 -
comp comp Stepping through the example effect E : string
let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp k parent 0 1 -
comp comp Stepping through the example effect E : string
let comp () = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp k parent 0 1 2 -
Stepping through the example effect E : string let comp
() = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp k 0 1 2 3 -
Stepping through the example effect E : string let comp
() = print_string "0 "; print_string (perform E); print_string "3 " let main () = try comp () with effect E k -> print_string "1 "; continue k "2 "; print_string “4 " pc main sp k 0 1 2 3 4 -
effect A : unit effect B : unit let baz
() = perform A let bar () = try baz () with effect B k -> continue k () let foo () = try bar () with effect A k -> continue k () Handlers can be nested foo bar baz sp parent parent pc -
effect A : unit effect B : unit let baz
() = perform A let bar () = try baz () with effect B k -> continue k () let foo () = try bar () with effect A k -> continue k () Handlers can be nested foo bar baz sp parent parent pc -
effect A : unit effect B : unit let baz
() = perform A let bar () = try baz () with effect B k -> continue k () let foo () = try bar () with effect A k -> continue k () Handlers can be nested foo bar baz sp parent pc k -
effect A : unit effect B : unit let baz
() = perform A let bar () = try baz () with effect B k -> continue k () let foo () = try bar () with effect A k -> continue k () Handlers can be nested foo bar baz sp parent pc k • Linear search through handler s • Handler stacks shallow in practice -
Lightweight Threading effect Fork : (unit -> unit) -> unit
effect Yield : unit -
Lightweight Threading effect Fork : (unit -> unit) -> unit
effect Yield : unit let run main = ... (* assume queue of continuations *) let run_next () = match dequeue () with | Some k -> continue k () | None -> () in let rec spawn f = match f () with | () -> run_next () (* value case *) | effect Yield k -> enqueue k; run_next () | effect (Fork f) k -> enqueue k; spawn f in spawn main -
Lightweight Threading effect Fork : (unit -> unit) -> unit
effect Yield : unit let run main = ... (* assume queue of continuations *) let run_next () = match dequeue () with | Some k -> continue k () | None -> () in let rec spawn f = match f () with | () -> run_next () (* value case *) | effect Yield k -> enqueue k; run_next () | effect (Fork f) k -> enqueue k; spawn f in spawn main let fork f = perform (Fork f) let yield () = perform Yield -
Lightweight threading let main () = fork (fun _ ->
print_endline "1.a"; yield (); print_endline "1.b"); fork (fun _ -> print_endline "2.a"; yield (); print_endline “2.b") ;; run main -
Lightweight threading let main () = fork (fun _ ->
print_endline "1.a"; yield (); print_endline "1.b"); fork (fun _ -> print_endline "2.a"; yield (); print_endline “2.b") ;; run main 1.a 2.a 1.b 2.b -
Lightweight threading let main () = fork (fun _ ->
print_endline "1.a"; yield (); print_endline "1.b"); fork (fun _ -> print_endline "2.a"; yield (); print_endline “2.b") ;; run main 1.a 2.a 1.b 2.b • Direct-style (no monads) • User-code need not be aware of effects -
Generators • Generators — non-continuous traversal of data structure by
yielding value s ✦ Primitives in JavaScript and Python -
Generators • Generators — non-continuous traversal of data structure by
yielding value s ✦ Primitives in JavaScript and Python function* generator(i) { yield i; yield i + 10; } const gen = generator(10); console.log(gen.next().value); // expected output: 10 console.log(gen.next().value); // expected output: 20 -
Generators • Generators — non-continuous traversal of data structure by
yielding value s ✦ Primitives in JavaScript and Python • Can be derived automatically from any iterator using effect handlers function* generator(i) { yield i; yield i + 10; } const gen = generator(10); console.log(gen.next().value); // expected output: 10 console.log(gen.next().value); // expected output: 20 -
Generators: effect handlers module MkGen (S :sig type 'a t
val iter : ('a -> unit) -> 'a t -> unit end) : sig val gen : 'a S.t -> (unit -> 'a option) end = struct -
Generators: effect handlers module MkGen (S :sig type 'a t
val iter : ('a -> unit) -> 'a t -> unit end) : sig val gen : 'a S.t -> (unit -> 'a option) end = struct let gen : type a. a S.t -> (unit -> a option) = fun l -> let module M = struct effect Yield : a -> unit end in let open M in let rec step = ref (fun () -> match S.iter (fun v -> perform (Yield v)) l with | () -> None | effect (Yield v) k -> step := (fun () -> continue k ()); Some v) in fun () -> !step () end -
Generators: List module L = MkGen (struct type 'a t
= 'a list let iter = List.iter end) -
Generators: List module L = MkGen (struct type 'a t
= 'a list let iter = List.iter end) let next = L.gen [1;2;3] next() (* Some 1 *) next() (* Some 2 *) next() (* Some 3 *) next() (* None *) -
Generators: Tree type 'a tree = | Leaf | Node
of 'a tree * 'a * 'a tree let rec iter f = function | Leaf -> () | Node (l, x, r) -> iter f l; f x; iter f r module T = MkGen(struct type 'a t = 'a tree let iter = iter end) -
Generators: Tree type 'a tree = | Leaf | Node
of 'a tree * 'a * 'a tree let rec iter f = function | Leaf -> () | Node (l, x, r) -> iter f l; f x; iter f r module T = MkGen(struct type 'a t = 'a tree let iter = iter end) (* Make a complete binary tree of depth [n] using [O(n)] space *) let rec make = function | 0 -> Leaf | n -> let t = make (n-1) in Node (t,n,t) -
Generators: Tree type 'a tree = | Leaf | Node
of 'a tree * 'a * 'a tree let rec iter f = function | Leaf -> () | Node (l, x, r) -> iter f l; f x; iter f r module T = MkGen(struct type 'a t = 'a tree let iter = iter end) let t = make 2 let next = T.gen t next() (* Some 1 *) next() (* Some 2 *) next() (* Some 1 *) next() (* None *) 2 1 1 (* Make a complete binary tree of depth [n] using [O(n)] space *) let rec make = function | 0 -> Leaf | n -> let t = make (n-1) in Node (t,n,t) -
Static Semantics • No effect safet y ✦ No static
guarantee that all the effects performed are handled (c.f. exceptions ) ✦ perform E at the top-level raises Unhandled exception -
Static Semantics • No effect safet y ✦ No static
guarantee that all the effects performed are handled (c.f. exceptions ) ✦ perform E at the top-level raises Unhandled exception • Effect system in the work s ✦ See also Eff, Koka, Links, Heliu m ✦ Track both user-de f i ned and built-in (ref, io) effect s ✦ OCaml becomes a pure language (in the Haskell sense) -
Static Semantics • No effect safet y ✦ No static
guarantee that all the effects performed are handled (c.f. exceptions ) ✦ perform E at the top-level raises Unhandled exception • Effect system in the work s ✦ See also Eff, Koka, Links, Heliu m ✦ Track both user-de f i ned and built-in (ref, io) effect s ✦ OCaml becomes a pure language (in the Haskell sense) let foo () = print_string "hello, world" val foo : unit -[ io ]-> unit Syntax is still in the works -
Static Semantics • No effect safet y ✦ No static
guarantee that all the effects performed are handled (c.f. exceptions ) ✦ perform E at the top-level raises Unhandled exception • Effect system in the work s ✦ See also Eff, Koka, Links, Heliu m ✦ Track both user-de f i ned and built-in (ref, io) effect s ✦ OCaml becomes a pure language (in the Haskell sense) let foo () = print_string "hello, world" val foo : unit -[ io ]-> unit Syntax is still in the works • Today, Multicore OCaml effect handler static semantics is simple -
Static Semantics (* OCaml extensible variant type *) type 'a
eff = .. type _ eff = E : string -> int eff gets translated to effect E : string -> int The declaration -
Static Semantics type ('a,'b) continuation argument type result type val
perform: 'a eff -> 'a val continue: ('a,'b) continuation -> 'a -> 'b -
Static Semantics match e with | None -> false |
Some b -> b | effect (E s) k1 -> e1 | effect (F f) k2 -> e2 match_with (fun () -> e) { retc = (function None -> false | Some b -> b); effc = (function | (E s) -> (fun k1 -> e1) | (F f) -> (fun k2 -> e2) | e -> (fun k -> continue k (perform e)); } (* Internal API *) type 'a comp = unit -> ‘a type ('a,'b) handler = { retc: 'a -> 'b; (* value case *) effc: 'c.'c eff -> ('c,'b) continuation -> 'b; (* effect case *) } val match_with: 'a comp -> ('a,'b) handler -> ‘b compiled to assuming we have -
Comparison to shift/reset • Effect handlers equivalent in expressive power
to other delimited control operator s ✦ Forster et al, “On the expressive power of user-de f i ned effects: Effect handlers, monadic re f l ection, delimited control”, JFP 201 9 ✦ Macro-expressible to each other (ignoring types) -
Comparison to shift/reset • Effect handlers equivalent in expressive power
to other delimited control operator s ✦ Forster et al, “On the expressive power of user-de f i ned effects: Effect handlers, monadic re f l ection, delimited control”, JFP 201 9 ✦ Macro-expressible to each other (ignoring types) • Nicer to program with thanks to the handler syntax goto : while loop :: shift/reset : effect handlers - Andrej Bauer -
Retro f i tting Challenges • Millions of lines of
legacy cod e ✦ Written without non-local control- f l ow in min d ✦ Cost of refactoring sequential code itself is prohibitive -
Retro f i tting Challenges • Millions of lines of
legacy cod e ✦ Written without non-local control- f l ow in min d ✦ Cost of refactoring sequential code itself is prohibitive • Low-latency and predictable performanc e ✦ Fast exceptions, FFI -
Retro f i tting Challenges • Millions of lines of
legacy cod e ✦ Written without non-local control- f l ow in min d ✦ Cost of refactoring sequential code itself is prohibitive • Low-latency and predictable performanc e ✦ Fast exceptions, FFI • Excellent compatibility with debugging and pro f i ling tool s ✦ gdb, lldb, perf, libunwind, etc. -
Retro f i tting Challenges • Millions of lines of
legacy cod e ✦ Written without non-local control- f l ow in min d ✦ Cost of refactoring sequential code itself is prohibitive • Low-latency and predictable performanc e ✦ Fast exceptions, FFI • Excellent compatibility with debugging and pro f i ling tool s ✦ gdb, lldb, perf, libunwind, etc. Backwards compatibility before fancy new features -
Defensive Programming • OCaml is a systems programming languag e
✦ Manipulates resources such as f i les, sockets, buffers, etc. -
Defensive Programming • OCaml is a systems programming languag e
✦ Manipulates resources such as f i les, sockets, buffers, etc. • OCaml code is written in defensive style to guard against exceptional behaviour and clear up resources -
Defensive Programming • OCaml is a systems programming languag e
✦ Manipulates resources such as f i les, sockets, buffers, etc. • OCaml code is written in defensive style to guard against exceptional behaviour and clear up resources let copy ic oc = let rec loop () = let l = input_line ic in output_string oc (l ^ "\n"); loop () in try loop () with | End_of_file -> close_in ic; close_out oc | e -> close_in ic; close_out oc; raise e -
Defensive Programming • OCaml is a systems programming languag e
✦ Manipulates resources such as f i les, sockets, buffers, etc. • OCaml code is written in defensive style to guard against exceptional behaviour and clear up resources let copy ic oc = let rec loop () = let l = input_line ic in output_string oc (l ^ "\n"); loop () in try loop () with | End_of_file -> close_in ic; close_out oc | e -> close_in ic; close_out oc; raise e raises End_of_file at the end -
Defensive Programming • OCaml is a systems programming languag e
✦ Manipulates resources such as f i les, sockets, buffers, etc. • OCaml code is written in defensive style to guard against exceptional behaviour and clear up resources let copy ic oc = let rec loop () = let l = input_line ic in output_string oc (l ^ "\n"); loop () in try loop () with | End_of_file -> close_in ic; close_out oc | e -> close_in ic; close_out oc; raise e raise Sys_error when channel is closed raises End_of_file at the end -
Defensive Programming • OCaml is a systems programming languag e
✦ Manipulates resources such as f i les, sockets, buffers, etc. • OCaml code is written in defensive style to guard against exceptional behaviour and clear up resources let copy ic oc = let rec loop () = let l = input_line ic in output_string oc (l ^ "\n"); loop () in try loop () with | End_of_file -> close_in ic; close_out oc | e -> close_in ic; close_out oc; raise e We would like to make this code transparently asynchronous raise Sys_error when channel is closed raises End_of_file at the end -
Asynchronous IO effect In_line : in_channel -> string effect Out_str
: out_channel * string -> unit let input_line ic = perform (In_line ic) let output_string oc s = perform (Out_str (oc,s)) let run_aio f = match f () with | v -> v | effect (In_line chan) k -> register_async_input_line chan k; run_next () | effect (Out_str (chan, s)) k -> register_async_output_string chan s k; run_next () -
Asynchronous IO effect In_line : in_channel -> string effect Out_str
: out_channel * string -> unit let input_line ic = perform (In_line ic) let output_string oc s = perform (Out_str (oc,s)) let run_aio f = match f () with | v -> v | effect (In_line chan) k -> register_async_input_line chan k; run_next () | effect (Out_str (chan, s)) k -> register_async_output_string chan s k; run_next () • Continue with appropriate value when the asynchronous IO call returns -
Asynchronous IO effect In_line : in_channel -> string effect Out_str
: out_channel * string -> unit let input_line ic = perform (In_line ic) let output_string oc s = perform (Out_str (oc,s)) let run_aio f = match f () with | v -> v | effect (In_line chan) k -> register_async_input_line chan k; run_next () | effect (Out_str (chan, s)) k -> register_async_output_string chan s k; run_next () • Continue with appropriate value when the asynchronous IO call returns • But what about termination identi f i ed by End_of_file and Sys_error exceptions? -
Discontinue • We add a discontinue primitive to resume a
continuation by raising an exceptio n • On End_of_file and Sys_error, the asynchronous IO scheduler uses discontinue to raise the appropriate exception val discontinue: ('a,'b) continuation -> exn -> 'b -
Linearity • Resources such as sockets, f i le descriptors,
channels and buffers are linear resource s ✦ Created and destroyed exactly once -
Linearity • Resources such as sockets, f i le descriptors,
channels and buffers are linear resource s ✦ Created and destroyed exactly once • When calling an OCaml function, the caller expects the callee to return exactly once either with a value or an exceptio n ✦ Defensive programming already guards against exceptional return cases -
Linearity • With effect handlers if the captured continuation is
dropped on the f l oor, then any function call may only return at-most onc e ✦ This breaks resource-safe legacy code -
Linearity • With effect handlers if the captured continuation is
dropped on the f l oor, then any function call may only return at-most onc e ✦ This breaks resource-safe legacy code effect E : unit let foo () = perform E let bar () = let ic = open_in "input.txt" in match foo () with | v -> close_in ic | exception e -> close_in ic; raise e let baz () = try bar () with | effect E _ -> () (* leak *) -
Linearity • We assume that well-formed programs resume captured continuations
exactly once either using continue or discontinu e ✦ Someone please add linear types to OCaml :-) -
Linearity • We assume that well-formed programs resume captured continuations
exactly once either using continue or discontinu e ✦ Someone please add linear types to OCaml :-) • Linear use of continuations ensures that non-local control- f l ow and resources work well togethe r ✦ No need for Scheme dynamic-wind -
Linearity • We assume that well-formed programs resume captured continuations
exactly once either using continue or discontinu e ✦ Someone please add linear types to OCaml :-) • Linear use of continuations ensures that non-local control- f l ow and resources work well togethe r ✦ No need for Scheme dynamic-wind • Core and Base provide unwind-protect implemented using exception s ✦ Backwards compatibility of resourceful code ensured thanks to linearity and defensive programming -
Foreign-function interface (* meander.ml *) external ocaml_to_c : unit ->
int = "ocaml_to_c" exception E1 exception E2 let c_to_ocaml () = raise E1;; Callback.register "c_to_ocaml" c_to_ocaml;; let omain () = try (* h1 *) try (* h2 *) ocaml_to_c () with E2 -> -42 with E1 -> 42;; assert (omain () = 42) /* meander.c */ #include <caml/mlvalues.h> #include <caml/callback.h value ocaml_to_c (value unit) { caml_callback(*caml_named_value("c_to_ocaml"), Val_unit); return Val_int(0); } -
Stack Management (* meander.ml *) external ocaml_to_c : unit ->
int = "ocaml_to_c" exception E1 exception E2 let c_to_ocaml () = raise E1;; Callback.register "c_to_ocaml" c_to_ocaml;; let omain () = try (* h1 *) try (* h2 *) ocaml_to_c () with E2 -> -42 with E1 -> 42;; assert (omain () = 42) /* meander.c */ #include <caml/mlvalues.h> #include <caml/callback.h value ocaml_to_c (value unit) { caml_callback(*caml_named_value("c_to_ocaml"), Val_unit); return Val_int(0); } Stock OCaml -
Stack Management Stock OCaml Multicore OCaml • Stack over f
l ow check s ✦ Reallocate with 2x stack spac e • FFI requires stack switch -
DWARF stack unwinding • DWARF bytecode is a Turing complete
language • In Multicore OCaml, we’ve encoded DWARF unwinding across callbacks, external calls and effect handler s ✦ gdb, lldb, perf continue to work! -
DWARF stack unwinding • DWARF bytecode is a Turing complete
language • In Multicore OCaml, we’ve encoded DWARF unwinding across callbacks, external calls and effect handler s ✦ gdb, lldb, perf continue to work! • Veri f i ed that the unwind tables are correct using an automated too l ✦ Basitien et al, “Reliable and Fast DWARF-Based Stack Unwinding”, OOPSLA 2019 -
No effects performance coq irmin menhir alt-ergo • ~1% faster
than stock (geomean of normalised running times ) ✦ Difference under measurement noise mostl y ✦ Outliers due to difference in allocators -
Performance let foo () = (* a *) try (*
b *) perform E (* d *) with effect E k -> (* c *) continue k () (* e *) -
Performance let foo () = (* a *) try (*
b *) perform E (* d *) with effect E k -> (* c *) continue k () (* e *) Instruction Sequence a to b b to c c to d d to e Signi f i cance Create a new stack & run the computation Performing & handling an e f f ect Resuming a continuation Returning from a computation & free the stack • Each of the instruction sequences involves a stack switc h • Intel(R) Xeon(R) Gold 5120 CPU @ 2.20GH z ★ For reference, memory read latency is 90 ns (local NUMA node) and 145 ns (remote NUMA node) -
Performance let foo () = (* a *) try (*
b *) perform E (* d *) with effect E k -> (* c *) continue k () (* e *) Instruction Sequence a to b b to c c to d d to e Signi f i cance Create a new stack & run the computation Performing & handling an e f f ect Resuming a continuation Returning from a computation & free the stack Time (ns) 23 5 11 7 • Each of the instruction sequences involves a stack switc h • Intel(R) Xeon(R) Gold 5120 CPU @ 2.20GH z ★ For reference, memory read latency is 90 ns (local NUMA node) and 145 ns (remote NUMA node) -
Performance: Generators • Traverse a complete binary-tree of depth 2
5 ✦ 226 stack switches -
Performance: Generators • Traverse a complete binary-tree of depth 2
5 ✦ 226 stack switches • Iterator — idiomatic recursive traversal -
Performance: Generators • Traverse a complete binary-tree of depth 2
5 ✦ 226 stack switches • Iterator — idiomatic recursive traversal • Generato r ✦ Hand-written generator (hw-generator ) ✤ CPS translation + defunctionalization to remove intermediate closure allocatio n ✦ Generator using effect handlers (eh-generator) -
Performance: Generators Variant Time (milliseconds) Iterator (baseline) 202 hw-generator 837
(3.76x) eh-generator 1879 (9.30x) Multicore OCaml -
Performance: Generators Variant Time (milliseconds) Iterator (baseline) 202 hw-generator 837
(3.76x) eh-generator 1879 (9.30x) Multicore OCaml Variant Time (milliseconds) Iterator (baseline) 492 generator 43842 (89.1x) nodejs 14.07 -
Performance: WebServer • Effect handlers for asynchronous I/O in direct-styl
e ✦ https://github.com/kayceesrk/ocaml-aeio/ • Variant s ✦ Go + net/http (GOMAXPROCS=1 ) ✦ OCaml + http/af + Lwt (explicit callbacks ) ✦ OCaml + http/af + Effect handlers (MC ) • Performance measured using wrk2 -
Performance: WebServer • Effect handlers for asynchronous I/O in direct-styl
e ✦ https://github.com/kayceesrk/ocaml-aeio/ • Variant s ✦ Go + net/http (GOMAXPROCS=1 ) ✦ OCaml + http/af + Lwt (explicit callbacks ) ✦ OCaml + http/af + Effect handlers (MC ) • Performance measured using wrk2 -
Performance: WebServer • Effect handlers for asynchronous I/O in direct-styl
e ✦ https://github.com/kayceesrk/ocaml-aeio/ • Variant s ✦ Go + net/http (GOMAXPROCS=1 ) ✦ OCaml + http/af + Lwt (explicit callbacks ) ✦ OCaml + http/af + Effect handlers (MC ) • Performance measured using wrk2 • Direct style (no monadic syntax) -
Thanks! • Multicore OCaml — https://github.com/ocaml-multicore/ocaml- multicore • Effects Examples
— https://github.com/ocaml-multicore/effects- examples • Sivaramakrishnan et al, “Retro f i tting Parallelism onto OCaml", ICFP 2020 • Dolan et al, “Concurrent System Programming with Effect Handlers”, TFP 2017 $ opam switch create 4.10.0+multicore \ --packages=ocaml-variants.4.10.0+multicore \ --repositories=multicore=git+https://github.com/ocaml-multicore/multicore-opam.git,default Install Multicore OCaml