Summary
In a workflow with ~200 nodes, panning the canvas at a low zoom level (where many nodes are visible at once) drops to ~14-24 fps with worst frames over 170 ms. Chrome DevTools Performance recording shows that imageCaption.js:1956 updatePosition accounts for ~62% of frame time (3921 ms out of 6000 ms), and the resulting browser Recalculate Style jumps from baseline ~14% to ~68% of frame time.
Disabling ComfyUI-Prompt-Assistant entirely restores normal panning performance.
Root cause (from reading the source)
In js/modules/imageCaption.js around line 2027, every per-node assistant wraps app.canvas.onDrawBackground:
const originalDrawBackground = app.canvas.onDrawBackground;
const onDrawWrapper = function () {
const ret = originalDrawBackground?.apply(this, arguments);
updatePosition();
return ret;
};
app.canvas.onDrawBackground = onDrawWrapper;
This means:
- The wrap chain grows linearly with the number of attached assistants. With N assistants, every canvas frame walks N nested function calls and runs N
updatePosition()s.
updatePosition() (line 1956) writes to containerDiv.style.left/bottom/transform every call → forces browser layout / Recalculate Style on N elements per frame.
- There is no visible-node / viewport culling — assistants update position even when their owning node is far outside the viewport.
- The cleanup function (line 2036) only restores
onDrawBackground if it currently equals the wrapper — so once another wrapper is added on top, that assistant can never be unwrapped, and the chain grows monotonically.
The combination produces O(N²)-ish-feeling behavior at high N: every frame walks every wrap, even for invisible nodes.
Reproduction
- Load a workflow with 100+ valid nodes for ImageCaption attachment
- Zoom canvas out so most/all nodes are in viewport (e.g.
app.canvas.ds.scale ≈ 0.05)
- Pan the canvas continuously
- Observe FPS in DevTools Performance / dropping below 20 fps
Suggested fixes
- Viewport culling: in
updatePosition, early-return if assistant.node.getBounding() does not intersect the visible canvas area
- Single global tick instead of per-assistant wrap: register one
onDrawBackground wrap that iterates ImageCaption.instances once per frame, instead of N nested wraps
- Skip update when transform/position has not changed: cache last known values and only write to DOM on change
- Throttle: at very low zoom, drop the update rate to e.g. 10 fps via
EventManager.debounce
The single-global-tick change alone should give a dramatic improvement.
Environment
- ComfyUI frontend 1.42.x
- Workflow: ~200 nodes, mix of standard + custom
- ComfyUI-Prompt-Assistant: latest at time of testing
Discovered while developing ComfyUI Mirror Panel, where the perf hit was initially misattributed to the Mirror plugin until DevTools profiling pointed at imageCaption.js.
Summary
In a workflow with ~200 nodes, panning the canvas at a low zoom level (where many nodes are visible at once) drops to ~14-24 fps with worst frames over 170 ms. Chrome DevTools Performance recording shows that
imageCaption.js:1956 updatePositionaccounts for ~62% of frame time (3921 ms out of 6000 ms), and the resulting browser Recalculate Style jumps from baseline ~14% to ~68% of frame time.Disabling ComfyUI-Prompt-Assistant entirely restores normal panning performance.
Root cause (from reading the source)
In
js/modules/imageCaption.jsaround line 2027, every per-node assistant wrapsapp.canvas.onDrawBackground:This means:
updatePosition()s.updatePosition()(line 1956) writes tocontainerDiv.style.left/bottom/transformevery call → forces browser layout / Recalculate Style on N elements per frame.onDrawBackgroundif it currently equals the wrapper — so once another wrapper is added on top, that assistant can never be unwrapped, and the chain grows monotonically.The combination produces O(N²)-ish-feeling behavior at high N: every frame walks every wrap, even for invisible nodes.
Reproduction
app.canvas.ds.scale ≈ 0.05)Suggested fixes
updatePosition, early-return ifassistant.node.getBounding()does not intersect the visible canvas areaonDrawBackgroundwrap that iteratesImageCaption.instancesonce per frame, instead of N nested wrapsEventManager.debounceThe single-global-tick change alone should give a dramatic improvement.
Environment
Discovered while developing ComfyUI Mirror Panel, where the perf hit was initially misattributed to the Mirror plugin until DevTools profiling pointed at
imageCaption.js.