Skip to content

Collection expressions: optimize List<T> construction#70197

Merged
cston merged 16 commits intodotnet:features/CollectionLiteralsfrom
cston:list-construct
Oct 5, 2023
Merged

Collection expressions: optimize List<T> construction#70197
cston merged 16 commits intodotnet:features/CollectionLiteralsfrom
cston:list-construct

Conversation

@cston
Copy link
Contributor

@cston cston commented Sep 30, 2023

When constructing a List<T> instance when the collection length is known and the collection satisfies certain heuristics:

  • Use CollectionsMarshal.SetCount<T>(List<T>, int) and CollectionsMarshal.AsSpan<T>(List<T>) if available
  • Fall back to List<T>..ctor(int capacity) if CollectionsMarshal is not available.

Initial commits are from #69875.

Relates to test plan #66418

@ghost ghost added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Sep 30, 2023
// while also allowing using the known length for common cases. In particular, this allows
// using the known length for simple concatenation of two elements [e, ..y] or [..x, ..y].
// Temporaries are only needed up to the last spread, so this also allows [..x, e1, e2, ...].
const int maxTemporaries = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

:'(

indexTemp,
_factory.Binary(BinaryOperatorKind.Addition, indexTemp.Type, indexTemp, _factory.Literal(1)),
isRef: false,
indexTemp.Type));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Before the first spread element, the compiler should emit constants indices rather than using an index variable.

(byte)SignatureTypeCode.TypeHandle, (byte)SpecialType.System_Int32,

// System_Collections_Generic_List_T__ctor
(byte)MemberFlags.Constructor, // Flags
Copy link
Member

Choose a reason for hiding this comment

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

nit: the indent on comments below is messed up

@RikkiGibson
Copy link
Member

Here are the commits introduced after #69875: https://github.com/dotnet/roslyn/pull/70197/files/2f4cd81c92dfe4a6bb58ed4bcd011ba6f64e4589..acaacd8483cbe34ee6d6f880f6d1c62eb257c3b3

@333fred and I should be able to sign off on this PR just by reviewing those 3 commits.

return includesIEnumerable(compilation, interfaces);
}

static bool includesIEnumerable(CSharpCompilation compilation, ImmutableArray<NamedTypeSymbol> allInterfaces)
Copy link
Member

@jcouv jcouv Oct 4, 2023

Choose a reason for hiding this comment

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

This refactoring (extracting a local function) doesn't seem particularly useful... #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The refactoring occurred before extracting GetAllInterfacesOrEffectiveInterfaces(). Merged.

_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ctorInt32, diagnostics, syntax: syntax);
_ = GetWellKnownTypeMember(WellKnownMember.System_Collections_Generic_List_T__ToArray, diagnostics, syntax: syntax);

if (collectionTypeKind is CollectionExpressionTypeKind.List)
Copy link
Member

@RikkiGibson RikkiGibson Oct 4, 2023

Choose a reason for hiding this comment

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

I didn't understand why we need Add but not ToArray when targeting to List and ToArray but not Add when targeting to list interface. #Resolved

Copy link
Contributor Author

@cston cston Oct 4, 2023

Choose a reason for hiding this comment

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

For list interface, we may use List<T>.Add. Updated.

localsBuilder,
numberIncludingLastSpread,
sideEffects,
(ArrayBuilder<BoundExpression> expressions, BoundExpression arrayTemp, BoundExpression rewrittenValue) =>
Copy link
Member

@jcouv jcouv Oct 4, 2023

Choose a reason for hiding this comment

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

nit: On the other hand, the refactoring here (inlining of the local function) seems negative to me as it makes the main body of the method harder to follow (much logic inside the lambda now). Consider keeping original structure.
I'd probably also use a local function for the other call to AddCollectionExpressionElements below (line 592) #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will keep as is, since there are three separate callers of AddCollectionExpressionElements() that each pass a lambda expression with inline method.

// Do not use optimizations in async method since the optimizations require Span<T>.
if (useKnownLength && elements.Length > 0 && _factory.CurrentFunction?.IsAsync == false)
{
setCount = ((MethodSymbol?)_compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_InteropServices_CollectionsMarshal__SetCount_T))?.Construct(typeArguments);
Copy link
Member

@jcouv jcouv Oct 4, 2023

Choose a reason for hiding this comment

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

In a custom BCL scenario, we'd want to check constraints. I'm okay to leave as-is as long as this was an explicit choice. It feels like the team has been more comfortable making assumptions about the well-known BCL APIs recently...

Also obsolete

@333fred
Copy link
Member

333fred commented Oct 4, 2023

It looks like @RikkiGibson and @jcouv are looking at this one, so I'll look at a different PR.

@333fred 333fred assigned RikkiGibson and unassigned 333fred Oct 4, 2023
MethodSymbol? asSpan = null;

// Do not use optimizations in async method since the optimizations require Span<T>.
if (useKnownLength && elements.Length > 0 && _factory.CurrentFunction?.IsAsync == false)
Copy link
Member

@jcouv jcouv Oct 4, 2023

Choose a reason for hiding this comment

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

elements.Length > 0

Do we have a test for this condition (empty collection)?
Same question for useKnownLength. I assume that's covered, but couldn't easily spot the test for that #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ListConstruction_02 includes a test for the empty collection. We had several existing tests that hit the unknown length but added an explicit test: ListConstruction_03.

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

Done with review pass (iteration 15). Only reviewed from commit 12. Minor comments/questions only

Copy link
Member

@jcouv jcouv left a comment

Choose a reason for hiding this comment

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

LGTM Thanks (iterations 12 to 16)

@jcouv jcouv requested a review from RikkiGibson October 5, 2023 00:00
Copy link
Member

@jjonescz jjonescz left a comment

Choose a reason for hiding this comment

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

LGTM (commits 13-16)


private BoundExpression GetKnownLengthExpression(ImmutableArray<BoundExpression> elements, int numberIncludingLastSpread, ArrayBuilder<BoundLocal> rewrittenExpressions)
{
Debug.Assert(rewrittenExpressions.Count >= numberIncludingLastSpread);
Copy link
Member

Choose a reason for hiding this comment

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

nit: consider adding a comment why rewrittenExpressions can have more items

@cston cston changed the base branch from release/dev17.8 to features/CollectionLiterals October 5, 2023 16:49
@cston cston merged commit b04edcf into dotnet:features/CollectionLiterals Oct 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-Compilers Feature - Collection Expressions untriaged Issues and PRs which have not yet been triaged by a lead

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants