Skip to content

Commit

Permalink
Reduce closure and Lazy allocations inside CSharpCompilation.ctor (#7…
Browse files Browse the repository at this point in the history
…2371)

These account for about 0.3% of all allocations in the CopyPlainText speedometer test.
  • Loading branch information
ToddGrun authored Mar 7, 2024
1 parent 0c56e47 commit 5839507
Show file tree
Hide file tree
Showing 9 changed files with 64 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ private void BinaryOperatorEasyOut(BinaryOperatorKind kind, BoundExpression left
return;
}

BinaryOperatorSignature signature = this.Compilation.builtInOperators.GetSignature(easyOut);
BinaryOperatorSignature signature = this.Compilation.BuiltInOperators.GetSignature(easyOut);

Conversion leftConversion = Conversions.FastClassifyConversion(leftType, signature.LeftType);
Conversion rightConversion = Conversions.FastClassifyConversion(rightType, signature.RightType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ private void GetAllBuiltInOperators(BinaryOperatorKind kind, bool isChecked, Bou
}
else
{
this.Compilation.builtInOperators.GetSimpleBuiltInOperators(kind, operators, skipNativeIntegerOperators: !left.Type.IsNativeIntegerOrNullableThereof() && !right.Type.IsNativeIntegerOrNullableThereof());
this.Compilation.BuiltInOperators.GetSimpleBuiltInOperators(kind, operators, skipNativeIntegerOperators: !left.Type.IsNativeIntegerOrNullableThereof() && !right.Type.IsNativeIntegerOrNullableThereof());

// SPEC 7.3.4: For predefined enum and delegate operators, the only operators
// considered are those defined by an enum or delegate type that is the binding
Expand All @@ -734,7 +734,7 @@ private void GetAllBuiltInOperators(BinaryOperatorKind kind, bool isChecked, Bou
isUtf8ByteRepresentation(left) &&
isUtf8ByteRepresentation(right))
{
this.Compilation.builtInOperators.GetUtf8ConcatenationBuiltInOperator(left.Type, operators);
this.Compilation.BuiltInOperators.GetUtf8ConcatenationBuiltInOperator(left.Type, operators);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private void UnaryOperatorEasyOut(UnaryOperatorKind kind, BoundExpression operan
return;
}

UnaryOperatorSignature signature = this.Compilation.builtInOperators.GetSignature(easyOut);
UnaryOperatorSignature signature = this.Compilation.BuiltInOperators.GetSignature(easyOut);

Conversion? conversion = Conversions.FastClassifyConversion(operandType, signature.OperandType);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ private void GetAllBuiltInOperators(UnaryOperatorKind kind, bool isChecked, Boun
// specification to match the previous implementation.

var operators = ArrayBuilder<UnaryOperatorSignature>.GetInstance();
this.Compilation.builtInOperators.GetSimpleBuiltInOperators(kind, operators, skipNativeIntegerOperators: !operand.Type.IsNativeIntegerOrNullableThereof());
this.Compilation.BuiltInOperators.GetSimpleBuiltInOperators(kind, operators, skipNativeIntegerOperators: !operand.Type.IsNativeIntegerOrNullableThereof());

GetEnumOperations(kind, operand, operators);

Expand Down
73 changes: 47 additions & 26 deletions src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,12 @@ public sealed partial class CSharpCompilation : Compilation
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

private readonly CSharpCompilationOptions _options;
private readonly Lazy<UsingsFromOptionsAndDiagnostics> _usingsFromOptions;
private readonly Lazy<ImmutableArray<NamespaceOrTypeAndUsingDirective>> _globalImports;
private readonly Lazy<Imports> _previousSubmissionImports;
private readonly Lazy<AliasSymbol> _globalNamespaceAlias; // alias symbol used to resolve "global::".
private readonly Lazy<ImplicitNamedTypeSymbol?> _scriptClass;
private UsingsFromOptionsAndDiagnostics? _lazyUsingsFromOptions;
private ImmutableArray<NamespaceOrTypeAndUsingDirective> _lazyGlobalImports;
private Imports? _lazyPreviousSubmissionImports;
private AliasSymbol? _lazyGlobalNamespaceAlias; // alias symbol used to resolve "global::".

private NamedTypeSymbol? _lazyScriptClass = ErrorTypeSymbol.UnknownResultType;

// The type of host object model if available.
private TypeSymbol? _lazyHostObjectTypeSymbol;
Expand Down Expand Up @@ -92,11 +93,11 @@ internal Conversions Conversions
/// <summary>
/// Manages anonymous types declared in this compilation. Unifies types that are structurally equivalent.
/// </summary>
private readonly AnonymousTypeManager _anonymousTypeManager;
private AnonymousTypeManager? _lazyAnonymousTypeManager;

private NamespaceSymbol? _lazyGlobalNamespace;

internal readonly BuiltInOperators builtInOperators;
private BuiltInOperators? _lazyBuiltInOperators;

/// <summary>
/// The <see cref="SourceAssemblySymbol"/> for this compilation. Do not access directly, use Assembly property
Expand Down Expand Up @@ -152,7 +153,7 @@ internal Conversions Conversions
/// <summary>
/// Cache of T to Nullable&lt;T&gt;.
/// </summary>
private readonly ConcurrentCache<TypeSymbol, NamedTypeSymbol> _typeToNullableVersion = new ConcurrentCache<TypeSymbol, NamedTypeSymbol>(size: 100);
private ConcurrentCache<TypeSymbol, NamedTypeSymbol>? _lazyTypeToNullableVersion;

/// <summary>Lazily caches SyntaxTrees by their mapped path. Used to look up the syntax tree referenced by an interceptor (temporary compat behavior).</summary>
/// <remarks>Must be removed prior to interceptors stable release.</remarks>
Expand Down Expand Up @@ -188,11 +189,19 @@ public override bool IsCaseSensitive
}
}

internal BuiltInOperators BuiltInOperators
{
get
{
return InterlockedOperations.Initialize(ref _lazyBuiltInOperators, static self => new BuiltInOperators(self), this);
}
}

internal AnonymousTypeManager AnonymousTypeManager
{
get
{
return _anonymousTypeManager;
return InterlockedOperations.Initialize(ref _lazyAnonymousTypeManager, static self => new AnonymousTypeManager(self), this);
}
}

Expand Down Expand Up @@ -469,16 +478,8 @@ private CSharpCompilation(
AsyncQueue<CompilationEvent>? eventQueue = null)
: base(assemblyName, references, features, isSubmission, semanticModelProvider, eventQueue)
{
WellKnownMemberSignatureComparer = new WellKnownMembersSignatureComparer(this);
_options = options;

this.builtInOperators = new BuiltInOperators(this);
_scriptClass = new Lazy<ImplicitNamedTypeSymbol?>(BindScriptClass);
_globalImports = new Lazy<ImmutableArray<NamespaceOrTypeAndUsingDirective>>(BindGlobalImports);
_usingsFromOptions = new Lazy<UsingsFromOptionsAndDiagnostics>(BindUsingsFromOptions);
_previousSubmissionImports = new Lazy<Imports>(ExpandPreviousSubmissionImports);
_globalNamespaceAlias = new Lazy<AliasSymbol>(CreateGlobalNamespaceAlias);
_anonymousTypeManager = new AnonymousTypeManager(this);
this.LanguageVersion = CommonLanguageVersion(syntaxAndDeclarations.ExternalSyntaxTrees);

if (isSubmission)
Expand Down Expand Up @@ -1503,7 +1504,15 @@ internal bool GetExternAliasTarget(string aliasName, out NamespaceSymbol @namesp
/// </summary>
internal new NamedTypeSymbol? ScriptClass
{
get { return _scriptClass.Value; }
get
{
if (ReferenceEquals(_lazyScriptClass, ErrorTypeSymbol.UnknownResultType))
{
Interlocked.CompareExchange(ref _lazyScriptClass, BindScriptClass()!, ErrorTypeSymbol.UnknownResultType);
}

return _lazyScriptClass;
}
}

/// <summary>
Expand All @@ -1526,7 +1535,8 @@ internal bool IsSubmissionSyntaxTree(SyntaxTree tree)
/// <summary>
/// Global imports (including those from previous submissions, if there are any).
/// </summary>
internal ImmutableArray<NamespaceOrTypeAndUsingDirective> GlobalImports => _globalImports.Value;
internal ImmutableArray<NamespaceOrTypeAndUsingDirective> GlobalImports
=> InterlockedOperations.Initialize(ref _lazyGlobalImports, static self => self.BindGlobalImports(), arg: this);

private ImmutableArray<NamespaceOrTypeAndUsingDirective> BindGlobalImports()
{
Expand Down Expand Up @@ -1565,7 +1575,8 @@ private ImmutableArray<NamespaceOrTypeAndUsingDirective> BindGlobalImports()
/// <summary>
/// Global imports not including those from previous submissions.
/// </summary>
private UsingsFromOptionsAndDiagnostics UsingsFromOptions => _usingsFromOptions.Value;
private UsingsFromOptionsAndDiagnostics UsingsFromOptions
=> InterlockedOperations.Initialize(ref _lazyUsingsFromOptions, static self => self.BindUsingsFromOptions(), this);

private UsingsFromOptionsAndDiagnostics BindUsingsFromOptions() => UsingsFromOptionsAndDiagnostics.FromOptions(this);

Expand All @@ -1590,7 +1601,8 @@ internal Imports GetSubmissionImports()
/// <summary>
/// Imports from all previous submissions.
/// </summary>
internal Imports GetPreviousSubmissionImports() => _previousSubmissionImports.Value;
internal Imports GetPreviousSubmissionImports()
=> InterlockedOperations.Initialize(ref _lazyPreviousSubmissionImports, static self => self.ExpandPreviousSubmissionImports(), this);

private Imports ExpandPreviousSubmissionImports()
{
Expand All @@ -1610,7 +1622,7 @@ internal AliasSymbol GlobalNamespaceAlias
{
get
{
return _globalNamespaceAlias.Value;
return InterlockedOperations.Initialize(ref _lazyGlobalNamespaceAlias, static self => self.CreateGlobalNamespaceAlias(), this);
}
}

Expand Down Expand Up @@ -1639,6 +1651,14 @@ internal AliasSymbol GlobalNamespaceAlias
return result;
}

private ConcurrentCache<TypeSymbol, NamedTypeSymbol> TypeToNullableVersion
{
get
{
return InterlockedOperations.Initialize(ref _lazyTypeToNullableVersion, static () => new ConcurrentCache<TypeSymbol, NamedTypeSymbol>(size: 100));
}
}

/// <summary>
/// Given a provided <paramref name="typeArgument"/>, gives back <see cref="Nullable{T}"/> constructed with that
/// argument. This function is only intended to be used for very common instantiations produced heavily during
Expand All @@ -1653,10 +1673,11 @@ internal NamedTypeSymbol GetOrCreateNullableType(TypeSymbol typeArgument)
Debug.Fail($"Unsupported type argument: {typeArgument.ToDisplayString()}");
#endif

if (!_typeToNullableVersion.TryGetValue(typeArgument, out var constructedNullableInstance))
var typeToNullableVersion = TypeToNullableVersion;
if (!typeToNullableVersion.TryGetValue(typeArgument, out var constructedNullableInstance))
{
constructedNullableInstance = this.GetSpecialType(SpecialType.System_Nullable_T).Construct(typeArgument);
_typeToNullableVersion.TryAdd(typeArgument, constructedNullableInstance);
typeToNullableVersion.TryAdd(typeArgument, constructedNullableInstance);
}

return constructedNullableInstance;
Expand Down Expand Up @@ -4198,7 +4219,7 @@ csharpLeftType.TypeKind is TypeKind.Dynamic ||

if (easyOutBinaryKind != BinaryOperatorKind.Error)
{
var signature = this.builtInOperators.GetSignature(easyOutBinaryKind);
var signature = this.BuiltInOperators.GetSignature(easyOutBinaryKind);
if (csharpReturnType.SpecialType == signature.ReturnType.SpecialType &&
csharpLeftType.SpecialType == signature.LeftType.SpecialType &&
csharpRightType.SpecialType == signature.RightType.SpecialType)
Expand Down Expand Up @@ -4421,7 +4442,7 @@ void validateSignature()

if (easyOutUnaryKind != UnaryOperatorKind.Error)
{
var signature = this.builtInOperators.GetSignature(easyOutUnaryKind);
var signature = this.BuiltInOperators.GetSignature(easyOutUnaryKind);
if (csharpReturnType.SpecialType == signature.ReturnType.SpecialType &&
csharpOperandType.SpecialType == signature.OperandType.SpecialType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.CSharp
{
public partial class CSharpCompilation
{
internal readonly WellKnownMembersSignatureComparer WellKnownMemberSignatureComparer;
private WellKnownMembersSignatureComparer? _lazyWellKnownMemberSignatureComparer;

/// <summary>
/// An array of cached well known types available for use in this Compilation.
Expand All @@ -35,6 +35,9 @@ public partial class CSharpCompilation
private int _needsGeneratedAttributes;
private bool _needsGeneratedAttributes_IsFrozen;

internal WellKnownMembersSignatureComparer WellKnownMemberSignatureComparer
=> InterlockedOperations.Initialize(ref _lazyWellKnownMemberSignatureComparer, static self => new WellKnownMembersSignatureComparer(self), this);

/// <summary>
/// Returns a value indicating which embedded attributes should be generated during emit phase.
/// The value is set during binding the symbols that need those attributes, and is frozen on first trial to get it.
Expand Down
4 changes: 2 additions & 2 deletions src/Compilers/CSharp/Test/Emit2/Emit/NumericIntPtrTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1537,7 +1537,7 @@ static void verifyOperators(CSharpCompilation comp)
static void verifyUnaryOperators(CSharpCompilation comp, UnaryOperatorKind operatorKind, bool skipNativeIntegerOperators)
{
var builder = ArrayBuilder<UnaryOperatorSignature>.GetInstance();
comp.builtInOperators.GetSimpleBuiltInOperators(operatorKind, builder, skipNativeIntegerOperators);
comp.BuiltInOperators.GetSimpleBuiltInOperators(operatorKind, builder, skipNativeIntegerOperators);
var operators = builder.ToImmutableAndFree();
int expectedSigned = skipNativeIntegerOperators ? 0 : 1;
int expectedUnsigned = skipNativeIntegerOperators ? 0 : (operatorKind == UnaryOperatorKind.UnaryMinus) ? 0 : 1;
Expand All @@ -1548,7 +1548,7 @@ static void verifyUnaryOperators(CSharpCompilation comp, UnaryOperatorKind opera
static void verifyBinaryOperators(CSharpCompilation comp, BinaryOperatorKind operatorKind, bool skipNativeIntegerOperators)
{
var builder = ArrayBuilder<BinaryOperatorSignature>.GetInstance();
comp.builtInOperators.GetSimpleBuiltInOperators(operatorKind, builder, skipNativeIntegerOperators);
comp.BuiltInOperators.GetSimpleBuiltInOperators(operatorKind, builder, skipNativeIntegerOperators);
var operators = builder.ToImmutableAndFree();
int expected = skipNativeIntegerOperators ? 0 : 1;
verifyOperators(operators, (op, signed) => isNativeInt(op.LeftType, signed), expected, expected);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4789,7 +4789,7 @@ static void verifyOperators(CSharpCompilation comp)
static void verifyUnaryOperators(CSharpCompilation comp, UnaryOperatorKind operatorKind, bool skipNativeIntegerOperators)
{
var builder = ArrayBuilder<UnaryOperatorSignature>.GetInstance();
comp.builtInOperators.GetSimpleBuiltInOperators(operatorKind, builder, skipNativeIntegerOperators);
comp.BuiltInOperators.GetSimpleBuiltInOperators(operatorKind, builder, skipNativeIntegerOperators);
var operators = builder.ToImmutableAndFree();
int expectedSigned = skipNativeIntegerOperators ? 0 : 1;
int expectedUnsigned = skipNativeIntegerOperators ? 0 : (operatorKind == UnaryOperatorKind.UnaryMinus) ? 0 : 1;
Expand All @@ -4800,7 +4800,7 @@ static void verifyUnaryOperators(CSharpCompilation comp, UnaryOperatorKind opera
static void verifyBinaryOperators(CSharpCompilation comp, BinaryOperatorKind operatorKind, bool skipNativeIntegerOperators)
{
var builder = ArrayBuilder<BinaryOperatorSignature>.GetInstance();
comp.builtInOperators.GetSimpleBuiltInOperators(operatorKind, builder, skipNativeIntegerOperators);
comp.BuiltInOperators.GetSimpleBuiltInOperators(operatorKind, builder, skipNativeIntegerOperators);
var operators = builder.ToImmutableAndFree();
int expected = skipNativeIntegerOperators ? 0 : 1;
verifyOperators(operators, (op, signed) => isNativeInt(op.LeftType, signed), expected, expected);
Expand Down
8 changes: 4 additions & 4 deletions src/Compilers/CSharp/Test/Semantic/Semantics/OperatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7593,7 +7593,7 @@ ExpressionSyntax node4
}
else
{
signature = compilation.builtInOperators.GetSignature(result);
signature = compilation.BuiltInOperators.GetSignature(result);

if ((object)underlying != (object)type)
{
Expand Down Expand Up @@ -8232,14 +8232,14 @@ ExpressionSyntax node8
else if ((op == BinaryOperatorKind.Addition || op == BinaryOperatorKind.Subtraction) &&
leftType.IsEnumType() && (rightType.IsIntegralType() || rightType.IsCharType()) &&
(result = OverloadResolution.BinopEasyOut.OpKind(op, leftType.EnumUnderlyingTypeOrSelf(), rightType)) != BinaryOperatorKind.Error &&
TypeSymbol.Equals((signature = compilation.builtInOperators.GetSignature(result)).RightType, leftType.EnumUnderlyingTypeOrSelf(), TypeCompareKind.ConsiderEverything2))
TypeSymbol.Equals((signature = compilation.BuiltInOperators.GetSignature(result)).RightType, leftType.EnumUnderlyingTypeOrSelf(), TypeCompareKind.ConsiderEverything2))
{
signature = new BinaryOperatorSignature(signature.Kind | BinaryOperatorKind.EnumAndUnderlying, leftType, signature.RightType, leftType);
}
else if ((op == BinaryOperatorKind.Addition || op == BinaryOperatorKind.Subtraction) &&
rightType.IsEnumType() && (leftType.IsIntegralType() || leftType.IsCharType()) &&
(result = OverloadResolution.BinopEasyOut.OpKind(op, leftType, rightType.EnumUnderlyingTypeOrSelf())) != BinaryOperatorKind.Error &&
TypeSymbol.Equals((signature = compilation.builtInOperators.GetSignature(result)).LeftType, rightType.EnumUnderlyingTypeOrSelf(), TypeCompareKind.ConsiderEverything2))
TypeSymbol.Equals((signature = compilation.BuiltInOperators.GetSignature(result)).LeftType, rightType.EnumUnderlyingTypeOrSelf(), TypeCompareKind.ConsiderEverything2))
{
signature = new BinaryOperatorSignature(signature.Kind | BinaryOperatorKind.EnumAndUnderlying, signature.LeftType, rightType, rightType);
}
Expand Down Expand Up @@ -8355,7 +8355,7 @@ ExpressionSyntax node8
}
else
{
signature = compilation.builtInOperators.GetSignature(result);
signature = compilation.BuiltInOperators.GetSignature(result);
}

Assert.NotNull(symbol1);
Expand Down

0 comments on commit 5839507

Please sign in to comment.