elm-benchmark currently needs about 40 lines of Native code to wrap the JavaScript performance API. We can't use Time.now because we need to minimize benchmark-irrelevant instructions to get accurate measurements.
Since elm-benchmark is the only place in the Elm ecosystem that wraps the performance API, I'm getting feature requests to add timing information for single function runs.
https://github.com/BrianHicks/elm-benchmark/issues/22 is the most recent, but I get requests on Slack and offline.
I intend to upgrade elm-benchmark to 0.19 and the new Kernel format at soon as possible, in whatever format that takes. However, it sounds like Kernel code should do the minimum possible, and a benchmarking library isn't exactly the minimum possible.
So given these facts, I propose that we extract the timing code to a new function: time : (() -> a) -> Task Error (Time.Time, a). "Time" is used here as a verb, but if that's unclear then "measure" or "timeExecution" may be alternatives. This defeats the drifting problems with the earlier proposal, since the returned time value is always a delta instead of an absolute value.
Where would this live? I suggest (in order of my preference):
- A new Performance module which can eventually grow to wrap the rest of the JavaScript performance API (in particular setting timing points, which would be really useful for benchmarking.)
- Core's Time module
- Core's Debug module (However, in elm-benchmark's case we run without the debugger to reduce overhead.)
The impact for elm-benchmark would be rewriting the benchmarking logic in terms of List.map. This will be a little slower, but I don't think it's a problem. Consistency matters more than absolute speed, and it's already pretty clear that measurements taken in elm-benchmark 1.x won't be directly comparable to those from 2.x. This may cause a little heartburn since people won't be able to compare their 0.18 code to 0.19, but I think in the long term it's better this way.
Alternative signatures, and why I think they're inferior:
- time : Int -> (() -> a) -> Task Error Time, where Int is the number of executions to run: this is exactly what I need for elm-benchmark, but it's too specific to be useful generally.
- time : (a -> b) -> Task Error (Time.Time, b): we don't know how to generate values of a, which would mean users having to write too much code.
- time : (() -> a) -> Task Error Time.Time: could possibly work, but doesn't solve for the actual feature request I keep getting (people can unblock by using Time.now, but it's inaccurate for reasons described above.)
Out of scope for this particular proposal, but useful:
- time : Task x a -> Task x (Time.Time, a). How long did that XHR take? I don't want to add this capability to elm-benchmark (too many uncontrollable variables to get accurate results) but I could see it being really useful for a production app reporting average latencies, etc back to the mothership.
Brian