Skip to content

Use two generations in BoundedCache instead of sorting the entries by last usage - #1739

Merged
meziantou merged 1 commit into
mainfrom
feature/priceless-babbage-41600a
Oct 20, 2026
Merged

meziantou merged 1 commit into
mainfrom
feature/priceless-babbage-41600a

Conversation

@meziantou

Copy link
Copy Markdown
Owner

BoundedCache used least-recently-used (LRU) eviction. That wrote a shared Interlocked counter and a per-entry timestamp on every hit, and every eviction copied all the entries to an array and sorted them. It now uses two generations of half the capacity:

  • A hit in the current generation writes nothing.
  • A hit in the previous generation copies the entry back into the current one, so entries that are used often stay in the cache.
  • When the current generation is full, it becomes the previous one and the old previous one is dropped. No sorting is needed.

The cache still holds at most Capacity entries, plus a few that may be added during a rotation. Each entry no longer needs its own Entry object.

Why

This came from looking into whether the 8,192-entry cache of OverloadCandidateIndex is too small for large compilations. That cache is shared by every rule that uses OverloadFinder (MA0002, MA0011, MA0032, MA0042, MA0054, MA0074, MA0167, MA0209).

Measurements per run on meziantou.framework/src (6,747 files), with the 8 OverloadFinder rules and sequential analysis:

Cache Hits Misses (distinct keys: 68,508) Time creating values Time evicting
LRU, 8,192 (before) 1.418M 76.1k–76.9k 63–80 ms 36–73 ms
LRU, 32,768 1.425M 69.0k–69.5k ~80 ms 25–42 ms
LRU, 131,072 1.426M 68.5k 62–76 ms 0
Two generations, 8,192 (this PR) 1.417M 78.0k 88 ms ~0

The capacity is not the bottleneck. The hit rate is already about 95%, and evictions cause only about 8k recomputations at about 1 µs each. Most of the CreateParameterTypes time in profiles is Roslyn loading symbol data (AllInterfaces, GetMembers) the first time. That cost is the same whatever the cache size, so the capacity is left at 8,192. The only cost that could be removed was the eviction itself and the write on every hit, which matters most under concurrent analysis (1.4M hits per run). This PR removes both, at the cost of about 1.5k extra misses.

Notes for the reviewer

  • BoundedCache is also used by RegexCache, RegexUsageAnalyzerBase, and DoNotUseBannedSyntaxAnalyzer (capacity 128). Their behavior is the same apart from which entries get evicted.
  • I didn't finish an end-to-end wall-clock comparison: the machine was heavily loaded (load average around 19 on 15 cores), which made the runs too noisy. The numbers above come from counters added to a local build only for measuring.
  • I replaced TheLeastRecentlyUsedEntriesAreEvicted with TheEntriesThatAreNotUsedAreEvicted and added TheEntriesThatAreUsedOftenAreKept.
  • dotnet test passes on all Roslyn versions (4.8, 4.14, 5.0, 5.6, 5.9): 28,363 passed, 0 failed, 45 skipped.

… last usage

The least recently used eviction wrote a shared counter on every hit and sorted all the entries on every eviction.
The two-generation scheme writes nothing when the entry is in the current generation and evicts by dropping a dictionary.
@meziantou
meziantou merged commit 6d6e4b1 into main Oct 20, 2026
14 checks passed
@meziantou
meziantou deleted the feature/priceless-babbage-41600a branch October 20, 2026 00:48
This was referenced Oct 20, 2026
vdolek pushed a commit to EvilBrainsTechnology/EvilCase that referenced this pull request Oct 20, 2026
Updated
[Meziantou.Analyzer](https://github.com/meziantou/Meziantou.Analyzer)
from 3.0.312 to 3.0.315.

<details>
<summary>Release notes</summary>

_Sourced from [Meziantou.Analyzer's
releases](https://github.com/meziantou/Meziantou.Analyzer/releases)._

## 3.0.315

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.315>

## What's Changed
* Use two generations in BoundedCache instead of sorting the entries by
last usage by @​meziantou in
meziantou/Meziantou.Analyzer#1739


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.314...3.0.315

## 3.0.314

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.314>

## What's Changed
* MA0011: find the overload before computing the culture sensitivity by
@​meziantou in meziantou/Meziantou.Analyzer#1738


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.313...3.0.314

## 3.0.313

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.313>

## What's Changed
* Fix the CA5394 build error in BoundedCacheTests by @​meziantou in
meziantou/Meziantou.Analyzer#1735
* MA0243: Use an escape sequence for invisible characters by @​meziantou
in meziantou/Meziantou.Analyzer#1737


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.312...3.0.313

Commits viewable in [compare
view](meziantou/Meziantou.Analyzer@3.0.312...3.0.315).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Meziantou.Analyzer&package-manager=nuget&previous-version=3.0.312&new-version=3.0.315)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
kivilev pushed a commit to kivilev/plsql-developer-text-tools that referenced this pull request Oct 21, 2026
Updated
[Meziantou.Analyzer](https://github.com/meziantou/Meziantou.Analyzer)
from 3.0.303 to 3.0.315.

<details>
<summary>Release notes</summary>

_Sourced from [Meziantou.Analyzer's
releases](https://github.com/meziantou/Meziantou.Analyzer/releases)._

## 3.0.315

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.315>

## What's Changed
* Use two generations in BoundedCache instead of sorting the entries by
last usage by @​meziantou in
meziantou/Meziantou.Analyzer#1739


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.314...3.0.315

## 3.0.314

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.314>

## What's Changed
* MA0011: find the overload before computing the culture sensitivity by
@​meziantou in meziantou/Meziantou.Analyzer#1738


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.313...3.0.314

## 3.0.313

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.313>

## What's Changed
* Fix the CA5394 build error in BoundedCacheTests by @​meziantou in
meziantou/Meziantou.Analyzer#1735
* MA0243: Use an escape sequence for invisible characters by @​meziantou
in meziantou/Meziantou.Analyzer#1737


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.312...3.0.313

## 3.0.312

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.312>

## What's Changed
* Read the usage of the cache entries once before sorting them for the
eviction by @​meziantou in
meziantou/Meziantou.Analyzer#1734


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.311...3.0.312

## 3.0.311

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.311>

## What's Changed
* Fuzz: remove the Moq, MSTest, Newtonsoft.Json, NUnit and Serilog
package references by @​meziantou in
meziantou/Meziantou.Analyzer#1712
* Fuzz: report a regression only when the fuzzed commit contains the fix
by @​meziantou in
meziantou/Meziantou.Analyzer#1733
* MA0110: track the node to fix when the regex is in an attribute of the
type by @​meziantou in
meziantou/Meziantou.Analyzer#1731


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.310...3.0.311

## 3.0.310

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.310>

## What's Changed
* MA0190: find the method declaration after updating the call sites by
@​meziantou in meziantou/Meziantou.Analyzer#1708
* MA0011: do not bind the invocations of attribute arguments
speculatively by @​meziantou in
meziantou/Meziantou.Analyzer#1709
* Fuzz: do not blame the rules for the inputs on which Roslyn fails by
@​meziantou in meziantou/Meziantou.Analyzer#1710
* MA0010: do not offer the code fix when the class has no name by
@​meziantou in meziantou/Meziantou.Analyzer#1700


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.309...3.0.310

## 3.0.309

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.309>

## What's Changed
* MA0110: keep the variable when it cannot be removed or renamed by
@​meziantou in meziantou/Meziantou.Analyzer#1698
* MA0071: do not move the attributes of the else block by @​meziantou in
meziantou/Meziantou.Analyzer#1699
* MA0077: do not throw when Equals(T) is declared several times by
@​meziantou in meziantou/Meziantou.Analyzer#1701
* MA0187: do not offer the code fix when the property has no name by
@​meziantou in meziantou/Meziantou.Analyzer#1702
* MA0102: do not offer the code fix when the diagnostic is not on a
member by @​meziantou in
meziantou/Meziantou.Analyzer#1703
* MA0117: do not offer the code fix when the property has no name by
@​meziantou in meziantou/Meziantou.Analyzer#1704
* MA0189: do not offer the code fix when the fixed buffer has no name or
type by @​meziantou in
meziantou/Meziantou.Analyzer#1705
* MA0089: fix the single-character constants that are not literals by
@​meziantou in meziantou/Meziantou.Analyzer#1706
* MA0004: fix the await expressions without semicolon by @​meziantou in
meziantou/Meziantou.Analyzer#1707


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.308...3.0.309

## 3.0.308

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.308>

## What's Changed
* MA0137, MA0138: do not offer the rename when the name would be empty
or the declaration does not compile by @​meziantou in
meziantou/Meziantou.Analyzer#1697
* MA0067: do not report System.Guid used as an attribute by @​meziantou
in meziantou/Meziantou.Analyzer#1696


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.307...3.0.308

## 3.0.307

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.307>

## What's Changed
* MA0002: insert the comparer at the right position when an extension
method is called as a static method by @​meziantou in
meziantou/Meziantou.Analyzer#1670
* MA0044: do not offer the code fix when ToString is used as a statement
by @​meziantou in
meziantou/Meziantou.Analyzer#1669
* MA0185: do not offer the code fix when string.Create is used as a
statement by @​meziantou in
meziantou/Meziantou.Analyzer#1668
* MA0101: do not offer the code fix for interpolated strings by
@​meziantou in meziantou/Meziantou.Analyzer#1667
* MA0187: do not offer the code fix when the class has an explicit
constructor by @​meziantou in
meziantou/Meziantou.Analyzer#1666
* MA0004: fix the code fix when the await expression is an argument by
@​meziantou in meziantou/Meziantou.Analyzer#1665
* MA0102: do not report partial members by @​meziantou in
meziantou/Meziantou.Analyzer#1664
* MA0093: do not offer EventArgs.Empty when the event args are generic
by @​meziantou in
meziantou/Meziantou.Analyzer#1663
* MA0053: do not report classes used as generic constraints by
@​meziantou in meziantou/Meziantou.Analyzer#1662
* Overload finder: Half is not implicitly convertible to float or double
by @​meziantou in
meziantou/Meziantou.Analyzer#1661
* MA0042, MA0045: make the enclosing function async in the code fix by
@​meziantou in meziantou/Meziantou.Analyzer#1660


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.306...3.0.307

## 3.0.306

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.306>

## What's Changed
* Allow the fuzzer to open up to 50 issues per run by @​meziantou in
meziantou/Meziantou.Analyzer#1652
* Build the fuzz project after the other builds of build_with_analyzers
by @​meziantou in
meziantou/Meziantou.Analyzer#1671
* MA0038: fix the analyzer crash on invalid code and the code fixes that
break the code by @​meziantou in
meziantou/Meziantou.Analyzer#1653
* MA0194: do not throw on invalid code by @​meziantou in
meziantou/Meziantou.Analyzer#1654
* MA0009, MA0023: do not throw when the GeneratedRegex arguments are
invalid by @​meziantou in
meziantou/Meziantou.Analyzer#1655
* MA0110: do not report invalid patterns and fix the partial property
used as a statement by @​meziantou in
meziantou/Meziantou.Analyzer#1656
* MA0182: remove all the parts of a partial type and analyze the
delegate signatures by @​meziantou in
meziantou/Meziantou.Analyzer#1657
* MA0036: do not report classes that contain protected members by
@​meziantou in meziantou/Meziantou.Analyzer#1658
* MA0137, MA0138: do not rename a method to a name that is already used
by @​meziantou in
meziantou/Meziantou.Analyzer#1659


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.305...3.0.306

## 3.0.305

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.305>

## What's Changed
* MA0008: do not overflow the stack on cyclic struct layouts by
@​meziantou in meziantou/Meziantou.Analyzer#1640


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.304...3.0.305

## 3.0.304

NuGet package:
<https://www.nuget.org/packages/Meziantou.Analyzer/3.0.304>

## What's Changed
* Add fuzz tests for the analyzers and the code fixers by @​meziantou in
meziantou/Meziantou.Analyzer#1641


**Full Changelog**:
meziantou/Meziantou.Analyzer@3.0.303...3.0.304

Commits viewable in [compare
view](meziantou/Meziantou.Analyzer@3.0.303...3.0.315).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Meziantou.Analyzer&package-manager=nuget&previous-version=3.0.303&new-version=3.0.315)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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.

1 participant