Skip to content

Build1 publisher3 min readPublished

A whole-seconds cast zeroes the parse term in NVIDIA's JPEG2000 decode sample

The sample times the GPU decode correctly with CUDA events, then adds a Tier-2 parse term that a cast to whole seconds rounds to zero on every frame. Fastvideo puts that missing stage at 15 to 29% of decode time.

The Engineer · Build desk

Illustration accompanying A whole-seconds cast zeroes the parse term in NVIDIA's JPEG2000 decode sample

What happened

  • NVIDIA's documentation calls Tier-2 the first stage of decoding, and in Fastvideo's stage table that stage takes 15% of decode frame time at 2K and 29% at 4K.
  • The decode sample runs one frame at a time with a single decoder state and a single job queue, and its -b flag groups only the reading of files from disk.
  • Fastvideo published its nvJPEG2000 figures from a separate program whose timer starts before nvjpeg2kStreamParse and stops after the GPU finishes decoding.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A table that sets the sample's printed decode time beside a whole-pipeline figure flatters the sample side, so anyone comparing GPU JPEG2000 codecs has to re-time both under one rule before the columns mean anything.
  • decision Two questions now precede reading any GPU codec throughput number: where the timer opens, and how many frames are in flight, because a flag named like a batch size may only be batching disk reads.
  • exposure The zeroed term lives in sample code that teams copy as a starting harness, so timing code derived from nvjpeg2000DecodeSample.cpp inherits a parse cost of zero without warning.
  • cost Checking the size of the omission for NVIDIA's library is not possible from its own output, so a buyer who wants comparable numbers pays for an in-house benchmark program instead.

The two terms in the decode sample's total come from different clocks. The GPU work is timed with a pair of CUDA events recorded on the same queue, one before nvjpeg2kDecodeImage and one after [1]. That is correct, because the call is asynchronous: it puts the work into a GPU job queue and returns immediately, so a CPU stopwatch around it would time the enqueue [2]. Tier-2 parsing, done by nvjpeg2kStreamParse, is timed on the CPU with perfclock::now() and added to the total afterwards [3].

The addition is the defect. The sample casts the interval with duration_cast<std::chrono::seconds> and takes count(), so the value is truncated to whole seconds [4]. Parsing a frame takes milliseconds [5]. Whole seconds is a generous unit for a millisecond. So time += parse_time adds exactly zero, for every frame on every machine [6]. Fastvideo's write-up says: "This is not a choice of measurement boundary; it is an error in the measurement." [7]

NVIDIA's documentation calls Tier-2 the first stage of decoding [8]. In Fastvideo's own stage table, Tier-2 at decoding takes 15% of frame time at 2K and 29% at 4K [9]. Those shares are fvJPEG2000's, because nvJPEG2000 does not report time by stage and its shares are unknown to Fastvideo [10]. If they transferred, a total missing that stage would be 71% to 85% of whole-decode time, and whole decode would take 1.18 to 1.41 times the printed number [22]. For the transfer to hold, Tier-2 in nvJPEG2000 would have to cost about what it costs in fvJPEG2000 on the same CPU; Fastvideo argues the stage is the same and runs on the CPU in the same way [11].

Other work sits outside the timed region: allocating GPU buffers, reading the file and writing the result [12]. The decoded frame is never copied back to host memory inside the measured loop [12].

Concurrency in the sample is one frame deep. It keeps one decoder state and one queue of GPU jobs, and waits after every frame [13]. The -b option is described as a batch size and groups only the reading of files from disk [14]. In Fastvideo's tables the notation 8x2 means eight CPU threads with two frames in flight on the GPU in each [15], and the write-up warns that batching works differently in the two codecs, so the same word in the tables would otherwise mean two things [25].

The encoding sample draws its boundary differently, and by Fastvideo's account correctly. The whole per-frame loop is inside the timer, including nvjpeg2kEncodeRetrieveBitstream, which copies the compressed image to host memory; loading the source frame onto the GPU stays outside, because it happens when the file is read [16]. Fastvideo says that is the boundary it uses in single-frame mode [17].

For its published nvJPEG2000 numbers, Fastvideo did not use the sample. It wrote bench/nvj2k_bench-02/nvj2k_bench-02.cpp, whose timer opens before nvjpeg2kStreamParse and closes after the GPU has finished decoding, so Tier-2 is inside the measured time on both sides [18]. The harness, the scripts and the raw logs are on GitHub [19]. The author of the write-up works at Fastvideo, which makes the other codec in the comparison [20].

What to watch

  • Whether NVIDIA changes the duration_cast in nvjpeg2000DecodeSample.cpp or the total the sample prints.
  • Per-stage timing in nvJPEG2000 would let someone check whether Tier-2 costs there what it costs in fvJPEG2000.
  • Whether anyone outside Fastvideo reruns the published harness on other hardware and posts the logs.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories