You benchmark a frame-differencing pipeline on an embedded vision controller. A stopwatch says the run takes 30 seconds; the timing code, built on the C clock() function, reports about 15. The pipeline did not get faster — the two numbers measure different things. This explainer covers what CPU time and wall-clock time each mean, why vision pipelines diverge between them most of all, how to measure both in C, C++ and Python, and how to turn the measurements into frames-per-second numbers you can trust when you compare a CPU implementation against a GPU one, or when you size a vision controller for a camera line.
What clock() actually measures
The C standard defines clock() as returning the processor time used by the program, divided into CLOCKS_PER_SEC units. Processor time is the accumulation of the intervals during which the operating system actually scheduled your process onto a CPU core. It is a resource-use counter, like kilowatt-hours on a meter, not a chronometer.
Everything your program does while not running on a core is invisible to clock():
- Sleeping, explicitly or waiting on a condition variable
- Blocking on file and disk I/O, including reading video from storage
- Waiting on a hardware video decoder or a camera's frame delivery
- Work delegated to the GPU, a DSP or another process
The simplest proof needs no camera at all:
A program that sleeps for ten seconds reports close to zero from clock(). A program that runs flat-out on one core with no blocking reports the full elapsed time. Real vision pipelines sit between the two, which is exactly why a 30-second run can report half of that.
Why vision pipelines diverge the most
Video processing is unusually rich in non-CPU time, so the gap between CPU time and wall-clock time is larger than in a typical compute kernel:
- Decode. Reading an MP4 file involves demuxing, and on many platforms part of the decode work happens in hardware blocks or in a separate media process. The CPU waits.
-
Display.
imshowandwaitKeyhand frames to a windowing system and block until it is done with them. - I/O stalls. Storage reads, memory-controller contention with camera acquisition, and network frame arrival from a GigE camera all consume wall-clock time without billing CPU time to your process.
-
GPU work. When you move the blur and threshold to CUDA,
clock()around the CPU side shrinks further, because the CPU now spends its time waiting on the device. A naive CPU-versus-GPU comparison built onclock()will overstate the GPU speedup or, if the calls are asynchronous, hide it entirely. - **Multitasking." Even pure CPU code loses time when the scheduler preempts your process for other work; the preempted intervals count as wall-clock time but not CPU time.
Measuring wall-clock time correctly
Use a monotonic clock. Monotonic means it only moves forward and is unaffected by system time corrections, so a time adjustment during a long benchmark cannot produce a negative interval.
In C or C++ on Linux:
gettimeofday() also works and is what most legacy examples use, but it reads the real-time clock, which an administrator or NTP can step backward. For benchmarks, prefer CLOCK_MONOTONIC; for per-frame timestamps that must survive across reboots, use CLOCK_MONOTONIC_RAW or the system real-time clock depending on what you correlate against. On Windows, QueryPerformanceCounter is the equivalent monotonic source; in Python, time.perf_counter() is monotonic by contract.
For a deeper treatment of timing measurements on control hardware, this PLC benchmarking method for CPU, I/O and scan time covers the same distinction for controller scan budgets.
A worked example: the 1999-frame run
Take the frame-differencing case from the opening scenario. The stopwatch says 30 s for 1999 frames; clock() says about 15 s.
Wall-clock throughput:
The pipeline spends 7.5 ms per frame of real CPU work and another 7.5 ms per frame blocked — on decode, on the display path in imshow, on storage. The diagnosis follows directly: either remove the blocking (decode to raw frames in advance, write results instead of displaying them) or overlap it (double-buffer, a decode thread feeding a processing thread). The two numbers together tell you which fix applies; either number alone does not.
If you need to know how many pixels a camera delivers and at what resolution to match that 66 fps budget, the camera resolution calculator turns a field of view and a defect size into a pixel count.
Turning timestamps into trustworthy FPS
A single start-to-stop average hides jitter, and jitter is what drops parts on a conveyor. Instrument per frame:
- Record a
CLOCK_MONOTONICtimestamp with each frame as it enters processing. - Compute per-frame intervals as consecutive timestamp differences.
- Report the mean, the 99th percentile and the maximum interval, plus a count of dropped or late frames.
- Repeat the run at least three times and compare medians, not single runs.
A pipeline averaging 66 fps whose worst interval is 200 ms still misses a part passing every 40 ms. Related symptoms show up in articles we already publish: if frames arrive at uneven intervals from the camera itself, the diagnosis in GigE Vision frame arrival jitter: isolate camera and host timing applies; if the host, not the camera, is starving the pipeline, start with camera acquisition high CPU usage: replace busy waiting. When color conversion limits the frame rate, see RGB camera frame rate limits: pixel format and exposure.
Choosing hardware with both numbers
CPU time tells you how much processor a pipeline needs; wall-clock time tells you whether a given processor delivers the frame rate. Both matter when you size a system:
- A smart camera such as the LS-SMC-0063 runs the whole pipeline on-device, so its embedded processor must cover the CPU-time budget at your target frame rate with margin for the on-board lighting and communication tasks.
- A PC-based camera like the MV-CS200-10UM or the LS-CMR-0166 shifts the CPU-time budget onto a host, so the host must be sized for the sum of all cameras, not one.
- A vision controller such as the MV-VC3301H-128G66 also services camera ports, light controller channels and PLC traffic, so reserve headroom: a utilization ratio consistently above about 0.7 per core at peak leaves too little for the acquisition and I/O your benchmark did not model.
| Specification | MV-CS200-10UM | LS-CMR-0166 | MV-VC3301H-128G66 | LS-SMC-0063 |
|---|---|---|---|---|
| Sensor model | Sony® IMX183 | Sony IMX226 | – | – |
| Pixel size | 2.4 μm × 2.4 μm | 1.85 μm | – | – |
| Sensor size | 1" | 1/1.7" | – | – |
| Resolution | 5472 × 3648 | 4000 × 3000 (12MP) | – | 1440x1080 |
| Max. frame rate | 19.2 fps @ 5472 × 3648 Mono 8 | 30 fps | – | 60 fps |
| Data interface | USB3.0, compatible with USB2.0 | USB 3.0 | – | GigE |
| Digital I/O | 6-pin P7 connector provides power and I/O, including opto-isolated input × 1 (Line 0), opto-isolated output × 1(Line 1), bi-directional non-isolated I/O × 1 (Line 2) | – | 8 digital isolated inputs supported; 16 opto-isolated outputs supported (NPN/PNP switchable) | RS232, 2 Opto-isolated inputs, 3 Opto-isolated outputs |
| Power supply | 9 VDC to 24 VDC, supports USB3.0 power supply | – | 24 VDC | DC9-26 V 2A |
| Power consumption | Typ. 2.3 W @ 5 VDC (USB3.0 provides power supply) | – | 95 W | <18 W |
| Dimension | 29 mm × 29 mm × 30 mm (1.1" × 1.1" × 1.2") | – | 161.4 mm × 208.5 mm × 105.5 mm | – |
| Weight | Approx. 80 g (0.18 lb.) | – | Approx. 2.1 kg | <350 g |
| Ingress protection | IP40 (under proper lens installation and wiring) | – | – | IP65 |
| Price | $759.00 | $560.00 | $2,419.00 | $1,110.00 |
Values as printed in each product's data sheet; open a part for its drawing and full specifications.
You can assemble a camera-plus-controller combination in the system configurator, or browse the area scan cameras finder filtered by interface and resolution.
The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.
Checks before you trust a benchmark
-
Pin the run to one core (for example with
taskset) and repeat it; variance across runs points to interference, not algorithm cost. -
Keep display out of the measurement, or measure with and without
imshowseparately so you can quantify its share of the wall-clock time. - Warm up before timing: first-frame costs include file opens, codec initialization and page cache faults that a steady-state run never pays again.
- Use the monotonic clock for intervals and the CPU clock for utilization, and label the two clearly in your results so nobody compares the wrong columns.
- Benchmark on the target hardware, with the real camera traffic or media load present; a quiet developer workstation flatters both numbers.
When the CPU-time number is the right one
There are cases where you genuinely want processor time. Sizing a processor for N cameras is a CPU-time budget: N times the per-frame CPU cost must fit into the cores available. Checking whether your OpenCV build parallelizes is a CPU-time question: if wall-clock time barely drops with four cores but CPU time stays flat, the kernel is single-threaded. Licensing a pipeline by compute is a CPU-time question. The mistake is only in reading clock() as a chronometer. Report both numbers, each labeled, and every one of these decisions gets easier.
A short checklist to carry into the next benchmark:
- Monotonic timestamps per frame, mean and worst interval reported
- CPU time recorded alongside, utilization computed as the ratio
- Display and decode either excluded or measured separately
- Multiple runs, medians compared
- Same hardware, same background load, as the production system
For help matching a measured budget to specific cameras or a controller, the contact page reaches our applications team, and volume pricing is available by quote.
Questions
Why does clock() report less time than a stopwatch shows?
clock() returns processor time: the time the operating system scheduled your process onto a CPU core. When your program sleeps, waits on disk or network, blocks on video decode hardware or hands work to the GPU, no CPU time accrues, yet real time passes. A pipeline that blocks for half its runtime therefore reports roughly half its true duration. Use a monotonic wall clock when you want elapsed time.
Is gettimeofday() a good way to measure elapsed time?
It works for rough measurements and is what many older examples use, but it reads the system real-time clock, which can step backward when an administrator or NTP corrects the time. A negative or truncated interval is possible. Prefer clock_gettime(CLOCK_MONOTONIC), which cannot go backward and is unaffected by clock adjustments. On Windows, QueryPerformanceCounter serves the same purpose.
How should I report frames per second for a camera pipeline?
Capture CLOCK_MONOTONIC timestamps at start and end, divide the frame count by the difference, and also record the minimum and maximum per-frame intervals from consecutive timestamps. The average hides jitter: a pipeline averaging 30 fps that occasionally stalls for 200 ms still drops frames on a moving conveyor. Report mean, worst-case interval and frame drops together, and repeat the run several times.
When is CPU time actually the more useful number?
CPU time is the right measure when you are comparing algorithm implementations, sizing a processor, or checking whether a codec or OpenCV build uses one core or four. Divide CPU time by wall-clock time to get average core utilization: a ratio near 1.0 on a four-core machine means three cores are idle and the pipeline is latency-bound, not compute-bound. Both numbers together tell you more than either alone.
Does running a benchmark on a shared vision controller distort results?
Yes. A vision controller also services camera GigE traffic, light controller signaling and PLC communication, so other processes compete for the same cores and memory bandwidth. Pin your benchmark to one core, keep the acquisition and display load fixed between runs, and note the controller's background load with the CPU-time ratio. Comparing a benchmark run on a quiet developer PC with one on a loaded controller is not a fair comparison.