Skip to content

Avoid per-instance lock object in InterruptibleLazy and DelayInitArrayMap - #20088

Open
auduchinok wants to merge 1 commit into
dotnet:mainfrom
auduchinok:optimize-interruptiblelazy-lock
Open

Avoid per-instance lock object in InterruptibleLazy and DelayInitArrayMap#20088
auduchinok wants to merge 1 commit into
dotnet:mainfrom
auduchinok:optimize-interruptiblelazy-lock

Conversation

@auduchinok

Copy link
Copy Markdown
Member

I've been measuring memory allocations on some my changes and found out that these sync objects retail a lot of memory due to usages like in ImportILTypeDef. This is a debatable change: it makes things more dangerous (an issue could be if someone locks on the lazy itself) but at the same it's an implementation detail inside the compiler which is fairly advanced, so I hope that it can be used responsibly.

@auduchinok
auduchinok requested a review from a team as a code owner July 28, 2026 10:12
@github-actions github-actions Bot added the AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files label Jul 28, 2026
…yMap

Both types allocated a dedicated `syncObj = obj()` for their one-time
initialisation. These instances are internal and never locked externally,
and there are enough of them (one per lazy IL member, per ILTypeDefs /
ILMethodDefs, etc.) that the extra bare System.Object adds up to tens of MB
on a large project. Lock on `this` instead and drop the field.

Measured on a single-file FCS check against a project with ~486 references:
bare System.Object instances dropped from ~1,000,000 to ~29,000 (~-22 MB).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@auduchinok
auduchinok force-pushed the optimize-interruptiblelazy-lock branch from 25074ee to bdb5b04 Compare July 28, 2026 11:46
@majocha

majocha commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

The way System.Lazy does it is they clear the sync object field via null assignment as soon as the value is created, but it is a bit complicated.

@auduchinok

Copy link
Copy Markdown
Member Author

The way System.Lazy does it is they clear the sync object field via null assignment as soon as the value is created, but it is a bit complicated.

Thanks for the suggestion! The problem is it still keeps the memory for that field, and for many thousands of types it's becoming a bit too much.

@auduchinok

Copy link
Copy Markdown
Member Author

Here's an interesting discovery about how this PR overlaps with #20090:

syncObj is worth much less than its own branch measured, and that's expected. It gave −80.3 MB on FSharp.Common when addConstraintSources was importing all 107 K types (≈1 M InterruptibleLazy+syncObj pairs); with only ~35 K types imported there are proportionally fewer lock objects left, and the branch's own DelayInitValue already locks on this. Hence −14.3 MB here. The two wins overlap; they don't add.

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

Labels

AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

2 participants