os.clock() versus os.time() for measuring CPU-bound versus I/O-bound bottlenecks in Lua
0 reputation · 14 Apr 2022, 10:14 UTC
0 reputation · 14 Apr 2022, 10:14 UTC
When profiling a Lua routine to determine whether latency stems from CPU computation or external I/O, the standard library offers two timing primitives: os.clock() and os.time(). The objective is to select a timer that accurately reflects the observed delay without relying on non-portable extensions. os.clock() reports processor time consumed by the program, excluding time spent blocked on I/O, network, or system calls. os.time() returns wall-clock time but with one-second granularity, rendering it unsuitable for fine-grained profiling. Neither function is documented as monotonic, and the Lua standard does not guarantee sub-second wall-clock resolution. This creates a fundamental trade-off: using os.clock() may underrepresent I/O-bound latency, while os.time() lacks the resolution to capture short bottlenecks. Developers must therefore choose between CPU-time accuracy and wall-clock relevance within the constraints of standard Lua.
Which timer most accurately reflects true wait time for an I/O-bound section? Can os.clock() be interpreted to approximate wall-clock latency without OS-specific calls? What documented approach captures sub-second latency portably in standard Lua?
35775 reputation · 14 Apr 2022, 19:19 UTC
For an I/O-bound section, neither os.clock() nor os.time() accurately reflects true wait time. os.clock() measures only CPU time consumed by the process and does not advance while blocked on I/O. os.time() returns wall-clock time but only with one-second granularity, so any latency under a second rounds to zero or one second. Standard Lua (5.1 through 5.4) provides no portable, sub-second wall-clock timer.
os.clock() Cannot Approximate Wall-Clock Latencyos.clock() and os.time() have different epochs, units, and monotonicity guarantees. Subtracting one from the other yields a meaningless value. On Unix, os.clock() typically returns seconds with sub-second precision (via clock_gettime(CLOCK_PROCESS_CPUTIME_ID) or times()); on Windows it uses GetProcessTimes and may return centiseconds. In both cases the clock stops ticking during a blocking read(), select(), or network request. Therefore an I/O-bound routine that waits 500 ms will show near-zero os.clock() elapsed time.
There is none. The Lua reference manual up to 5.4 documents only os.time() (whole seconds since epoch) and os.clock() (approximate CPU time). No standard function offers monotonic, sub-second wall-clock time. To measure I/O latency portably you must use an external library:
socket.gettime() returns fractional seconds since epoch (monotonic on most platforms).uv.hrtime() gives nanosecond-resolution monotonic time.clock_gettime(CLOCK_MONOTONIC) or QueryPerformanceCounter directly.When you cannot add dependencies, combine the two standard timers to infer wait time, accepting the one-second floor:
local cpu_start = os.clock()
local wall_start = os.time()
-- I/O-bound operation
local cpu_elapsed = os.clock() - cpu_start
local wall_elapsed = os.time() - wall_start
-- wait_time ≈ wall_elapsed - cpu_elapsed (but wall_elapsed has ±1 s error)
This only works for operations lasting several seconds. For anything shorter, the wall-clock measurement is too coarse.
for i=1,1e7 do end) and measure with os.clock(); it should report a non-zero fraction of a second.socket.select(nil, nil, 0.5) with LuaSocket) and measure with os.clock(); it should report near-zero while os.time() may advance by 0 or 1 second.os.clock and os.time to confirm documented resolution and caveats.This answer assumes standard Lua 5.1–5.4 without LuaJIT extensions. If you are using LuaJIT, os.clock() may have higher resolution on some platforms, but it still measures CPU time only. Embedded targets may have even coarser os.clock() resolution (e.g., 1/60 s) or return 0. Always verify on your deployment platform.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.