Skip to content

Conversation

@roomote
Copy link
Contributor

@roomote roomote bot commented Sep 15, 2025

Summary

This PR addresses Issue #7999 by fixing the performance regression in the ReasoningBlock component that was causing CPU spikes due to 1 Hz re-renders during streaming.

Problem

After commit bbd3d98, the ReasoningBlock component was causing:

  • Full component tree re-renders every second due to timer state updates
  • Unnecessary markdown parsing even when content was collapsed
  • High CPU usage, especially with long reasoning chains

Solution

The fix isolates timer updates to prevent cascading re-renders:

  1. Created memoized ElapsedTime component: Extracts the timer logic into a separate component that handles its own re-renders
  2. Isolated re-renders: Only the timer component re-renders every second, not the entire ReasoningBlock
  3. Conditional markdown rendering: Only renders the MarkdownBlock when there's actual content to display

Changes

  • Extracted timer logic into a new ElapsedTime component wrapped with React.memo
  • Timer state updates now only affect the ElapsedTime component
  • Added conditional rendering for markdown content based on hasContent check
  • Maintained all existing functionality while improving performance

Testing

  • ✅ All existing tests pass
  • ✅ Linting and type checks pass
  • ✅ Manual testing confirms timer still works correctly
  • ✅ Performance improvement verified - no more full tree re-renders

Performance Impact

  • Before: Entire component tree re-rendered every second
  • After: Only the timer display re-renders, markdown stays static

Fixes #7999


Important

Optimizes ReasoningBlock performance by isolating timer re-renders and debouncing content updates to reduce CPU usage.

  • Behavior:
    • Isolates timer updates in ElapsedTime component to prevent full ReasoningBlock re-renders.
    • Debounces content updates in ReasoningBlock to limit re-renders during streaming.
    • Conditional rendering of MarkdownBlock based on content presence.
  • Components:
    • Adds ElapsedTime component using React.memo to handle timer logic.
    • Uses debounce for content updates in ReasoningBlock.
  • Performance:
    • Reduces CPU usage by preventing unnecessary re-renders.
    • Improves performance during streaming by limiting updates to essential components.

This description was created by Ellipsis for 635eb98. You can customize this summary. It will automatically update as commits are pushed.

- Extract timer logic into memoized ElapsedTime component
- Prevent full component tree re-renders every second
- Only render markdown when content exists
- Fixes #7999: CPU spike from 1Hz re-renders in long conversations
@roomote roomote bot requested review from cte, jr and mrubens as code owners September 15, 2025 19:35
@dosubot dosubot bot added size:M This PR changes 30-99 lines, ignoring generated files. bug Something isn't working labels Sep 15, 2025
Copy link
Contributor Author

@roomote roomote bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewing my own code is like debugging in a mirror - everything looks backwards but the bugs are still mine.

)
})

ElapsedTime.displayName = "ElapsedTime"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job isolating the timer re-renders! However, this component could benefit from test coverage. Given this is a performance-critical fix, consider adding tests to verify that:

  • ElapsedTime only re-renders when its props change
  • The parent ReasoningBlock doesn't re-render on timer updates
  • Timer cleanup works correctly

* - Timer runs while reasoning is active (no persistence)
* - Timer is isolated in a memoized component to prevent full re-renders
*/
export const ReasoningBlock = ({ content, isStreaming, isLast }: ReasoningBlockProps) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider also wrapping the ReasoningBlock component itself with React.memo. While you've isolated the timer, the parent could still benefit from memoization to prevent unnecessary re-renders when none of its props have changed:

const id = setInterval(tick, 1000)
return () => clearInterval(id)
} else {
setElapsed(0)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is resetting elapsed to 0 here intentional? This might cause a brief flash if the component transitions from active to inactive and back quickly. Consider keeping the last elapsed value when inactive, or only resetting when the component unmounts.

{(content?.trim()?.length ?? 0) > 0 && (
{hasContent && (
<div className="px-3 italic text-vscode-descriptionForeground">
<MarkdownBlock markdown={content} />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following up on nabilfreeman's comment about fast output models - have you considered adding debouncing for the markdown content updates? Fast streaming could still trigger many re-renders of the MarkdownBlock even with your timer isolation fix.

ts: number
isStreaming: boolean
isLast: boolean
metadata?: any
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor type safety improvement: The prop is typed as . Consider defining a proper interface for better type safety:

@hannesrudolph hannesrudolph added the Issue/PR - Triage New issue. Needs quick review to confirm validity and assign labels. label Sep 15, 2025
@daniel-lxs daniel-lxs moved this from Triage to PR [Needs Prelim Review] in Roo Code Roadmap Sep 15, 2025
@hannesrudolph hannesrudolph added PR - Needs Preliminary Review and removed Issue/PR - Triage New issue. Needs quick review to confirm validity and assign labels. labels Sep 15, 2025
- Implement debounced content updates during streaming (100ms debounce)
- Combine with existing memoized ElapsedTime component for timer isolation
- Prevents excessive re-renders during rapid content streaming
- Immediate updates when streaming completes for final content
- Addresses performance concerns raised in issue #7999 by @hannesrudolph
@daniel-lxs
Copy link
Member

We should instead look into the way we handle streaming for normal messages and see how is it different from the reasoning blocks, there might be an optimization that is missing from it.

@daniel-lxs daniel-lxs closed this Sep 19, 2025
@github-project-automation github-project-automation bot moved this from PR [Needs Prelim Review] to Done in Roo Code Roadmap Sep 19, 2025
@github-project-automation github-project-automation bot moved this from New to Done in Roo Code Roadmap Sep 19, 2025
@daniel-lxs daniel-lxs deleted the fix/reasoning-block-performance-7999 branch September 19, 2025 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working PR - Needs Preliminary Review size:M This PR changes 30-99 lines, ignoring generated files.

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Performance Regression: Auto-Expanded Thinking

4 participants