Skip to content

Avoid intermediary allocation in Incremental Driver#62089

Merged
CyrusNajmabadi merged 2 commits intodotnet:mainfrom
CyrusNajmabadi:avoidAlloc
Jun 27, 2022
Merged

Avoid intermediary allocation in Incremental Driver#62089
CyrusNajmabadi merged 2 commits intodotnet:mainfrom
CyrusNajmabadi:avoidAlloc

Conversation

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

This saves 600 MB of allocs in my editing testing.

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner June 22, 2022 23:19
@ghost ghost added the Area-Compilers label Jun 22, 2022
internal Builder(NodeStateTable<T> previous, string? name, bool stepTrackingEnabled)
{
_states = ArrayBuilder<TableEntry>.GetInstance();
_states = ArrayBuilder<TableEntry>.GetInstance(previous._states.Length);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

helps avoid resizes. useful since hte prior and current state lengths are often similar

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

note: this pooled array is still highly problematic. because it routinely is >128 elements, we never actually pool it. so we end up churning GB of arrays in incremental scenarios.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would creating a dedicated pool for generators that deals in large arrays only be a potential help here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@jaredpar very much so. but i'd like to do in another PR :)

}

entries = _states[^1].ToImmutableArray();
entry = _states[^1];
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is the alloc we're avoiding. tableentry is already immutable, so we can return that directly.

}

private readonly struct TableEntry
internal readonly struct TableEntry
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

don't love this piece. any ideas?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe make a new type next to it CachedEntryEnumerable?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

tried making this work, but it still had a problem. i can't see any way to make the accessibility work where CachedEntryEnumerable references TableEntry, but doesn't fall afoul of accessibility rules. e.g.:

e.g. even this doesn't work:

        public readonly struct CachedEntryEnumerable
        {
            private readonly TableEntry _entry;

            internal CachedEntryEnumerable(TableEntry entry)
            {
                _entry = entry;
            }

making the constructor private doesn't work either as that then can't be invoked.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

for now, i'm ok exposing this. it's purely immutable data, so it should be very safe. It might make sense to make this a top level type though at this point instead of an internal nested type.

@RikkiGibson
Copy link
Copy Markdown
Member

This saves 600 MB of allocs in my editing testing.

is there a documented workflow you're using to test this?

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor Author

is there a documented workflow you're using to test this?

The workflow is pretty trivial (and is in IncrementalSourceGeneratorBenchmarks). Basically, the benchmark opens Roslyn.sln, opens a file (in my case AbstractCaseCorrectionService.cs), then it continually makes edits to it, getting the incremental compilation result (using the generator driver to actually run the generator pipeline).

I have a trivial generator which is just calling:

                var input = ctx.SyntaxProvider.ForAttributeWithMetadataName(
                    "System.Text.Json.Serialization.JsonSerializableAttribute",
                    (n, _) => n is ClassDeclarationSyntax,
                    (ctx, _) => 0);

In other words, it is saying: run this logic when we hit a class with the [JsonSerializable] attribute on it. I'm then profiling this and seeing where the hotspots are for CPU/ram and i'm going to town on making things better.

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor Author

@RikkiGibson @jcouv for additional reviews plz :)

@CyrusNajmabadi CyrusNajmabadi merged commit 51bfbfd into dotnet:main Jun 27, 2022
@ghost ghost added this to the Next milestone Jun 27, 2022
@CyrusNajmabadi CyrusNajmabadi deleted the avoidAlloc branch June 27, 2022 18:24
@RikkiGibson RikkiGibson modified the milestones: Next, 17.3 P3 Jun 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants