Paul King created GROOVY-11947:
----------------------------------
Summary: Here you go: Title: Add timing utility methods (timed,
timedNanos) and Timed record to groovy-jdk
Key: GROOVY-11947
URL: https://issues.apache.org/jira/browse/GROOVY-11947
Project: Groovy
Issue Type: New Feature
Reporter: Paul King
h3. Summary
Add a small set of timing utilities to the groovy-jdk as extension methods,
providing a convenient way to measure the elapsed time of a block of code
without the caller having to manually record start/end times. Include a
{{Timed}} record type for returning a result together with its elapsed duration.
h3. Motivation
Timing a block of code is one of the most common ad-hoc measurements developers
perform. The current idiom in Groovy is verbose and error-prone:
{code}
long start = System.nanoTime()
def result = computeSomething()
long elapsedNanos = System.nanoTime() - start
{code}
Developers frequently reach for {{System.currentTimeMillis()}} here, which is
the wrong tool - wall-clock time is not guaranteed to be monotonic and can
produce negative or wildly incorrect durations when the system clock is
adjusted (NTP sync, DST, manual change). A built-in utility that uses
{{System.nanoTime()}} correctly by default steers users toward the right
primitive.
h3. Proposed API
Three additions to the groovy-jdk:
{{long timedNanos(Closure c)}} - runs the closure and returns the elapsed time
in nanoseconds. Does not return the closure's result.
{{long timedMillis(Closure c)}} - same as above but returns milliseconds
(computed from nanoTime, not currentTimeMillis, so still monotonic).
{{<T> Timed<T> timed(Closure<T> c)}} - runs the closure and returns a {{Timed}}
record containing both the result and the elapsed nanoseconds.
The {{Timed<T>}} type would be a record with:
{{T result}} - the value returned by the closure
{{long nanos}} - elapsed time in nanoseconds
{{Duration getDuration()}} - convenience accessor returning
{{Duration.ofNanos(nanos)}}
{{long getMillis()}} - convenience accessor returning {{nanos / 1_000_000}}
h3. Example Usage
{code}
// Just time it
long elapsed = timedNanos { expensiveOperation() }
println "took ${elapsed}ns"
// Time it and keep the result
def t = timed { computeReport() }
println "report generated in ${t.millis}ms"
processReport(t.result)
// Use the Duration accessor for human-friendly output
println "took ${timed { loadData() }.duration}"
{code}
h3. Implementation Notes
All three methods must use {{System.nanoTime()}}, not
{{System.currentTimeMillis()}}. This is the whole point of the utility.
Exceptions thrown by the closure should propagate unchanged - the utility does
not swallow or transform them. Timing information is lost in the exception
case; if users need timing on failure they can use a try/finally around
{{timedNanos}} themselves.
The closure runs synchronously on the calling thread. If the closure submits
asynchronous work without waiting for it, only the submission time is measured.
This should be documented clearly.
{{long}} subtraction of two {{nanoTime}} readings handles wraparound correctly
as long as the interval is under ~292 years, so no special handling is needed.
h3. Location
These are best placed as extension methods in {{DefaultGroovyMethods}} or a
dedicated timing extension module. The {{Timed}} record would live alongside
them.
h3. Out of Scope
Benchmarking utilities (warmup, statistical measurement, JIT handling) - users
who need this should use JMH.
Async / reactive timing helpers - a separate concern, possibly a future
enhancement.
CPU time measurement - {{ThreadMXBean.getCurrentThreadCpuTime()}} has different
semantics and should not be conflated with wall-clock elapsed time.
h3. Related
Complements the existing time-related sugar in groovy-jdk (e.g.
{{use(TimeCategory)}}, {{Date}} extensions) by adding a primitive for the
measurement case specifically.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)