ocaml/ocaml — pull request #109 by vbrankov (2014-10-24 19:25:28+00:00)
Title: Lazily box float and int references

An argument follows that the penalty for early boxing of floats and ints is bigger than lazy boxing. It is supported by a benchmark of a production application.

`Cmmgen.transl_unbox_let` will unbox a float reference if it only exists in:
1. Left side of assignments: `x := `
2. Its value is read in an operation, because operations accept unboxed values: `!x +. 5.`

Unboxing a float reference means that the variable will contain the very value of float. This is okay since the variable will never end up in OCaml heap. This gives a significant speedup since assigning it a value will never result in memory allocation. For instance in `s := !s +. a *. b`, the aggregate cost of allocating and cleaning a float will be considerable compared to the cost of computing the right side.

Unboxing will not occur if the value is read but not in an operation. This is done in language constructs that accept only boxed references. Probably the most frequent situations are:
1. Using the value as an argument to a call: `let y = foo !x in ...`. Call to "float" native functions are excluded (`exp, sin, cos`), because they accept unboxed floats.
2. Assigning the value to a variable: `let y = !x in`
3. Returning the value:
```ocaml
let foo () =
let s = ref 0. in
...
!s
```

I'm advocating the approach of lazy boxing, which means that float references are always unboxed. For the language constructs which accept boxed floats, the value is boxed in place. This is advantageous to early boxing: more likely is that assignments will be much more frequent than such language constructs. In the unlikely situation where this approach is indeed slower because of frequent boxing, it is much easier to write a workaround than with the current compiler:

```ocaml
let s = ref 0. in
...
let s_value = !s in
while true do
foo s_value (* instead of !s *)
...
```

I've seen a lot of slow code because the compiler chooses not to unbox a reference due to a single call or assignment. Here's an example illustrating it:

> https://github.com/vbrankov/ocaml-demos/blob/master/lazy-boxing/test.ml

A benchmark of a production application shows speed improvement and less garbage:

Name |Time/Run | mWd/Run | mjWd/Run | Prom/Run | Percentage
--------|-------------|-------------|----------------|--------------|---
no patch| 5.12s | 1.71Gw | 2.55Mw | 2.32Mw | 100.00%
with patch | 5.01s | 1.61Gw | 2.45Mw | 2.22Mw | 100.00%