Skip to content

Conversation

@mondeja
Copy link

@mondeja mondeja commented Oct 22, 2025

This is a 🙋 feature or enhancement.

Summary

Context

Resolves #9827

Performance

Case Without Cache With Cache
Best O(n · N) O(n)
Worst O(n · N) O(k · N + n)

Where:

  • n = number of {% link %} renders performed in the page or process.
  • N = number of files in the site that the {% link %} tag must search through to find a match.
  • k = number of distinct paths rendered (k ≤ n).

Explanation:

  • Without Cache: each render searches linearly through N files → n renders × N files.
  • With Cache: each distinct path is searched only once among N files; subsequent accesses are O(1) thanks to the hash cache.

Memory

Case Without Cache With Cache
Memory O(n) allocations O(k) allocations

Where:

  • n = number of {% link %} renders performed.
  • k = number of distinct file paths rendered (k ≤ n).

Explanation:

  • Without Cache: Each render allocates new objects for parsing and rendering the Liquid template, repeated n times → memory usage grows roughly linearly with n.
  • With Cache: Parsing and rendering still happen per distinct path, but repeated renders of the same path reuse cached results, so memory allocations scale with k instead of n, which can drastically reduce memory consumption if n ≫ k.

Summary: in the average case (randomization of 1000 paths with 1/1000 changes to be the same), the memoization is ~47% faster. In the worst case, memoization is 2% slower. In both cases, the memory used by the memoized implementation uses less memory. For example:

$ bundle exec ruby benchmark/link-tag-memoized.rb
Benchmark (average case):
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux-gnu]
Warming up --------------------------------------
       Without cache     1.000 i/100ms
          With cache     1.000 i/100ms
Calculating -------------------------------------
       Without cache      6.646 (± 0.0%) i/s  (150.46 ms/i) -     34.000 in   5.115738s
          With cache      9.791 (± 0.0%) i/s  (102.13 ms/i) -     49.000 in   5.008767s

Comparison:
          With cache:        9.8 i/s
       Without cache:        6.6 i/s - 1.47x  slower

Memory profile for Without cache (average case):
Total allocated: 9560.5078125 KB (128000 objects)
Total retained:  1.078125 KB (1 objects)
Memory profile for With cache (average case):
Total allocated: 8608.3984375 KB (127000 objects)
Total retained:  0.0 KB (0 objects)

Benchmark (worst case):
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux-gnu]
Warming up --------------------------------------
       Without cache     1.000 i/100ms
          With cache     1.000 i/100ms
Calculating -------------------------------------
       Without cache      6.407 (± 0.0%) i/s  (156.07 ms/i) -     33.000 in   5.151846s
          With cache      6.311 (± 0.0%) i/s  (158.45 ms/i) -     32.000 in   5.072271s

Comparison:
       Without cache:        6.4 i/s
          With cache:        6.3 i/s - 1.02x  slower

Memory profile for Without cache (worst case):
Total allocated: 9570.1474609375 KB (128128 objects)
Total retained:  1.078125 KB (1 objects)
Memory profile for With cache (worst case):
Total allocated: 8617.0068359375 KB (127127 objects)
Total retained:  0.0 KB (0 objects)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Optimization oportunity for tag {% link %}

2 participants