A generic filter allocates its Vec, and the sweep says what a rebuild costs
This commit is contained in:
parent
50798aac89
commit
0749913420
@ -2884,7 +2884,14 @@ and file_guard ctx loc ~path_slot ~op mk_steps =
|
|||||||
both callers, so the next kind of type added cannot be added to one of
|
both callers, so the next kind of type added cannot be added to one of
|
||||||
them. *)
|
them. *)
|
||||||
and type_named ctx n =
|
and type_named ctx n =
|
||||||
List.mem n Types.primitive_names
|
(* A type variable names a type here too, which is what lets [(vec-new t)]
|
||||||
|
be written in a generic body: inside an instantiation [resolve_name]
|
||||||
|
answers with the concrete element type, and during the abstract pass it
|
||||||
|
answers [Var t] and the [Vec] that comes back is a [(Vec t)] — generic,
|
||||||
|
and refused by anything that needs a size. *)
|
||||||
|
List.mem n ctx.env.tyvars
|
||||||
|
|| List.mem_assoc n ctx.env.subst
|
||||||
|
|| List.mem n Types.primitive_names
|
||||||
|| Hashtbl.mem ctx.env.structs n
|
|| Hashtbl.mem ctx.env.structs n
|
||||||
|| Hashtbl.mem ctx.env.unions n
|
|| Hashtbl.mem ctx.env.unions n
|
||||||
|| Hashtbl.mem ctx.env.enums n
|
|| Hashtbl.mem ctx.env.enums n
|
||||||
|
|||||||
171
spike/generics/measure.ml
Normal file
171
spike/generics/measure.ml
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
(* What redefining a generic function costs the dev loop, measured.
|
||||||
|
|
||||||
|
The question the spike exists to answer: C-c C-c on a concrete function is
|
||||||
|
about 35 ms today, and a generic function that is redefined has to rebuild
|
||||||
|
*every* instantiation. So the sweep is one generic called at N concrete
|
||||||
|
types, N = 1..8, against the handwritten N-copies program it replaces, and
|
||||||
|
the three things a C-c C-c actually pays for are timed separately:
|
||||||
|
|
||||||
|
check Check.program_with_env over the whole accumulated program —
|
||||||
|
which is what Session.eval does on every evaluation, so this is
|
||||||
|
paid whether the redefined function is generic or not.
|
||||||
|
emit Emit.redefinition for the fns being installed.
|
||||||
|
build llc + ld -shared, from Build.shared — the dominant term.
|
||||||
|
|
||||||
|
Nothing here modifies the session or the dev loop; it drives the real ones. *)
|
||||||
|
|
||||||
|
let tys = [| "i8"; "i16"; "i32"; "i64"; "u8"; "u16"; "u32"; "f32" |]
|
||||||
|
|
||||||
|
let time f =
|
||||||
|
let t0 = Unix.gettimeofday () in
|
||||||
|
let x = f () in
|
||||||
|
(x, (Unix.gettimeofday () -. t0) *. 1000.)
|
||||||
|
|
||||||
|
(* Best of k, because llc and the linker are processes and the machine is
|
||||||
|
noisy; a median would hide a systematic cost and a mean would report the
|
||||||
|
scheduler. *)
|
||||||
|
let best k f =
|
||||||
|
let rec go i acc = if i = 0 then acc else
|
||||||
|
let _, ms = time f in go (i - 1) (min acc ms) in
|
||||||
|
go k infinity
|
||||||
|
|
||||||
|
let generic_src n =
|
||||||
|
let b = Buffer.create 1024 in
|
||||||
|
Buffer.add_string b
|
||||||
|
"(defn gswap [xs [$t] i i32 j i32] ()\n\
|
||||||
|
\ (let [tmp (at xs i)]\n\
|
||||||
|
\ (set (at xs i) (at xs j))\n\
|
||||||
|
\ (set (at xs j) tmp)))\n\n\
|
||||||
|
(defn gsort [s [$t] before? (Fn [$t $t] bool)] ()\n\
|
||||||
|
\ (let [i 1]\n\
|
||||||
|
\ (while (< i (len s))\n\
|
||||||
|
\ (let [j i]\n\
|
||||||
|
\ (while (and (> j 0) (before? (at s j) (at s (- j 1))))\n\
|
||||||
|
\ (gswap s (- j 1) j)\n\
|
||||||
|
\ (set j (- j 1))))\n\
|
||||||
|
\ (set i (+ i 1)))))\n\n";
|
||||||
|
for i = 0 to n - 1 do
|
||||||
|
Buffer.add_string b (Printf.sprintf "(defvar xs-%s [8 %s])\n" tys.(i) tys.(i))
|
||||||
|
done;
|
||||||
|
Buffer.add_string b "\n(defn main [] ()\n";
|
||||||
|
for i = 0 to n - 1 do
|
||||||
|
Buffer.add_string b
|
||||||
|
(Printf.sprintf " (gsort (slice xs-%s 0 8) (fn [a b] (< a b)))\n" tys.(i))
|
||||||
|
done;
|
||||||
|
Buffer.add_string b " )\n";
|
||||||
|
Buffer.contents b
|
||||||
|
|
||||||
|
(* The same program as it is written today: one copy of each function per
|
||||||
|
element type, by hand. This is prelude.ml's shape. *)
|
||||||
|
let mono_src n =
|
||||||
|
let b = Buffer.create 1024 in
|
||||||
|
for i = 0 to n - 1 do
|
||||||
|
let t = tys.(i) in
|
||||||
|
Buffer.add_string b
|
||||||
|
(Printf.sprintf
|
||||||
|
"(defn mswap-%s [xs [%s] i i32 j i32] ()\n\
|
||||||
|
\ (let [tmp (at xs i)]\n\
|
||||||
|
\ (set (at xs i) (at xs j))\n\
|
||||||
|
\ (set (at xs j) tmp)))\n\n\
|
||||||
|
(defn msort-%s [s [%s] before? (Fn [%s %s] bool)] ()\n\
|
||||||
|
\ (let [i 1]\n\
|
||||||
|
\ (while (< i (len s))\n\
|
||||||
|
\ (let [j i]\n\
|
||||||
|
\ (while (and (> j 0) (before? (at s j) (at s (- j 1))))\n\
|
||||||
|
\ (mswap-%s s (- j 1) j)\n\
|
||||||
|
\ (set j (- j 1))))\n\
|
||||||
|
\ (set i (+ i 1)))))\n\n"
|
||||||
|
t t t t t t t);
|
||||||
|
Buffer.add_string b (Printf.sprintf "(defvar xs-%s [8 %s])\n\n" t t)
|
||||||
|
done;
|
||||||
|
Buffer.add_string b "(defn main [] ()\n";
|
||||||
|
for i = 0 to n - 1 do
|
||||||
|
Buffer.add_string b
|
||||||
|
(Printf.sprintf " (msort-%s (slice xs-%s 0 8) (fn [a b] (< a b)))\n"
|
||||||
|
tys.(i) tys.(i))
|
||||||
|
done;
|
||||||
|
Buffer.add_string b " )\n";
|
||||||
|
Buffer.contents b
|
||||||
|
|
||||||
|
let write path s =
|
||||||
|
let oc = open_out path in output_string oc s; close_out oc
|
||||||
|
|
||||||
|
let dir =
|
||||||
|
let d = Filename.concat (Filename.get_temp_dir_name ()) "flan-generics-spike" in
|
||||||
|
(try Unix.mkdir d 0o700 with Unix.Unix_error (Unix.EEXIST, _, _) -> ());
|
||||||
|
d
|
||||||
|
|
||||||
|
let decls_of path =
|
||||||
|
(Flan.Load.program ~file:path
|
||||||
|
(Flan.Parse.program (Flan.Reader.read_file path))).Flan.Load.decls
|
||||||
|
|
||||||
|
(* Every function the program ended up with whose name starts with one of the
|
||||||
|
generic names — the instantiations, which is exactly what a redefinition of
|
||||||
|
the generic would have to rebuild. *)
|
||||||
|
let instantiations (p : Flan.Tast.program) =
|
||||||
|
List.filter_map
|
||||||
|
(fun (f : Flan.Tast.fn) ->
|
||||||
|
let n = f.Flan.Tast.name in
|
||||||
|
if String.length n > 6 && String.sub n 0 6 = "gswap-" then Some n
|
||||||
|
else if String.length n > 6 && String.sub n 0 6 = "gsort-" then Some n
|
||||||
|
else None)
|
||||||
|
p.Flan.Tast.fns
|
||||||
|
|
||||||
|
let build_ms ir =
|
||||||
|
let out = Filename.concat dir "redef.so" in
|
||||||
|
best 3 (fun () ->
|
||||||
|
ignore
|
||||||
|
(Flan.Build.shared
|
||||||
|
~opts:{ Flan.Build.default with dev = true } ~ir ~out ()))
|
||||||
|
|
||||||
|
let () =
|
||||||
|
Printf.printf
|
||||||
|
"n check-gen check-mono emit-1 emit-N build-1 build-N fns\n";
|
||||||
|
(try
|
||||||
|
for n = 1 to 8 do
|
||||||
|
let gpath = Filename.concat dir (Printf.sprintf "gen%d.flan" n) in
|
||||||
|
let mpath = Filename.concat dir (Printf.sprintf "mono%d.flan" n) in
|
||||||
|
write gpath (generic_src n);
|
||||||
|
write mpath (mono_src n);
|
||||||
|
let gd = decls_of gpath and md = decls_of mpath in
|
||||||
|
let check_gen = best 3 (fun () -> ignore (Flan.Check.program_with_env gd)) in
|
||||||
|
let check_mono = best 3 (fun () -> ignore (Flan.Check.program_with_env md)) in
|
||||||
|
let p, _ = Flan.Check.program_with_env gd in
|
||||||
|
let insts = instantiations p in
|
||||||
|
let one = [ List.hd insts ] in
|
||||||
|
let ir_one =
|
||||||
|
Flan.Emit.redefinition ~dev:true ~known:(fun _ -> true) p ~fns:one
|
||||||
|
in
|
||||||
|
let ir_all =
|
||||||
|
Flan.Emit.redefinition ~dev:true ~known:(fun _ -> true) p ~fns:insts
|
||||||
|
in
|
||||||
|
let emit1 =
|
||||||
|
best 3 (fun () ->
|
||||||
|
ignore (Flan.Emit.redefinition ~dev:true ~known:(fun _ -> true) p ~fns:one))
|
||||||
|
and emitn =
|
||||||
|
best 3 (fun () ->
|
||||||
|
ignore (Flan.Emit.redefinition ~dev:true ~known:(fun _ -> true) p ~fns:insts))
|
||||||
|
in
|
||||||
|
let b1 = build_ms ir_one and bn = build_ms ir_all in
|
||||||
|
Printf.printf "%d %8.1f %10.1f %7.1f %7.1f %8.1f %8.1f %d\n%!"
|
||||||
|
n check_gen check_mono emit1 emitn b1 bn (List.length insts)
|
||||||
|
done
|
||||||
|
with Flan.Loc.Error d -> prerr_endline (Flan.Loc.report d); exit 1);
|
||||||
|
(* And what the session actually does when the generic itself is redefined.
|
||||||
|
This is the real C-c C-c path — Session.eval on the form the editor sent
|
||||||
|
— and what it reports is the finding, not the timing. *)
|
||||||
|
let gpath = Filename.concat dir "gen4.flan" in
|
||||||
|
let t, _ = Flan.Session.create ~file:gpath () in
|
||||||
|
let form =
|
||||||
|
"(defn gswap [xs [$t] i i32 j i32] ()\n\
|
||||||
|
\ (let [tmp (at xs i)]\n\
|
||||||
|
\ (set (at xs i) (at xs j))\n\
|
||||||
|
\ (set (at xs j) tmp)))\n"
|
||||||
|
in
|
||||||
|
let c, ms = time (fun () -> Flan.Session.eval ~origin:gpath t form) in
|
||||||
|
Printf.printf
|
||||||
|
"\nSession.eval on the generic gswap itself: %.1f ms, installs=%b, \
|
||||||
|
fns=[%s], names=[%s]\n"
|
||||||
|
ms c.Flan.Session.installs
|
||||||
|
(String.concat " " c.Flan.Session.fns)
|
||||||
|
(String.concat " " c.Flan.Session.names)
|
||||||
51
spike/generics/prelude-shapes.flan
Normal file
51
spike/generics/prelude-shapes.flan
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
;; Which of prelude.ml's per-type families collapse as they are written, and
|
||||||
|
;; which need their signature changed. Nothing here is installed in the
|
||||||
|
;; prelude; it is the same bodies, over $t, checked and run.
|
||||||
|
|
||||||
|
(defn keep [s [$t] keep? (Fn [$t] bool)] (Vec $t)
|
||||||
|
(let [v (vec-new t)]
|
||||||
|
(dotimes [i (len s)]
|
||||||
|
(when (keep? (at s i))
|
||||||
|
(push v (at s i))))
|
||||||
|
v))
|
||||||
|
|
||||||
|
(defn apply! [s [$t] f (Fn [$t] $t)] ()
|
||||||
|
(dotimes [i (len s)]
|
||||||
|
(set (at s i) (f (at s i)))))
|
||||||
|
|
||||||
|
(defn fold [s [$t] init $t f (Fn [$t $t] $t)] t
|
||||||
|
(let [acc init]
|
||||||
|
(dotimes [i (len s)]
|
||||||
|
(set acc (f acc (at s i))))
|
||||||
|
acc))
|
||||||
|
|
||||||
|
(defn flip! [s [$t]] ()
|
||||||
|
(let [i 0
|
||||||
|
j (- (len s) 1)]
|
||||||
|
(while (< i j)
|
||||||
|
(let [tmp (at s i)]
|
||||||
|
(set (at s i) (at s j))
|
||||||
|
(set (at s j) tmp))
|
||||||
|
(set i (+ i 1))
|
||||||
|
(set j (- j 1)))))
|
||||||
|
|
||||||
|
(defvar ns [5 i32])
|
||||||
|
(defvar fs [5 f32])
|
||||||
|
|
||||||
|
(defn main [] ()
|
||||||
|
(let [xs (slice ns 0 5)
|
||||||
|
ys (slice fs 0 5)]
|
||||||
|
(dotimes [i 5]
|
||||||
|
(set (at xs i) (+ i 1))
|
||||||
|
(set (at ys i) (f32 (* 2 (+ i 1)))))
|
||||||
|
(apply! xs (fn [x] (* x 10)))
|
||||||
|
(apply! ys (fn [x] (* x (f32 2))))
|
||||||
|
(flip! xs)
|
||||||
|
(flip! ys)
|
||||||
|
(println (fold xs 0 (fn [a b] (+ a b))))
|
||||||
|
(println (fold ys (f32 0) (fn [a b] (+ a b))))
|
||||||
|
(let [evens (keep xs (fn [x] (= (% x 20) 0)))]
|
||||||
|
(println (len evens))
|
||||||
|
(free evens))
|
||||||
|
(println (at xs 0))
|
||||||
|
(println (at ys 0))))
|
||||||
28
spike/generics/run.sh
Normal file
28
spike/generics/run.sh
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# The generics spike's measurement, driven by hand with ocamlfind against the
|
||||||
|
# flan.cmxa dune already builds — the same arrangement spike/backend uses, and
|
||||||
|
# for the same reason: nothing under spike/ is wired into the build, there is
|
||||||
|
# no dune file here, and `dune test --root .` cannot see any of it.
|
||||||
|
#
|
||||||
|
# The three .flan programs beside this file are run with the ordinary driver:
|
||||||
|
# dune exec --root . bin/main.exe -- run spike/generics/sort.flan
|
||||||
|
set -u
|
||||||
|
here=$(cd "$(dirname "$0")" && pwd)
|
||||||
|
root=$(cd "$here/../.." && pwd)
|
||||||
|
cd "$root" || exit 1
|
||||||
|
|
||||||
|
dune build --root . lib/flan.cmxa 2>&1 | head -20
|
||||||
|
|
||||||
|
out=$(mktemp -d); trap 'rm -rf "$out"' EXIT
|
||||||
|
|
||||||
|
ocamlfind ocamlopt -thread -package unix,threads.posix -linkpkg \
|
||||||
|
-I "$root/_build/default/lib/.flan.objs/byte" \
|
||||||
|
-I "$root/_build/default/lib/.flan.objs/native" \
|
||||||
|
-I "$out" -I "$here" \
|
||||||
|
-o "$out/measure" \
|
||||||
|
"$root/_build/default/lib/flan.cmxa" \
|
||||||
|
-cclib -rdynamic -ccopt -L"$root/_build/default/lib" \
|
||||||
|
"$here/measure.ml" 2>&1 | head -40
|
||||||
|
|
||||||
|
test -x "$out/measure" || { echo "build failed"; exit 1; }
|
||||||
|
"$out/measure"
|
||||||
Loading…
x
Reference in New Issue
Block a user