Direct answer
Choose the clock only after defining the quantity. User-observed elapsed latency normally means time from a named user-action boundary to a named result boundary, including scheduling and any sleep that occurred. Active device uptime excludes deep sleep. CPU time measures processor time consumed by a thread, not the user waiting interval. A benchmark that calls all three values “duration” can produce internally plausible but incomparable numbers. Record the metric meaning, start and end events, unit, clock API, and whether the interval is expected to include device sleep. For general elapsed intervals on Android, SystemClock.elapsedRealtimeNanos() is monotonic since boot and includes deep sleep. It is a strong default when the question is how much real elapsed device time passed, including suspension. SystemClock.uptimeMillis() is also monotonic since boot but excludes deep sleep, so it answers a narrower active-uptime question. The APIs have different units; convert only after taking a difference in the same clock domain. Do not subtract an uptime reading from an elapsed-realtime reading or label the result with a shared epoch. System.currentTimeMillis() is wall-clock time. It is useful for a human-readable timestamp or correlation with external logs, but it can change because the wall clock is corrected or set. It should not be the elapsed benchmark interval source: a clock jump can make an interval too large, negative, or otherwise misleading. If a report needs both an event date and a stable duration, store a wall timestamp and a monotonic duration as separate fields, each with its own meaning. Never silently “repair” a negative wall-clock difference into a performance result. System.nanoTime() has an arbitrary origin and is intended for measuring elapsed intervals by subtracting values from the same Java virtual-machine instance. Its absolute reading is not a calendar time and must not be persisted as a portable timestamp. Android documents nanoTime as based on uptimeMillis and therefore not counting deep sleep; elapsedRealtimeNanos is the sleep-inclusive alternative. Java documents nanosecond precision but does not guarantee nanosecond resolution. Keep nanoTime endpoints in the same JVM instance; do not compare raw values across processes, devices, boots, or independently chosen clock origins. If measurement spans process boundaries, choose a shared documented Android clock and record both readings under that exact API. Thread CPU time is another metric, not a faster wall clock. SystemClock.currentThreadTimeMillis() reports CPU time for the current thread. It can help explain compute consumed by that thread, but excludes waiting and does not account for work performed on other runtime threads or native workers. A local inference task may dispatch work away from the calling thread, so one thread’s CPU time is not total inference cost. Use it only when the question explicitly asks for that thread’s CPU consumption and the scope is valid. Make event boundaries part of the data contract. For model loading, decide whether “start” is request acceptance, file access, runtime initialization, or a later readiness point. For inference, distinguish invocation, first output, accepted terminal result, and cleanup. Select a clock once, take both endpoints from it, preserve the raw unit or clearly defined conversion, and do not combine intervals measured with different sources. Keep cold-start, warm-task, and user-visible latency as separate measurements if they answer different questions. Sleep and background tests expose mistaken assumptions. If the measured operation can outlive screen-off or process handoff, test a controlled sleep interval and verify whether the chosen metric should grow. A sleep-inclusive elapsed duration should grow; uptime and nanoTime intervals intentionally will not include deep sleep. Wall-clock adjustments should not alter a monotonic interval. Thread CPU time should remain about computation rather than idle waiting, subject to platform scheduling and measurement noise. These are semantic checks, not promised performance values. Finally, do not compare monotonic timestamps across boot sessions. Raw values are meaningful only for differences in the same documented clock domain and lifetime. Persist a duration with its unit and source, or persist a separate wall timestamp when chronology is needed. Group observations only within one boot/session when the test harness already provides a trustworthy session label; do not carry comparisons across boots or invent a boot identifier solely to make unrelated clock origins comparable. Reject cross-boot subtraction. This article is a generic Android measurement protocol: it contains no measured Cove latency, CPU use, thermal effect, or model speed claim.
A screen-off interval makes two latency reports disagree
One benchmark records inference boundaries with uptimeMillis while a second uses a wall timestamp. A device sleeps during one run and the wall clock is adjusted during another, producing durations that appear contradictory.
Decision
Define whether the metric is sleep-inclusive user elapsed time, active uptime, or thread CPU consumption, then capture both endpoints from one documented monotonic clock domain.
Expected outcome
The team can explain why the values differ, preserve dates separately from durations, and compare only runs with compatible boundaries, units, and clock semantics.
Evidence to collect
The claim becomes reviewable only when the following evidence is attached to the same artifact and test run:
- Clock-domain and lifetime identity
- Wall-change and sleep test fixtures
- Consistent interval units and missing-data states
A release claim is valid only for the recorded configuration and acceptance set. Generalization requires additional records.
Implementation workflow
- Name the quantity and boundaries. Decide whether the number represents user-observed elapsed latency, active uptime, or CPU time. Mark exact start and end events for load, first output, terminal result, and cleanup rather than using a vague “inference duration.”
- Select a matching Android clock. Use elapsedRealtimeNanos for monotonic elapsed time that includes deep sleep, uptimeMillis only when sleep should be excluded, and thread CPU time only for that thread’s computation. Keep wall time for calendar correlation.
- Capture same-domain endpoints. Read both start and end from the same API and preserve the source and unit. Never subtract elapsed realtime, uptime, wall time, and arbitrary-origin nanoTime readings from one another.
- Exercise sleep and wall-clock changes. Test the measurement contract with a controlled deep-sleep interval and a wall-clock correction where practical. Confirm the expected semantic behavior without treating the test as a latency result.
- Keep process and boot boundaries explicit. Do not compare raw monotonic values across devices or boots. If a measurement crosses processes, use one documented shared Android timebase and retain context; otherwise keep endpoints within one process.
- Store and analyze compatible records. Persist duration, unit, clock API, event definitions, process or boot grouping, workload, and outcome. Keep wall timestamps separate, reject incompatible pairs, and distinguish task latency from CPU consumption.
Keep each transition observable. A failure should identify the stage, artifact, runtime, and recovery action without logging private user content.
Acceptance criteria
- Every duration identifies its intended meaning, exact event boundaries, clock API, and unit.
- Both endpoints come from the same clock domain; no raw subtraction crosses wall time, uptime, elapsed realtime, nanoTime origins, processes without a shared contract, or boot sessions.
- Reports distinguish sleep-inclusive elapsed latency, active uptime, calendar timestamps, and thread CPU time instead of combining them as one metric.
Decision table
| Dimension | Choice or evidence | How to interpret it |
|---|---|---|
| elapsedRealtimeNanos | Use for monotonic elapsed duration when deep sleep should count. | Since-boot time includes deep sleep; record both endpoints in this source and keep the nanosecond unit clear. |
| uptimeMillis | Use only when the intended interval excludes deep sleep. | Monotonic since-boot uptime does not grow during deep sleep; it is not interchangeable with sleep-inclusive user elapsed time. |
| currentTimeMillis | Use for human-readable wall timestamps or external-log correlation. | Wall time can be corrected or set, so it is not a stable elapsed-interval source. |
| currentThreadTimeMillis / nanoTime | Use thread CPU time for that thread’s consumption; use nanoTime differences only within the same JVM instance. | Neither is a substitute for general sleep-inclusive user latency; nanoTime has nanosecond precision but not a guaranteed nanosecond resolution, and raw values are not portable timestamps. |
Failure patterns to prevent
- Subtracting different clock domains
- Calling uptime a CPU-work measurement
Also prevent silent fallback, unpinned artifacts, missing cancellation, and conclusions that combine unlike configurations. Store unsuccessful runs alongside successful ones.
Minimum reproducibility record
| Layer | Record |
|---|---|
| Device | Manufacturer, model, chipset, RAM class, operating-system build |
| Software | Application version and git commit |
| Model | Family, variant, revision, format, file length, hash, quantization |
| Runtime | Name, revision, requested backend, observed backend evidence |
| Workload | Fixture revision, input hash, prompt hash, output policy |
| Outcome | Completed, failed, cancelled, fallback, and privacy-safe diagnostics |
Release checklist
- ☐ The primary query is answered without an unsupported number.
- ☐ Every artifact and runtime is pinned.
- ☐ The representative task and failure policy are explicit.
- ☐ Lifecycle, cancellation, cleanup, and fallback are tested.
- ☐ User-content and network boundaries are documented.
- ☐ Result wording applies only to the recorded configuration.
- ☐ The page links to raw method or evidence when results are added.
Sources and related evidence
- Cove benchmark method
- Android Developers: SystemClock reference
- Android Developers: Java System.nanoTime reference
Chinese deployment and troubleshooting content is organized in the .奇连 AI 端侧专题.
Frequently asked questions
Which clock should measure a user-visible inference delay?
First define its boundaries. If the intent is elapsed time that includes device sleep, elapsedRealtimeNanos is the Android monotonic option documented for that purpose. Use one source for both endpoints and keep CPU time as a separate metric.
Can I save System.nanoTime and compare it after a process restart?
Do not treat its arbitrary-origin reading as a persistent timestamp. Take differences within the same documented context; for cross-process Android timing, choose a shared documented clock and record that choice rather than assuming origins match.
Why is currentThreadTimeMillis lower than task latency?
It measures CPU time for the current thread, not how long the user waited. Waiting, scheduling, sleep, and work on other threads or native workers are outside that single-thread number.