-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow BatchNodes to reuse prior computed arrays if they would generate hte same value #62169
Changes from all commits
dfb2790
73b59b5
41ae13b
f680981
e0d8675
aed0abe
2135030
505f444
d27401d
d596007
02099ea
644ed1b
97bb4b0
7317907
2a1b30c
572b674
383e64e
51f2a9a
38ce8ef
ed286ca
6bfd5dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,6 @@ | |
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics; | ||
|
@@ -30,6 +28,81 @@ public BatchNode(IIncrementalGeneratorNode<TInput> sourceNode, IEqualityComparer | |
|
||
public IIncrementalGeneratorNode<ImmutableArray<TInput>> WithTrackingName(string name) => new BatchNode<TInput>(_sourceNode, _comparer, name); | ||
|
||
private (ImmutableArray<TInput>, ImmutableArray<(IncrementalGeneratorRunStep InputStep, int OutputIndex)>) GetValuesAndInputs( | ||
NodeStateTable<TInput> sourceTable, | ||
NodeStateTable<ImmutableArray<TInput>> previousTable, | ||
NodeStateTable<ImmutableArray<TInput>>.Builder newTable) | ||
{ | ||
// Do an initial pass to both get the steps, and determine how many entries we'll have. | ||
var sourceInputsBuilder = newTable.TrackIncrementalSteps ? ArrayBuilder<(IncrementalGeneratorRunStep InputStep, int OutputIndex)>.GetInstance() : null; | ||
|
||
var entryCount = 0; | ||
foreach (var entry in sourceTable) | ||
{ | ||
// Always keep track of its step information, regardless of if the entry was removed or not, so we | ||
// can accurately report how long it took and what actually happened (for testing validation). | ||
sourceInputsBuilder?.Add((entry.Step!, entry.OutputIndex)); | ||
|
||
if (entry.State != EntryState.Removed) | ||
entryCount++; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. first pass determiens how many entries we have. note: this will allocate an IEnumerable. If we worry about the cost of that, we could have a dedicated method on SourceTable (called something like "CountOfNonRemovedEntries"). |
||
|
||
var sourceInputs = sourceInputsBuilder != null ? sourceInputsBuilder.ToImmutableAndFree() : default; | ||
|
||
// First, see if we can reuse the entries from previousTable. | ||
// If not, produce the actual values we need from sourceTable. | ||
var result = tryReusePreviousTableValues(entryCount) ?? computeCurrentTableValues(entryCount); | ||
return (result, sourceInputs); | ||
|
||
ImmutableArray<TInput>? tryReusePreviousTableValues(int entryCount) | ||
{ | ||
if (previousTable.Count != 1) | ||
return null; | ||
|
||
var previousItems = previousTable.Single().item; | ||
|
||
// If they don't have the same length, we clearly can't reuse them. | ||
if (previousItems.Length != entryCount) | ||
return null; | ||
|
||
var indexInPrevious = 0; | ||
foreach (var entry in sourceTable) | ||
{ | ||
if (entry.State == EntryState.Removed) | ||
continue; | ||
|
||
// If the entries aren't the same, we can't reuse. | ||
if (!EqualityComparer<TInput>.Default.Equals(entry.Item, previousItems[indexInPrevious])) | ||
return null; | ||
|
||
indexInPrevious++; | ||
} | ||
|
||
// We better have the exact same count as previousItems as we checked that above. | ||
Debug.Assert(indexInPrevious == previousItems.Length); | ||
|
||
// Looks good, we can reuse this. | ||
return previousItems; | ||
} | ||
|
||
ImmutableArray<TInput> computeCurrentTableValues(int entryCount) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. finally, we do a pass where we allocate a builder. however, importantly, we know the exact amount of items we need, so the builder doesn't create a scratch array that has the chance of not being pooled if too large. |
||
{ | ||
// Important: we initialize with the exact capacity we need here so that we don't make a pointless | ||
// scratch array that may be very large and may cause GC churn when it cannot be returned to the pool. | ||
var builder = ArrayBuilder<TInput>.GetInstance(entryCount); | ||
foreach (var entry in sourceTable) | ||
{ | ||
if (entry.State == EntryState.Removed) | ||
continue; | ||
|
||
builder.Add(entry.Item); | ||
} | ||
|
||
Debug.Assert(builder.Count == entryCount); | ||
return builder.ToImmutableAndFree(); | ||
} | ||
} | ||
|
||
public NodeStateTable<ImmutableArray<TInput>> UpdateStateTable(DriverStateTable.Builder builder, NodeStateTable<ImmutableArray<TInput>> previousTable, CancellationToken cancellationToken) | ||
{ | ||
// grab the source inputs | ||
|
@@ -50,22 +123,7 @@ public NodeStateTable<ImmutableArray<TInput>> UpdateStateTable(DriverStateTable. | |
|
||
var stopwatch = SharedStopwatch.StartNew(); | ||
|
||
var sourceValuesBuilder = ArrayBuilder<TInput>.GetInstance(); | ||
var sourceInputsBuilder = newTable.TrackIncrementalSteps ? ArrayBuilder<(IncrementalGeneratorRunStep InputStep, int OutputIndex)>.GetInstance() : null; | ||
|
||
foreach (var entry in sourceTable) | ||
{ | ||
// At this point, we can remove any 'Removed' items and ensure they're not in our list of states. | ||
if (entry.State != EntryState.Removed) | ||
sourceValuesBuilder.Add(entry.Item); | ||
|
||
// However, regardless of if the entry was removed or not, we still keep track of its step information | ||
// so we can accurately report how long it took and what actually happened (for testing validation). | ||
sourceInputsBuilder?.Add((entry.Step!, entry.OutputIndex)); | ||
} | ||
|
||
var sourceValues = sourceValuesBuilder.ToImmutableAndFree(); | ||
var sourceInputs = newTable.TrackIncrementalSteps ? sourceInputsBuilder!.ToImmutableAndFree() : default; | ||
var (sourceValues, sourceInputs) = GetValuesAndInputs(sourceTable, previousTable, newTable); | ||
|
||
if (previousTable.IsEmpty) | ||
CyrusNajmabadi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we initialize this with
sourceTable.Count
as well?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. that would be a reasonable approximation. The only reasons i didn't were that:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair enough.