Skip to content

Performance issue when rendering deeply nested ordered/unordered lists #445

Description

@Beliefei

Hi, thanks for this great library.

I noticed a significant performance problem when rendering Markdown with deeply nested lists, especially when the content contains multiple levels of nested ordered and unordered lists.

What happens

Rendering becomes very slow when the nesting depth increases. In my testing, the slowdown is much more noticeable with nested lists than with other block structures.

This seems to be a rendering/layout problem rather than a Markdown parsing problem.

Why this may be happening

After reading the code, I think the main cost comes from SwiftUI layout invalidation and repeated recomputation in the list rendering pipeline:

  1. NumberedListView measures marker width dynamically via GeometryReader + PreferenceKey, then writes the result back into @State.
    This appears to cause an extra layout pass for each ordered-list level.

  2. BlockSequence uses PreferenceKey updates to collect block margins and stores them in local state.
    In deeply nested list trees, this likely creates cascading state updates and relayout across parent/child list containers.

  3. BlockSequence uses ForEach(..., id: \.self), while BlockNode / RawListItem are recursive Hashable structures.
    For deeply nested content, identity/hash calculation may become unnecessarily expensive.

  4. When relayout happens, inline text rendering also gets recomputed repeatedly, which amplifies the visible cost.

Relevant code paths

  • Sources/MarkdownUI/Views/Blocks/NumberedListView.swift
  • Sources/MarkdownUI/Views/Blocks/ListItemView.swift
  • Sources/MarkdownUI/Views/Blocks/ColumnWidthPreference.swift
  • Sources/MarkdownUI/Views/Blocks/BlockSequence.swift

Suggested direction

A minimal optimization might be:

  • Avoid dynamic marker width measurement for ordered lists, or at least avoid feeding it back through @State for every nested list level.
  • Use stable positional identity like id: \.index in BlockSequence instead of id: \.self for recursive list/block nodes.

I suspect these two changes alone could significantly improve nested list rendering performance.

Minimal example

A deeply nested structure like this is enough to reproduce the issue:

1. Level 1
   - Level 2
     1. Level 3
        - Level 4
          1. Level 5
             - Level 6
               1. Level 7
                  - Level 8
                    1. Level 9
                       - Level 10

The real issue becomes more obvious when each item contains paragraph text and multiple sibling items at each level.

Expected behavior

Rendering nested lists should remain reasonably fast even when the nesting depth is high.

Actual behavior

Rendering time increases sharply as list nesting grows, especially with ordered lists.

If helpful, I can also rewrite this into a more GitHub-native style, like a shorter “bug report” version or a more technical version with clearer implementation notes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions