Highly reduce the CPU by setting the ogre workers to 0 (backport #1302)#1306
Open
mergify[bot] wants to merge 1 commit into
Open
Highly reduce the CPU by setting the ogre workers to 0 (backport #1302)#1306mergify[bot] wants to merge 1 commit into
mergify[bot] wants to merge 1 commit into
Conversation
Contributor
Author
|
Cherry-pick of 32b2508 has failed: To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally |
19 tasks
Historically this was set to the number of logical CPU cores. For the scenes Gazebo typically renders that is a net loss: each of those passes fans out to every worker thread through a pthread barrier (~20-30 barrier syncs per rendered frame) regardless of how little work there is, with no early-out for low object counts. Default to 0, which makes Ogre run these passes inline on the main thread (no worker threads, no barrier). Very large scenes (tens of thousands of objects) can opt back into threading via GZ_RENDERING_OGRE2_WORKER_THREADS: unset / "0" -> run inline on the main thread (default) "N" -> use N worker threads Generated-by: Claude Opus 4.8 / Claude Sonnet 4.6 --------- Signed-off-by: Jose Luis Rivero <jrivero@honurobotics.com> (cherry picked from commit 32b2508)
c913626 to
0465460
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🦟 Bug fix
Summary
gz-rendering's ogre2 backend has historically created one Ogre SceneManager worker thread per logical CPU core. For the scenes Gazebo typically renders this is a net loss: the worker threads burn several CPU cores on per-frame synchronization while barely changing — and sometimes worsening — frame latency, and the rendered image is bit-identical with or without them.
This PR changes the default to 0 worker threads (Ogre runs the passes inline on the main thread, with no thread pool and no barrier), and adds an opt-in environment variable for the rare scenes (tens of thousands of objects) that still benefit from threading. It also adds a small headless
cpu_benchexample used to characterize the problem so the result is reproducible.Why 0
Passing
0does not create a degenerate zero-thread state. The constructor splits the meaning of "0" into two fields (OgreSceneManager.cpp:147-148):So with
0:mForceMainThread = true(no real threads, no barrier — everyfireWorkerThreadsAndWait()takes theupdateWorkerThreadImpl(0)inline branch at:4636/2204/2242/4663), whilemNumWorkerThreadsis clamped to 1.Therefore
getNumWorkerThreads()returns 1 (OgreSceneManager.h:1001),mQueuedRenderablesPerThread.resize(1)(OgreRenderQueue.cpp:103) allocates exactly one bucket, and the main thread fills it as thread 0. Nothing is sizedto empty, no work is skipped — which is exactly why the rendered output is bit-identical.
What the worker threads actually do (and why removing them helps)
Each rendered frame, the SceneManager runs a series of per-frame passes and fans every one of them out to all worker threads through a pthread barrier. The passes are dispatched via the
RequestTypework items (OgreMain/src/OgreSceneManager.cpp):UPDATE_ALL_ANIMATIONSUPDATE_ALL_TRANSFORMS:1752,:1800UPDATE_ALL_BOUNDS:1877UPDATE_ALL_LODS:1923/1930BUILD_LIGHT_LIST01/02:2179/2207,:2240/2245CULL_FRUSTUM:4648/4654Each dispatch goes through
fireWorkerThreadsAndWait()(OgreSceneManager.cpp:4633), which costs two barrier syncs — one to wake the pool, one to wait for completion.What the workers are not parallelizing
The work the threads do is genuinely parallelizable prep (transform/bounds/LOD/light-list/cull). It is not the draw submission: the render-queue build is per-thread (
OgreRenderQueue.cpp:103,210, each thread fills its ownmQueuedRenderablesPerThreadbucket), butRenderQueue::render()— the HLMS constant-buffer fills and actual draw-call submission — is serial on the main thread (single GL/Vulkan context in ogre-next 2.x).Testing and numbers
All on a 16-logical-core machine.
CORES BUSY= process CPU-seconds / wall seconds (so 1.00 ≈ one core fully busy). "old" = previous default (16 worker threads = cores); "fix" = new default (0, inline). Render output verified bit-identical in every case (480000/480000 lit pixels match across thread counts).Headline —
cpu_bench, 400 boxesThread-count sweep —
cpu_bench, CPU ms/frame (the lever)Wall time stays essentially flat across all thread counts (≈1.6–2.0 ms at N=400; ≈13–14 ms at N=10000, where T=8 13.3 vs T=16 14.0 vs inline 14.1 — within ~6%). In other words, threading almost never wins latency but always costs CPU, and
T=8beatsT=16on both CPU and wall — the oldT=coresdefault was already past the point of diminishing returns. The win grows with core count, so 32-/64-core machines were hurt most and gain most.Final-build sanity check (env=16 vs default 0): N=400 CPU 8.43 → 1.77 ms (−79%); N=1600 10.94 → 2.82 ms (−74%); wall tied; render identical.
cpu_bench example
examples/cpu_bench/— a standalone headless N-box benchmark that isolates per-frame CPU vs. wall time, with knobs for object count, shadows, per-frame pose dirtying, frustum culling, frame-rate cap, and render size. Reports wall min/median/mean, CPU/frame, aCORES BUSYfigure (CPU_seconds / wall_seconds), and a grep-friendly CSV line. This is the tool used to produce the measurements above.Backport Policy
There is the question on backporting the setting to existing releases. Given than it could potentially benefit all the running simulations a lot, I would tend to change it and provide the setting as a fallback.
Checklist
codecheckpassed (See contributing)Generated-by: Claude Opus 4.6
Note to maintainers: Remember to use Squash-Merge and edit the commit message to match the pull request summary while retaining
Signed-off-byandGenerated-bymessages.Backports: If this is a backport, please use Rebase and Merge instead.
This is an automatic backport of pull request #1302 done by Mergify.