diff options
| author | Guillaume Melquiond | 2020-10-02 08:16:05 +0200 |
|---|---|---|
| committer | Guillaume Melquiond | 2020-11-13 15:14:35 +0100 |
| commit | 5c7f63fa7a88cf2cb9b6837eb2797268c5843030 (patch) | |
| tree | 044370440e0ead2f2ee95fc161fb1684308271ff | |
| parent | c9cf844e7bdb4eb8bf1b7259f9edd6116629d201 (diff) | |
Add allocation-free variants of the nextup and nextdown floating-point operations.
Floating-point values are boxed, which means that any operation causes an
allocation. While short-lived, they nonetheless cause the minor heap to
fill, which in turn triggers the garbage collector.
To reduce the number of allocations, I initially went with a shadow stack
mechanism for storing floating-point values. But assuming the CoqInterval
library is representative, this was way too complicated in practice, as
most stack-located values ended up being passed to nextdown and nextup
before being stored in memory.
So, this commit implements a different mechanism. Variants of nextdown and
nextup are added, which reuse the allocation of their input argument.
Obviously, this is only correct if there is no other reference to this
argument. To ensure this property, the commit only uses these opcode
during a peephole optimization. If two primitive operations follow one
another, then the second one can reuse the allocation of the first one,
since it never had time to even reach the stack.
For CoqInterval, this divides the number of allocations due to
floating-point operations by about two.
The following snippet is made 4% faster by this commit (and 13% faster if
we consider only the floating-point operations).
From Coq Require Import Int63 BinPos PrimFloat.
Definition foo n :=
let eps := sub (next_up one) one in
Pos.iter (fun x => next_down (add x eps)) two n.
Time Eval vm_compute in foo 100000000.
| -rw-r--r-- | kernel/byterun/coq_interp.c | 14 | ||||
| -rw-r--r-- | kernel/genOpcodeFiles.ml | 2 | ||||
| -rw-r--r-- | kernel/vmemitcodes.ml | 15 |
3 files changed, 31 insertions, 0 deletions
diff --git a/kernel/byterun/coq_interp.c b/kernel/byterun/coq_interp.c index 0f7713a6bb..3c3f45f7e6 100644 --- a/kernel/byterun/coq_interp.c +++ b/kernel/byterun/coq_interp.c @@ -1753,6 +1753,20 @@ value coq_interprete Next; } + Instruct (CHECKNEXTUPFLOATINPLACE) { + print_instr("CHECKNEXTUPFLOATINPLACE"); + CheckFloat1(); + Store_double_val(accu, nextafter(Double_val(accu), INFINITY)); + Next; + } + + Instruct (CHECKNEXTDOWNFLOATINPLACE) { + print_instr("CHECKNEXTDOWNFLOATINPLACE"); + CheckFloat1(); + Store_double_val(accu, nextafter(Double_val(accu), -INFINITY)); + Next; + } + Instruct(CHECKCAMLCALL2_1) { // arity-2 callback, the last argument can be an accumulator value arg; diff --git a/kernel/genOpcodeFiles.ml b/kernel/genOpcodeFiles.ml index 63abb50cc2..dc2cd349ce 100644 --- a/kernel/genOpcodeFiles.ml +++ b/kernel/genOpcodeFiles.ml @@ -147,6 +147,8 @@ let opcodes = "CHECKLDSHIFTEXP"; "CHECKNEXTUPFLOAT"; "CHECKNEXTDOWNFLOAT"; + "CHECKNEXTUPFLOATINPLACE"; + "CHECKNEXTDOWNFLOATINPLACE"; "CHECKCAMLCALL2_1"; "CHECKCAMLCALL1"; "CHECKCAMLCALL2"; diff --git a/kernel/vmemitcodes.ml b/kernel/vmemitcodes.ml index 592bdf775f..c1d8fcb855 100644 --- a/kernel/vmemitcodes.ml +++ b/kernel/vmemitcodes.ml @@ -257,6 +257,15 @@ let check_prim_op = function | Arraydefault | Arraycopy | Arraylength -> opCHECKCAMLCALL1 +let inplace_prim_op = function + | Float64next_up | Float64next_down -> true + | _ -> false + +let check_prim_op_inplace = function + | Float64next_up -> opCHECKNEXTUPFLOATINPLACE + | Float64next_down -> opCHECKNEXTDOWNFLOATINPLACE + | _ -> assert false + let emit_instr env = function | Klabel lbl -> define_label env lbl | Kacc n -> @@ -392,6 +401,12 @@ let rec emit env insns remaining = match insns with out env opRETURN; out_int env n; emit env c remaining | Ksequence c1 :: c -> emit env c1 (c :: remaining) + | Kprim (op1, (q1, _)) :: Kprim (op2, (q2, _)) :: c when inplace_prim_op op2 -> + out env (check_prim_op op1); + slot_for_getglobal env q1; + out env (check_prim_op_inplace op2); + slot_for_getglobal env q2; + emit env c remaining (* Default case *) | instr :: c -> emit_instr env instr; emit env c remaining |
