-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Miscellaneous performance fixes for Crossgen2 #758
Changes from 2 commits
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 |
---|---|---|
|
@@ -4,7 +4,6 @@ | |
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Security.Cryptography; | ||
using System.Text; | ||
|
||
|
@@ -144,7 +143,7 @@ private string SanitizeNameWithHash(string literal) | |
/// <summary> | ||
/// Dictionary given a mangled name for a given <see cref="TypeDesc"/> | ||
/// </summary> | ||
private ImmutableDictionary<TypeDesc, string> _mangledTypeNames = ImmutableDictionary<TypeDesc, string>.Empty; | ||
private Dictionary<TypeDesc, string> _mangledTypeNames = new Dictionary<TypeDesc, string>(); | ||
|
||
/// <summary> | ||
/// Given a set of names <param name="set"/> check if <param name="origName"/> | ||
|
@@ -166,11 +165,14 @@ private string DisambiguateName(string origName, ISet<string> set) | |
|
||
public override string GetMangledTypeName(TypeDesc type) | ||
{ | ||
string mangledName; | ||
if (_mangledTypeNames.TryGetValue(type, out mangledName)) | ||
return mangledName; | ||
lock (this) | ||
{ | ||
string mangledName; | ||
if (_mangledTypeNames.TryGetValue(type, out mangledName)) | ||
return mangledName; | ||
|
||
return ComputeMangledTypeName(type); | ||
return ComputeMangledTypeName(type); | ||
} | ||
} | ||
|
||
private string EnterNameScopeSequence => _mangleForCplusPlus ? "_A_" : "<"; | ||
|
@@ -273,12 +275,11 @@ private string ComputeMangledTypeName(TypeDesc type) | |
// Ensure that name is unique and update our tables accordingly. | ||
name = DisambiguateName(name, deduplicator); | ||
deduplicator.Add(name); | ||
_mangledTypeNames = _mangledTypeNames.Add(t, name); | ||
_mangledTypeNames.Add(t, name); | ||
} | ||
} | ||
return _mangledTypeNames[type]; | ||
} | ||
|
||
return _mangledTypeNames[type]; | ||
} | ||
|
||
string mangledName; | ||
|
@@ -351,14 +352,14 @@ private string ComputeMangledTypeName(TypeDesc type) | |
{ | ||
// Ensure that name is unique and update our tables accordingly. | ||
if (!_mangledTypeNames.ContainsKey(type)) | ||
_mangledTypeNames = _mangledTypeNames.Add(type, mangledName); | ||
_mangledTypeNames.Add(type, mangledName); | ||
} | ||
|
||
return mangledName; | ||
} | ||
|
||
private ImmutableDictionary<MethodDesc, Utf8String> _mangledMethodNames = ImmutableDictionary<MethodDesc, Utf8String>.Empty; | ||
private ImmutableDictionary<MethodDesc, Utf8String> _unqualifiedMangledMethodNames = ImmutableDictionary<MethodDesc, Utf8String>.Empty; | ||
private Dictionary<MethodDesc, Utf8String> _mangledMethodNames = new Dictionary<MethodDesc, Utf8String>(); | ||
private Dictionary<MethodDesc, Utf8String> _unqualifiedMangledMethodNames = new Dictionary<MethodDesc, Utf8String>(); | ||
|
||
public override Utf8String GetMangledMethodName(MethodDesc method) | ||
{ | ||
|
@@ -381,7 +382,7 @@ public override Utf8String GetMangledMethodName(MethodDesc method) | |
lock (this) | ||
{ | ||
if (!_mangledMethodNames.ContainsKey(method)) | ||
_mangledMethodNames = _mangledMethodNames.Add(method, utf8MangledName); | ||
_mangledMethodNames.Add(method, utf8MangledName); | ||
} | ||
|
||
return utf8MangledName; | ||
|
@@ -390,11 +391,14 @@ public override Utf8String GetMangledMethodName(MethodDesc method) | |
|
||
private Utf8String GetUnqualifiedMangledMethodName(MethodDesc method) | ||
{ | ||
Utf8String mangledName; | ||
if (_unqualifiedMangledMethodNames.TryGetValue(method, out mangledName)) | ||
return mangledName; | ||
lock (this) | ||
{ | ||
Utf8String mangledName; | ||
if (_unqualifiedMangledMethodNames.TryGetValue(method, out mangledName)) | ||
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. Same here... if we make _unqualifiedMangledMethodNames a lock-free dictionary, we can avoid locking while looking up. We only need locking while adding new entries |
||
return mangledName; | ||
|
||
return ComputeUnqualifiedMangledMethodName(method); | ||
return ComputeUnqualifiedMangledMethodName(method); | ||
} | ||
} | ||
|
||
private Utf8String GetPrefixMangledTypeName(IPrefixMangledType prefixMangledType) | ||
|
@@ -481,12 +485,11 @@ private Utf8String ComputeUnqualifiedMangledMethodName(MethodDesc method) | |
name = DisambiguateName(name, deduplicator); | ||
deduplicator.Add(name); | ||
|
||
_unqualifiedMangledMethodNames = _unqualifiedMangledMethodNames.Add(m, name); | ||
_unqualifiedMangledMethodNames.Add(m, name); | ||
} | ||
} | ||
return _unqualifiedMangledMethodNames[method]; | ||
} | ||
|
||
return _unqualifiedMangledMethodNames[method]; | ||
} | ||
|
||
Utf8String utf8MangledName; | ||
|
@@ -550,22 +553,25 @@ private Utf8String ComputeUnqualifiedMangledMethodName(MethodDesc method) | |
lock (this) | ||
{ | ||
if (!_unqualifiedMangledMethodNames.ContainsKey(method)) | ||
_unqualifiedMangledMethodNames = _unqualifiedMangledMethodNames.Add(method, utf8MangledName); | ||
_unqualifiedMangledMethodNames.Add(method, utf8MangledName); | ||
} | ||
} | ||
|
||
return utf8MangledName; | ||
} | ||
|
||
private ImmutableDictionary<FieldDesc, Utf8String> _mangledFieldNames = ImmutableDictionary<FieldDesc, Utf8String>.Empty; | ||
private Dictionary<FieldDesc, Utf8String> _mangledFieldNames = new Dictionary<FieldDesc, Utf8String>(); | ||
|
||
public override Utf8String GetMangledFieldName(FieldDesc field) | ||
{ | ||
Utf8String mangledName; | ||
if (_mangledFieldNames.TryGetValue(field, out mangledName)) | ||
return mangledName; | ||
lock (this) | ||
{ | ||
Utf8String mangledName; | ||
if (_mangledFieldNames.TryGetValue(field, out mangledName)) | ||
return mangledName; | ||
|
||
return ComputeMangledFieldName(field); | ||
return ComputeMangledFieldName(field); | ||
} | ||
} | ||
|
||
private Utf8String ComputeMangledFieldName(FieldDesc field) | ||
|
@@ -594,12 +600,11 @@ private Utf8String ComputeMangledFieldName(FieldDesc field) | |
if (prependTypeName != null) | ||
name = prependTypeName + "__" + name; | ||
|
||
_mangledFieldNames = _mangledFieldNames.Add(f, name); | ||
_mangledFieldNames.Add(f, name); | ||
} | ||
} | ||
return _mangledFieldNames[field]; | ||
} | ||
|
||
return _mangledFieldNames[field]; | ||
} | ||
|
||
|
||
|
@@ -613,26 +618,29 @@ private Utf8String ComputeMangledFieldName(FieldDesc field) | |
lock (this) | ||
{ | ||
if (!_mangledFieldNames.ContainsKey(field)) | ||
_mangledFieldNames = _mangledFieldNames.Add(field, utf8MangledName); | ||
_mangledFieldNames.Add(field, utf8MangledName); | ||
} | ||
|
||
return utf8MangledName; | ||
} | ||
|
||
private ImmutableDictionary<string, string> _mangledStringLiterals = ImmutableDictionary<string, string>.Empty; | ||
private Dictionary<string, string> _mangledStringLiterals = new Dictionary<string, string>(); | ||
|
||
public override string GetMangledStringName(string literal) | ||
{ | ||
string mangledName; | ||
if (_mangledStringLiterals.TryGetValue(literal, out mangledName)) | ||
return mangledName; | ||
lock (this) | ||
{ | ||
if (_mangledStringLiterals.TryGetValue(literal, out mangledName)) | ||
return mangledName; | ||
} | ||
|
||
mangledName = SanitizeNameWithHash(literal); | ||
|
||
lock (this) | ||
{ | ||
if (!_mangledStringLiterals.ContainsKey(literal)) | ||
_mangledStringLiterals = _mangledStringLiterals.Add(literal, mangledName); | ||
_mangledStringLiterals.Add(literal, mangledName); | ||
} | ||
|
||
return mangledName; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
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. I can see that you removed usage of these apis, but this file seems to still be here. Please remove this file. 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. Oh oops, yes. |
||
using ILCompiler.DependencyAnalysis.ReadyToRun; | ||
using Internal.JitInterface; | ||
using Internal.ReadyToRunConstants; | ||
|
||
namespace System | ||
{ | ||
/// <summary> | ||
/// Enum.CompareTo takes an Object, so using that requires boxing the enum we are comparing to (which is slow | ||
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. I do not think it makes sense to have these in the shared file. Instead, I would just add casts to int to every place where this is used (like what I have suggested below). |
||
/// and allocates unnecessarily). | ||
/// </summary> | ||
internal static class EnumExtensions | ||
{ | ||
public static int CompareEnum(this ReadyToRunFixupKind first, ReadyToRunFixupKind second) | ||
{ | ||
return (int)first - (int)second; | ||
} | ||
|
||
public static int CompareEnum(this ImportThunk.Kind first, ImportThunk.Kind second) | ||
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. If the type is not in Common, code using the type should not be in Common. +1 to Jan's advice. |
||
{ | ||
return (int)first - (int)second; | ||
} | ||
|
||
public static int CompareEnum(this mdToken first, mdToken second) | ||
{ | ||
return (int)first - (int)second; | ||
} | ||
|
||
public static int CompareEnum(this CORINFO_RUNTIME_LOOKUP_KIND first, CORINFO_RUNTIME_LOOKUP_KIND second) | ||
{ | ||
return (int)first - (int)second; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
using System.Collections.Immutable; | ||
using System.Reflection.Metadata; | ||
using System.Reflection.Metadata.Ecma335; | ||
using System.Reflection.PortableExecutable; | ||
using Internal.Text; | ||
using Internal.TypeSystem; | ||
using Internal.TypeSystem.Ecma; | ||
|
@@ -61,8 +62,6 @@ private unsafe byte[] GetRvaData(int targetPointerSize) | |
BlobReader metadataBlob = new BlobReader(_module.PEReader.GetMetadata().Pointer, _module.PEReader.GetMetadata().Length); | ||
metadataBlob.Offset = metadataReader.GetTableMetadataOffset(TableIndex.FieldRva); | ||
|
||
ImmutableArray<byte> memBlock = _module.PEReader.GetSectionData(_rva).GetContent(); | ||
|
||
for (int i = 1; i <= metadataReader.GetTableRowCount(TableIndex.FieldRva); i++) | ||
{ | ||
int currentFieldRva = metadataBlob.ReadInt32(); | ||
|
@@ -76,18 +75,20 @@ private unsafe byte[] GetRvaData(int targetPointerSize) | |
int currentSize = field.FieldType.GetElementSize().AsInt; | ||
if (currentSize > size) | ||
{ | ||
if (currentSize > memBlock.Length) | ||
throw new BadImageFormatException(); | ||
|
||
// We need to handle overlapping fields by reusing blobs based on the rva, and just update | ||
// the size and contents | ||
size = currentSize; | ||
result = new byte[AlignmentHelper.AlignUp(size, targetPointerSize)]; | ||
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. We can also move the array allocation outside of the loop. No need to allocate and resize now |
||
memBlock.CopyTo(0, result, 0, size); | ||
} | ||
} | ||
|
||
Debug.Assert(size > 0); | ||
|
||
PEMemoryBlock block = _module.PEReader.GetSectionData(_rva); | ||
if (block.Length < size) | ||
throw new BadImageFormatException(); | ||
|
||
block.GetContent(0, size).CopyTo(result); | ||
return result; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -56,7 +56,7 @@ public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilde | |||||
public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) | ||||||
{ | ||||||
FieldFixupSignature otherNode = (FieldFixupSignature)other; | ||||||
int result = _fixupKind.CompareTo(otherNode._fixupKind); | ||||||
int result = _fixupKind.CompareEnum(otherNode._fixupKind); | ||||||
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.
Suggested change
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. Why not just 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. No need to call Applies to all other places 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. Note that the subtraction does not work to compare arbitrary integers. It only works if the integers are small. It is probably the case here, but then you need to think about all the time about whether you are dealing with small integers or not. 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. Might be an opportunity to improve the codegen for
(Might also be able to use a conditional move, although it doesn't seem that necessary.) |
||||||
if (result != 0) | ||||||
return result; | ||||||
|
||||||
|
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.
Can we break down the locking hierarchy so that there's not a single giant lock for type names, method names, field names, and mangled strings, but instead there's a lock for each?
In general, I'm worried that we're probably not using this from multithreaded code at all right now, so this looks like an improvement, but the locks will become a bottleneck as soon as we start doing 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.
How about using the lock free concurrent dictionaries here, to avoid locking while performing the lookup?
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.
@MichalStrehovsky We are using this from multithreaded code at the moment. I tried using more granulated locking and it didn't seem to make a performance difference. @davidwrighton and I thought that, since there's no performance difference right now, it would be better to keep it in this form where it's clear to people reading it that there are no deadlocks.
@fadimounir That's an option, but then it gets a little hard to analyze because it's hard to reason about whether each of the GetOrAdd functions must only be run once and so has to be either locked or wrapped in a Lazy. This + the limited time left is why we didn't end up using that right now, but perhaps @davidwrighton can provide more direction.
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.
What do you mean? We can always do a .TryGetValue outside of a lock, and .TryAdd inside a lock (
Dictionary
has the same APIs too so we can use it as well). Locking while retrieving a value is not idealThere 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.
Oh I see what you mean now. Yes I could change it to do that, but I'm not sure whether we even have contention there.
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.
When using Dictionary you cannot use the TryGetValue apis outside of a lock. This worked before with ImmutableDictionary as that api allows lock-free reads. My expectation is that ConcurrentDictionary would be better in performance here than Dictionary, but I don't want to see us use that without seeing the performance impact in a profiler. I think we should check Anubhav's work in here as written, and explore finding even more performance in the future.