From 96f65355e757b4539543dfc2c4afd9ab245baa12 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tom=C3=A1=C5=A1=20Rylek?= Date: Fri, 29 Jan 2021 21:10:31 +0100 Subject: [PATCH] Initial implementation of call chain statistics (#47547) Add support for producing "callchain profile quality map", a new auxiliary output file describing code layout properties w.r.t. a given call chain profile. Sample output for System.Private.CoreLib: CHARACTERISTIC | PAIR COUNT | CALL COUNT | PERCENTAGE ---------------------------------------------------------------- ENTRIES TOTAL | 291 | 266229 | 100.00 RESOLVED ENTRIES | 267 | 260172 | 97.72 UNRESOLVED ENTRIES | 24 | 6057 | 2.28 NEAR (INTRA-PAGE) CALLS | 145 | 109055 | 40.96 FAR (CROSS-PAGE) CALLS | 122 | 151117 | 56.76 Thanks Tomas --- .../CodeGen/ReadyToRunObjectWriter.cs | 24 ++- .../Compiler/CallChainProfile.cs | 4 +- .../ILCompiler.ReadyToRun/Compiler/ProfileData.cs | 5 + .../Compiler/ReadyToRunCodegenCompilation.cs | 29 +-- .../ReadyToRunCodegenCompilationBuilder.cs | 8 + .../ILCompiler.ReadyToRun.csproj | 1 + .../ObjectWriter/OutputInfoBuilder.cs | 3 + .../ObjectWriter/ProfileFileBuilder.cs | 205 +++++++++++++++++++++ src/coreclr/tools/aot/crossgen2.sln | 4 +- src/coreclr/tools/aot/crossgen2/Program.cs | 2 + 10 files changed, 269 insertions(+), 16 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 47976ee..c0d107c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -66,6 +66,11 @@ namespace ILCompiler.DependencyAnalysis private readonly SymbolFileBuilder _symbolFileBuilder; /// + /// Set to non-null when generating callchain profile info. + /// + private readonly ProfileFileBuilder _profileFileBuilder; + + /// /// True when the map file builder should emit a textual map file /// private bool _generateMapFile; @@ -130,6 +135,8 @@ namespace ILCompiler.DependencyAnalysis string pdbPath, bool generatePerfMapFile, string perfMapPath, + bool generateProfileFile, + CallChainProfile callChainProfile, int customPESectionAlignment) { _objectFilePath = objectFilePath; @@ -147,7 +154,7 @@ namespace ILCompiler.DependencyAnalysis bool generateMap = (generateMapFile || generateMapCsvFile); bool generateSymbols = (generatePdbFile || generatePerfMapFile); - if (generateMap || generateSymbols) + if (generateMap || generateSymbols || generateProfileFile) { _outputInfoBuilder = new OutputInfoBuilder(); @@ -160,6 +167,11 @@ namespace ILCompiler.DependencyAnalysis { _symbolFileBuilder = new SymbolFileBuilder(_outputInfoBuilder); } + + if (generateProfileFile) + { + _profileFileBuilder = new ProfileFileBuilder(_outputInfoBuilder, callChainProfile, _nodeFactory.Target); + } } } @@ -348,6 +360,12 @@ namespace ILCompiler.DependencyAnalysis } _symbolFileBuilder.SavePerfMap(path, _objectFilePath); } + + if (_profileFileBuilder != null) + { + string path = Path.ChangeExtension(_objectFilePath, ".profile"); + _profileFileBuilder.SaveProfile(path); + } } succeeded = true; @@ -417,6 +435,8 @@ namespace ILCompiler.DependencyAnalysis string pdbPath, bool generatePerfMapFile, string perfMapPath, + bool generateProfileFile, + CallChainProfile callChainProfile, int customPESectionAlignment) { Console.WriteLine($@"Emitting R2R PE file: {objectFilePath}"); @@ -431,6 +451,8 @@ namespace ILCompiler.DependencyAnalysis pdbPath: pdbPath, generatePerfMapFile: generatePerfMapFile, perfMapPath: perfMapPath, + generateProfileFile: generateProfileFile, + callChainProfile, customPESectionAlignment); objectWriter.EmitPortableExecutable(); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs index eaf66f9..97ae366 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/CallChainProfile.cs @@ -323,7 +323,7 @@ namespace ILCompiler Console.WriteLine($"Successfully resolved {_methodsSuccessfullyResolved} methods ({(double)_methodsSuccessfullyResolved / (double)_methodResolvesAttempted:P})"); } #endif - } + public IReadOnlyDictionary> ResolvedProfileData => _resolvedProfileData; + } } - diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs index b8b6135..77cf879 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ProfileData.cs @@ -100,6 +100,7 @@ namespace ILCompiler private readonly HashSet _placedProfileMethodsAll = new HashSet(); private readonly bool _partialNGen; private readonly ReadyToRunCompilationModuleGroupBase _compilationGroup; + private readonly CallChainProfile _callChainProfile; public ProfileDataManager(Logger logger, IEnumerable possibleReferenceModules, @@ -107,11 +108,13 @@ namespace ILCompiler IEnumerable versionBubbleModules, ModuleDesc nonLocalGenericsHome, IReadOnlyList mibcFiles, + CallChainProfile callChainProfile, CompilerTypeSystemContext context, ReadyToRunCompilationModuleGroupBase compilationGroup) { _ibcParser = new IBCProfileParser(logger, possibleReferenceModules); _compilationGroup = compilationGroup; + _callChainProfile = callChainProfile; HashSet versionBubble = new HashSet(versionBubbleModules); { @@ -265,5 +268,7 @@ namespace ILCompiler return profileData; } } + + public CallChainProfile CallChainProfile => _callChainProfile; } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs index ee73ed8..7f42ede 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs @@ -225,20 +225,21 @@ namespace ILCompiler private readonly string _compositeRootPath; - private bool _resilient; + private readonly bool _resilient; - private int _parallelism; + private readonly int _parallelism; - private bool _generateMapFile; - private bool _generateMapCsvFile; - private bool _generatePdbFile; - private Func _printReproInstructions; - private string _pdbPath; - private bool _generatePerfMapFile; - private string _perfMapPath; + private readonly bool _generateMapFile; + private readonly bool _generateMapCsvFile; + private readonly bool _generatePdbFile; + private readonly string _pdbPath; + private readonly bool _generatePerfMapFile; + private readonly string _perfMapPath; + private readonly bool _generateProfileFile; + private readonly Func _printReproInstructions; - private ProfileDataManager _profileData; - private ReadyToRunFileLayoutOptimizer _fileLayoutOptimizer; + private readonly ProfileDataManager _profileData; + private readonly ReadyToRunFileLayoutOptimizer _fileLayoutOptimizer; public ProfileDataManager ProfileData => _profileData; @@ -269,6 +270,7 @@ namespace ILCompiler string pdbPath, bool generatePerfMapFile, string perfMapPath, + bool generateProfileFile, int parallelism, ProfileDataManager profileData, ReadyToRunMethodLayoutAlgorithm methodLayoutAlgorithm, @@ -293,6 +295,7 @@ namespace ILCompiler _pdbPath = pdbPath; _generatePerfMapFile = generatePerfMapFile; _perfMapPath = perfMapPath; + _generateProfileFile = generateProfileFile; _customPESectionAlignment = customPESectionAlignment; SymbolNodeFactory = new ReadyToRunSymbolNodeFactory(nodeFactory, verifyTypeAndFieldLayout); _corInfoImpls = new ConditionalWeakTable(); @@ -335,6 +338,8 @@ namespace ILCompiler pdbPath: _pdbPath, generatePerfMapFile: _generatePerfMapFile, perfMapPath: _perfMapPath, + generateProfileFile: _generateProfileFile, + callChainProfile: _profileData.CallChainProfile, _customPESectionAlignment); CompilationModuleGroup moduleGroup = _nodeFactory.CompilationModuleGroup; @@ -411,6 +416,8 @@ namespace ILCompiler pdbPath: _pdbPath, generatePerfMapFile: false, perfMapPath: _perfMapPath, + generateProfileFile: _generateProfileFile, + _profileData.CallChainProfile, customPESectionAlignment: 0); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs index ba92a69..c01ef6e 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs @@ -29,6 +29,7 @@ namespace ILCompiler private string _pdbPath; private bool _generatePerfMapFile; private string _perfMapPath; + private bool _generateProfileFile; private int _parallelism; Func _printReproInstructions; private InstructionSetSupport _instructionSetSupport; @@ -153,6 +154,12 @@ namespace ILCompiler return this; } + public ReadyToRunCodegenCompilationBuilder UseProfileFile(bool generateProfileFile) + { + _generateProfileFile = generateProfileFile; + return this; + } + public ReadyToRunCodegenCompilationBuilder UseParallelism(int parallelism) { _parallelism = parallelism; @@ -282,6 +289,7 @@ namespace ILCompiler pdbPath: _pdbPath, generatePerfMapFile: _generatePerfMapFile, perfMapPath: _perfMapPath, + generateProfileFile: _generateProfileFile, _parallelism, _profileData, _r2rMethodLayoutAlgorithm, diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 08d921f..2cbb680 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -203,6 +203,7 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs index 00a7372..82145e3 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/OutputInfoBuilder.cs @@ -225,6 +225,9 @@ namespace ILCompiler.PEWriter public IReadOnlyList
Sections => _sections; public IReadOnlyList Symbols => _symbols; + public IReadOnlyDictionary NodeSymbolMap => _nodeSymbolMap; + public IReadOnlyDictionary MethodSymbolMap => _methodSymbolMap; + public IReadOnlyDictionary RelocCounts => _relocCounts; } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs new file mode 100644 index 0000000..c35aac1 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ObjectWriter/ProfileFileBuilder.cs @@ -0,0 +1,205 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Internal.TypeSystem; + +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysis.ReadyToRun; + +namespace ILCompiler.PEWriter +{ + /// + /// Helper class used to calculate code layout quality heuristics w.r.t. given call chain profile. + /// + public class ProfileFileBuilder + { + private enum CrossPageCall : byte + { + No, + Yes, + Unresolved, + } + + private class CallInfo + { + public readonly MethodDesc Caller; + public readonly OutputNode CallerNode; + public readonly int CallerRVA; + public readonly MethodDesc Callee; + public readonly OutputNode CalleeNode; + public readonly int CalleeRVA; + public readonly int CallCount; + public readonly CrossPageCall CallType; + + public CallInfo(MethodDesc caller, OutputNode callerNode, int callerRVA, MethodDesc callee, OutputNode calleeNode, int calleeRVA, int callCount, CrossPageCall callType) + { + Caller = caller; + CallerNode = callerNode; + CallerRVA = callerRVA; + Callee = callee; + CalleeNode = calleeNode; + CalleeRVA = calleeRVA; + CallCount = callCount; + CallType = callType; + } + } + + private readonly OutputInfoBuilder _outputInfoBuilder; + private readonly CallChainProfile _callChainProfile; + private readonly TargetDetails _targetDetails; + private readonly int _pageSize; + + private Dictionary _symbolMethodMap; + private List _callInfo; + + public ProfileFileBuilder(OutputInfoBuilder outputInfoBuilder, CallChainProfile callChainProfile, TargetDetails targetDetails) + { + _outputInfoBuilder = outputInfoBuilder; + _callChainProfile = callChainProfile; + _targetDetails = targetDetails; + _pageSize = _targetDetails.Architecture switch + { + TargetArchitecture.X86 => 0x00001000, + TargetArchitecture.X64 => 0x00010000, + TargetArchitecture.ARM => 0x00001000, + TargetArchitecture.ARM64 => 0x00010000, + _ => throw new NotImplementedException(_targetDetails.Architecture.ToString()) + }; + } + + public void SaveProfile(string profileFileName) + { + Console.WriteLine("Emitting profile file: {0}", profileFileName); + + CalculateCallInfo(); + using (StreamWriter writer = new StreamWriter(profileFileName)) + { + writer.WriteLine("CHARACTERISTIC | PAIR COUNT | CALL COUNT | PERCENTAGE"); + writer.WriteLine("----------------------------------------------------------------"); + int callCount = _callInfo.Sum(info => info.CallCount); + double percentFactor = 100.0 / Math.Max(callCount, 1); + writer.WriteLine("ENTRIES TOTAL | {0,10} | {1,10} | {2,10:F2}", _callInfo.Count, callCount, callCount * percentFactor); + int resolvedPairCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Unresolved ? 0 : 1); + int resolvedCallCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Unresolved ? 0 : info.CallCount); + writer.WriteLine("RESOLVED ENTRIES | {0,10} | {1,10} | {2,10:F2}", resolvedPairCount, resolvedCallCount, resolvedCallCount * percentFactor); + int unresolvedPairCount = _callInfo.Count - resolvedPairCount; + int unresolvedCallCount = callCount - resolvedCallCount; + writer.WriteLine("UNRESOLVED ENTRIES | {0,10} | {1,10} | {2,10:F2}", unresolvedPairCount, unresolvedCallCount, unresolvedCallCount * percentFactor); + int nearPairCount = _callInfo.Sum(info => info.CallType == CrossPageCall.No ? 1 : 0); + int nearCallCount = _callInfo.Sum(info => info.CallType == CrossPageCall.No ? info.CallCount : 0); + writer.WriteLine("NEAR (INTRA-PAGE) CALLS | {0,10} | {1,10} | {2,10:F2}", nearPairCount, nearCallCount, nearCallCount * percentFactor); + int farPairCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Yes ? 1 : 0); + int farCallCount = _callInfo.Sum(info => info.CallType == CrossPageCall.Yes ? info.CallCount : 0); + writer.WriteLine("FAR (CROSS-PAGE) CALLS | {0,10} | {1,10} | {2,10:F2}", farPairCount, farCallCount, farCallCount * percentFactor); + + writer.WriteLine(); + writer.WriteLine("CALLER RVA | CALLER LEN | CALLEE RVA | CALLEE LEN | COUNT | FAR (CROSS-PAGE) CALLS (CALLER -> CALLEE)"); + writer.WriteLine("----------------------------------------------------------------------------------------------------------"); + DumpCallInfo(writer, _callInfo.Where(info => info.CallType == CrossPageCall.Yes).OrderByDescending(info => info.CallCount)); + + writer.WriteLine(); + writer.WriteLine("CALLER RVA | CALLER LEN | CALLEE RVA | CALLEE LEN | COUNT | NEAR (INTRA-PAGE) CALLS (CALLER -> CALLEE)"); + writer.WriteLine("-----------------------------------------------------------------------------------------------------------"); + DumpCallInfo(writer, _callInfo.Where(info => info.CallType == CrossPageCall.No).OrderByDescending(info => info.CallCount)); + } + } + + private void DumpCallInfo(StreamWriter writer, IEnumerable callInfos) + { + foreach (CallInfo callInfo in callInfos) + { + writer.Write($@"{callInfo.CallerRVA,10:X8} | "); + writer.Write($@"{callInfo.CallerNode.Length,10:X8} | "); + writer.Write($@"{callInfo.CalleeRVA,10:X8} | "); + writer.Write($@"{callInfo.CalleeNode.Length,10:X8} | "); + writer.Write($@"{callInfo.CallCount,10} | "); + writer.WriteLine($@"{callInfo.Caller.ToString()} -> {callInfo.Callee.ToString()}"); + } + } + + private void CalculateSymbolMethodMap() + { + if (_symbolMethodMap != null) + { + // Already calculated + return; + } + _symbolMethodMap = new Dictionary(); + foreach (KeyValuePair kvpSymbolMethod in _outputInfoBuilder.MethodSymbolMap) + { + _symbolMethodMap.Add(kvpSymbolMethod.Value.Method, kvpSymbolMethod.Key); + } + } + + private void CalculateCallInfo() + { + if (_callInfo != null) + { + // Already calculated + return; + } + + CalculateSymbolMethodMap(); + + _callInfo = new List(); + foreach (KeyValuePair> kvpCallerCalleeCount in _callChainProfile.ResolvedProfileData) + { + OutputNode callerNode = null; + int callerRVA = 0; + if (_symbolMethodMap.TryGetValue(kvpCallerCalleeCount.Key, out ISymbolDefinitionNode callerSymbol) && + _outputInfoBuilder.NodeSymbolMap.TryGetValue(callerSymbol, out callerNode)) + { + callerRVA = _outputInfoBuilder.Sections[callerNode.SectionIndex].RVAWhenPlaced + callerNode.Offset; + } + + foreach (KeyValuePair kvpCalleeCount in kvpCallerCalleeCount.Value) + { + OutputNode calleeNode = null; + int calleeRVA = 0; + if (_symbolMethodMap.TryGetValue(kvpCalleeCount.Key, out ISymbolDefinitionNode calleeSymbol) && + _outputInfoBuilder.NodeSymbolMap.TryGetValue(calleeSymbol, out calleeNode)) + { + calleeRVA = _outputInfoBuilder.Sections[calleeNode.SectionIndex].RVAWhenPlaced + calleeNode.Offset; + } + + _callInfo.Add(new CallInfo( + caller: kvpCallerCalleeCount.Key, + callerNode: callerNode, + callerRVA: callerRVA, + callee: kvpCalleeCount.Key, + calleeNode: calleeNode, + calleeRVA: calleeRVA, + callCount: kvpCalleeCount.Value, + callType: GetCallType(callerNode, callerRVA, calleeNode, calleeRVA))); + } + } + } + + private CrossPageCall GetCallType(OutputNode caller, int callerRVA, OutputNode callee, int calleeRVA) + { + if (caller == null || callee == null) + { + return CrossPageCall.Unresolved; + } + int callerStartPage = callerRVA / _pageSize; + int callerEndPage = (callerRVA + caller.Length - 1) / _pageSize; + int calleePage = calleeRVA / _pageSize; + + if (callerStartPage == calleePage && callerEndPage == calleePage) + { + // The entire caller and the callee entrypoint are on the same page, no cross-page call penalty + return CrossPageCall.No; + } + + // Pessimistic estimate - we don't know where exactly the call is, we just know that it might cross a page. + return CrossPageCall.Yes; + } + } +} diff --git a/src/coreclr/tools/aot/crossgen2.sln b/src/coreclr/tools/aot/crossgen2.sln index cb78b75..ac541c0 100644 --- a/src/coreclr/tools/aot/crossgen2.sln +++ b/src/coreclr/tools/aot/crossgen2.sln @@ -112,8 +112,8 @@ Global {3EACD929-4725-4173-A845-734936BBDF87}.Checked|x86.Build.0 = Debug|x86 {3EACD929-4725-4173-A845-734936BBDF87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3EACD929-4725-4173-A845-734936BBDF87}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3EACD929-4725-4173-A845-734936BBDF87}.Debug|x64.ActiveCfg = Debug|Any CPU - {3EACD929-4725-4173-A845-734936BBDF87}.Debug|x64.Build.0 = Debug|Any CPU + {3EACD929-4725-4173-A845-734936BBDF87}.Debug|x64.ActiveCfg = Debug|x64 + {3EACD929-4725-4173-A845-734936BBDF87}.Debug|x64.Build.0 = Debug|x64 {3EACD929-4725-4173-A845-734936BBDF87}.Debug|x86.ActiveCfg = Debug|Any CPU {3EACD929-4725-4173-A845-734936BBDF87}.Debug|x86.Build.0 = Debug|Any CPU {3EACD929-4725-4173-A845-734936BBDF87}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs index 0a1a462..3b51b9b 100644 --- a/src/coreclr/tools/aot/crossgen2/Program.cs +++ b/src/coreclr/tools/aot/crossgen2/Program.cs @@ -540,6 +540,7 @@ namespace ILCompiler versionBubbleModules, _commandLineOptions.CompileBubbleGenerics ? inputModules[0] : null, mibcFiles, + jsonProfile, _typeSystemContext, compilationGroup); @@ -593,6 +594,7 @@ namespace ILCompiler .UseMapCsvFile(_commandLineOptions.MapCsv) .UsePdbFile(_commandLineOptions.Pdb, _commandLineOptions.PdbPath) .UsePerfMapFile(_commandLineOptions.PerfMap, _commandLineOptions.PerfMapPath) + .UseProfileFile(jsonProfile != null) .UseParallelism(_commandLineOptions.Parallelism) .UseProfileData(profileDataManager) .FileLayoutAlgorithms(_methodLayout, _fileLayout) -- 2.7.4