public int Offset => 0;
public override bool IsShareable => throw new NotImplementedException();
public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) => IsEmpty;
+
+ public override string ToString() => _method.ToString();
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+namespace ILCompiler.PettisHansenSort
+{
+ public class CallGraphNode
+ {
+ public CallGraphNode(int index)
+ {
+ Index = index;
+ }
+
+ public int Index { get; }
+ public Dictionary<CallGraphNode, long> OutgoingEdges { get; } = new Dictionary<CallGraphNode, long>();
+
+ public void IncreaseEdge(CallGraphNode callee, long count)
+ {
+ if (OutgoingEdges.TryGetValue(callee, out long curCount))
+ OutgoingEdges[callee] = curCount + count;
+ else
+ OutgoingEdges.Add(callee, count);
+ }
+
+ public override string ToString() => Index.ToString();
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+namespace ILCompiler.PettisHansenSort
+{
+ public class DisjointSetForest
+ {
+ private Node[] _nodes;
+
+ /// <summary>
+ /// Construct a new forest with the specified number of disjoint sets.
+ /// </summary>
+ public DisjointSetForest(int numNodes)
+ {
+ _nodes = new Node[numNodes];
+ for (int i = 0; i < _nodes.Length; i++)
+ _nodes[i].Parent = i;
+
+ NumNodes = numNodes;
+ NumDisjointSets = numNodes;
+ }
+
+ /// <summary>
+ /// Gets the count of disjoint sets that are currently entered in this forest.
+ /// </summary>
+ public int NumDisjointSets { get; private set; }
+ public int NumNodes { get; private set; }
+
+ // Add a new disjoint set.
+ public int Add()
+ {
+ if (NumNodes >= _nodes.Length)
+ Array.Resize(ref _nodes, NumNodes * 2);
+
+ int index = NumNodes;
+ _nodes[index].Parent = index;
+ NumDisjointSets++;
+ NumNodes++;
+
+ return index;
+ }
+
+ public int FindSet(int node)
+ {
+ if (node < 0 || node >= _nodes.Length)
+ throw new ArgumentOutOfRangeException(nameof(node), node,
+ "Node must be positive and less than number of nodes");
+
+ return FindSetInternal(node);
+ }
+
+ private int FindSetInternal(int node)
+ {
+ int parent = _nodes[node].Parent;
+ if (parent != node)
+ _nodes[node].Parent = parent = FindSetInternal(parent);
+
+ return parent;
+ }
+
+ public bool Union(int x, int y)
+ {
+ x = FindSet(x);
+ y = FindSet(y);
+
+ if (x == y)
+ return false;
+
+ // Make smallest a child of the largest
+ if (_nodes[y].Rank > _nodes[x].Rank)
+ _nodes[x].Parent = y;
+ else
+ {
+ _nodes[y].Parent = x;
+ if (_nodes[x].Rank == _nodes[y].Rank)
+ _nodes[x].Rank++;
+ }
+
+ NumDisjointSets--;
+ return true;
+ }
+
+ private struct Node
+ {
+ public int Parent;
+ public int Rank;
+ }
+ }
+}
--- /dev/null
+// 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.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ILCompiler.PettisHansenSort
+{
+ public static class PettisHansen
+ {
+ public static List<List<int>> Sort(List<CallGraphNode> graph)
+ {
+ // Create initial graph with a node for each method.
+ DisjointSetForest unionFind = new DisjointSetForest(graph.Count);
+ var phNodes = new List<int>[graph.Count];
+ // Undirected edges, stored in both directions.
+ var phEdges = new Dictionary<int, long>[graph.Count];
+ // Construct initial graph with nodes for each method.
+ for (int i = 0; i < phNodes.Length; i++)
+ {
+ CallGraphNode node = graph[i];
+ phNodes[i] = new List<int>(1) { i };
+ var dict = new Dictionary<int, long>(node.OutgoingEdges.Count);
+ phEdges[i] = dict;
+ }
+
+ void AddEdge(int a, int b, long weight)
+ {
+ if (a == b)
+ return;
+
+ if (phEdges[a].TryGetValue(b, out long curWeight))
+ phEdges[a][b] = curWeight + weight;
+ else
+ phEdges[a].Add(b, weight);
+ }
+ // Now add edges.
+ for (int i = 0; i < phNodes.Length; i++)
+ {
+ foreach (var kvp in graph[i].OutgoingEdges)
+ {
+ AddEdge(i, kvp.Key.Index, kvp.Value);
+ AddEdge(kvp.Key.Index, i, kvp.Value);
+ }
+ }
+
+#if DEBUG
+ for (int i = 0; i < phNodes.Length; i++)
+ {
+ foreach (var kvp in phEdges[i])
+ Debug.Assert(phEdges[kvp.Key][i] == phEdges[i][kvp.Key]);
+ }
+#endif
+
+ var queue = new PriorityQueue<(int from, int to), long>();
+ for (int i = 0; i < phEdges.Length; i++)
+ {
+ foreach (var kvp in phEdges[i])
+ {
+ if (kvp.Key > i)
+ {
+ queue.Enqueue((i, kvp.Key), -kvp.Value); // PriorityQueue gives lowest prio first
+ }
+ }
+ }
+
+ while (queue.Count > 0)
+ {
+ (int from, int to) = queue.Dequeue();
+ from = unionFind.FindSet(from);
+ to = unionFind.FindSet(to);
+
+ if (from == to)
+ continue; // Already unioned through a different path
+
+ Debug.Assert(phEdges[from][to] == phEdges[to][from]);
+
+ bool unioned = unionFind.Union(from, to);
+ Trace.Assert(unioned);
+
+ int winner = unionFind.FindSet(from);
+ int loser = winner == from ? to : from;
+
+ long OrigWeight(int a, int b)
+ {
+ graph[a].OutgoingEdges.TryGetValue(graph[b], out long ab);
+ graph[b].OutgoingEdges.TryGetValue(graph[a], out long ba);
+ return ab + ba;
+ }
+
+ // Transfer all method names from loser to winner, preferring highest weight between endpoints
+ long wff = OrigWeight(phNodes[winner].First(), phNodes[loser].First());
+ long wfl = OrigWeight(phNodes[winner].First(), phNodes[loser].Last());
+ long wlf = OrigWeight(phNodes[winner].Last(), phNodes[loser].First());
+ long wll = OrigWeight(phNodes[winner].Last(), phNodes[loser].Last());
+ if (wlf >= wff && wlf >= wfl && wlf >= wll)
+ {
+ // Already in right order
+ }
+ else if (wll >= wff && wll >= wfl && wll >= wlf)
+ {
+ phNodes[loser].Reverse();
+ }
+ else if (wff >= wfl && wff >= wlf && wff >= wll)
+ {
+ phNodes[winner].Reverse();
+ }
+ else
+ {
+ Debug.Assert(wfl >= wff && wfl >= wlf && wfl >= wll);
+ phNodes[winner].Reverse();
+ phNodes[loser].Reverse();
+ }
+
+ phNodes[winner].AddRange(phNodes[loser]);
+ phNodes[loser].Clear();
+
+ // Verify that there is exactly one edge between winner's set and loser's set
+ Debug.Assert(phEdges[loser].Count(e => unionFind.FindSet(e.Key) == winner) == 1);
+
+ // Get rid of unifying edge
+ phEdges[winner].Remove(loser);
+ phEdges[loser].Remove(winner);
+
+ // Transfer all edges from loser to winner, coalescing when there are multiple.
+ foreach (var edge in phEdges[loser])
+ {
+ // Remove counter edge.
+ bool removed = phEdges[edge.Key].Remove(loser);
+ Debug.Assert(removed);
+
+ // Add edge and counter edge, coalescing when necessary.
+ AddEdge(winner, edge.Key, edge.Value);
+ AddEdge(edge.Key, winner, edge.Value);
+ // Add a new entry in the queue as the edge could have changed weight from coalescing.
+ long weight = phEdges[winner][edge.Key];
+ queue.Enqueue((winner, edge.Key), -weight); // Priority queue gives lowest priority first
+ }
+
+ phEdges[loser].Clear();
+
+#if DEBUG
+ // Assert that there are only edges between representatives.
+ for (int i = 0; i < phEdges.Length; i++)
+ {
+ foreach (var edge in phEdges[i])
+ {
+ Debug.Assert(unionFind.FindSet(i) == i && unionFind.FindSet(edge.Key) == edge.Key);
+ Debug.Assert(phEdges[edge.Key][i] == phEdges[i][edge.Key]);
+ }
+ }
+#endif
+ }
+
+ // Order by component size as we return. Note that we rely on the
+ // stability of the sort here to keep trivial components of only a
+ // single method (meaning that we did not see any call edges) in
+ // the same order as it was in the input (i.e. increasing indices,
+ // asserted below).
+ List<List<int>> components =
+ phNodes
+ .Where(n => n.Count != 0)
+ .OrderByDescending(n => n.Count)
+ .ToList();
+
+ // We also expect to see a permutation.
+ Debug.Assert(components.SelectMany(l => l).OrderBy(i => i).SequenceEqual(Enumerable.Range(0, graph.Count)));
+
+#if DEBUG
+ int prev = -1;
+ foreach (List<int> component in components.SkipWhile(l => l.Count != 1))
+ {
+ Debug.Assert(component[0] > prev);
+ prev = component[0];
+ }
+#endif
+
+ return components;
+ }
+ }
+}
_profileData = profileData;
- _fileLayoutOptimizer = new ReadyToRunFileLayoutOptimizer(methodLayoutAlgorithm, fileLayoutAlgorithm, profileData, _nodeFactory);
+ _fileLayoutOptimizer = new ReadyToRunFileLayoutOptimizer(logger, methodLayoutAlgorithm, fileLayoutAlgorithm, profileData, _nodeFactory);
}
private readonly static string s_folderUpPrefix = ".." + Path.DirectorySeparatorChar;
using System.Collections.Immutable;
using System.Text;
using System.Reflection.Metadata.Ecma335;
+using ILCompiler.PettisHansenSort;
namespace ILCompiler
{
HotCold,
HotWarmCold,
CallFrequency,
+ PettisHansen,
}
public enum ReadyToRunFileLayoutAlgorithm
class ReadyToRunFileLayoutOptimizer
{
- public ReadyToRunFileLayoutOptimizer (ReadyToRunMethodLayoutAlgorithm methodAlgorithm,
+ public ReadyToRunFileLayoutOptimizer (Logger logger,
+ ReadyToRunMethodLayoutAlgorithm methodAlgorithm,
ReadyToRunFileLayoutAlgorithm fileAlgorithm,
ProfileDataManager profileData,
NodeFactory nodeFactory)
{
+ _logger = logger;
_methodLayoutAlgorithm = methodAlgorithm;
_fileLayoutAlgorithm = fileAlgorithm;
_profileData = profileData;
_nodeFactory = nodeFactory;
}
+ private Logger _logger;
private ReadyToRunMethodLayoutAlgorithm _methodLayoutAlgorithm = ReadyToRunMethodLayoutAlgorithm.DefaultSort;
private ReadyToRunFileLayoutAlgorithm _fileLayoutAlgorithm = ReadyToRunFileLayoutAlgorithm.DefaultSort;
private ProfileDataManager _profileData;
methods = MethodCallFrequencySort(methods);
break;
+ case ReadyToRunMethodLayoutAlgorithm.PettisHansen:
+ methods = PettisHansenSort(methods);
+ break;
+
default:
throw new NotImplementedException(_methodLayoutAlgorithm.ToString());
}
Debug.Assert(outputMethods.Count == methodsToPlace.Count);
return outputMethods;
}
+
+ /// <summary>
+ /// Sort methods with Pettis-Hansen using call graph data from profile.
+ /// </summary>
+ private List<MethodWithGCInfo> PettisHansenSort(List<MethodWithGCInfo> methodsToPlace)
+ {
+ var graphNodes = new List<CallGraphNode>(methodsToPlace.Count);
+ var mdToIndex = new Dictionary<MethodDesc, int>();
+ int index = 0;
+ foreach (MethodWithGCInfo method in methodsToPlace)
+ {
+ mdToIndex.Add(method.Method, index);
+ graphNodes.Add(new CallGraphNode(index));
+ index++;
+ }
+
+ bool any = false;
+ foreach (MethodWithGCInfo method in methodsToPlace)
+ {
+ MethodProfileData data = _profileData[method.Method];
+ if (data == null || data.CallWeights == null)
+ continue;
+
+ foreach ((MethodDesc other, int count) in data.CallWeights)
+ {
+ if (!mdToIndex.TryGetValue(other, out int otherIndex))
+ continue;
+
+ graphNodes[mdToIndex[method.Method]].IncreaseEdge(graphNodes[otherIndex], count);
+ any = true;
+ }
+ }
+
+ if (!any)
+ {
+ _logger.Writer.WriteLine("Warning: no call graph data was found or a .mibc file was not specified. Skipping Pettis Hansen method ordering.");
+ return methodsToPlace;
+ }
+
+ List<List<int>> components = PettisHansen.Sort(graphNodes);
+ // We expect to see a permutation.
+ Debug.Assert(components.SelectMany(l => l).OrderBy(i => i).SequenceEqual(Enumerable.Range(0, methodsToPlace.Count)));
+
+ List<MethodWithGCInfo> result = new List<MethodWithGCInfo>(methodsToPlace.Count);
+ foreach (List<int> component in components)
+ {
+ foreach (int node in component)
+ result.Add(methodsToPlace[node]);
+ }
+
+ return result;
+ }
}
}
<Compile Include="Compiler\DependencyAnalysis\TypeAndMethod.cs" />
<Compile Include="Compiler\IRootingServiceProvider.cs" />
<Compile Include="Compiler\ReadyToRunCompilationModuleGroupBase.cs" />
+ <Compile Include="Compiler\PettisHansenSort\CallGraphNode.cs" />
+ <Compile Include="Compiler\PettisHansenSort\DisjointSetForest.cs" />
+ <Compile Include="Compiler\PettisHansenSort\PettisHansen.cs" />
<Compile Include="IBC\IBCDataModel.cs" />
<Compile Include="IBC\IBCDataReader.cs" />
<Compile Include="IBC\MIbcProfileParser.cs" />
"hotcold" => ReadyToRunMethodLayoutAlgorithm.HotCold,
"hotwarmcold" => ReadyToRunMethodLayoutAlgorithm.HotWarmCold,
"callfrequency" => ReadyToRunMethodLayoutAlgorithm.CallFrequency,
+ "pettishansen" => ReadyToRunMethodLayoutAlgorithm.PettisHansen,
_ => throw new CommandLineException(SR.InvalidMethodLayout)
};
}
<value>Method layout must be either DefaultSort or MethodOrder.</value>
</data>
<data name="InvalidMethodLayout" xml:space="preserve">
- <value>Method layout must be either DefaultSort, ExclusiveWeight, HotCold, HotWarmCold, or CallFrequency.</value>
+ <value>Method layout must be either DefaultSort, ExclusiveWeight, HotCold, HotWarmCold, CallFrequency or PettisHansen.</value>
</data>
<data name="CompileNoMethodsOption" xml:space="preserve">
<value>True to skip compiling methods into the R2R image (default = false)</value>