Ich habe einen leistungskritischen binären Entscheidungsbaum und möchte diese Frage auf eine einzelne Codezeile konzentrieren. Der Code für den Binärbaum-Iterator ist unten mit den Ergebnissen der laufenden Leistungsanalyse aufgeführt.
public ScTreeNode GetNodeForState(int rootIndex, float[] inputs)
{
0.2% ScTreeNode node = RootNodes[rootIndex].TreeNode;
24.6% while (node.BranchData != null)
{
0.2% BranchNodeData b = node.BranchData;
0.5% node = b.Child2;
12.8% if (inputs[b.SplitInputIndex] <= b.SplitValue)
0.8% node = b.Child1;
}
0.4% return node;
}
BranchData ist ein Feld, keine Eigenschaft. Ich habe dies getan, um zu verhindern, dass das Risiko besteht, dass es nicht inline wird.
Die BranchNodeData-Klasse lautet wie folgt:
public sealed class BranchNodeData
{
/// <summary>
/// The index of the data item in the input array on which we need to split
/// </summary>
internal int SplitInputIndex = 0;
/// <summary>
/// The value that we should split on
/// </summary>
internal float SplitValue = 0;
/// <summary>
/// The nodes children
/// </summary>
internal ScTreeNode Child1;
internal ScTreeNode Child2;
}
Wie Sie sehen können, ist die while-Schleife / Null-Prüfung ein massiver Leistungseinbruch. Der Baum ist massiv, daher würde ich erwarten, dass die Suche nach einem Blatt eine Weile dauert, aber ich würde gerne verstehen, wie viel Zeit unverhältnismäßig viel Zeit in dieser einen Zeile verbracht wird.
Ich habe es versucht:
- Trennen Sie den Null-Check von der Weile - es ist der Null-Check, der den Treffer darstellt.
- Das Hinzufügen eines booleschen Feldes zum Objekt und das Abgleichen davon machte keinen Unterschied. Es ist egal, was verglichen wird, es ist der Vergleich, der das Problem ist.
Ist dies ein Problem mit der Verzweigungsvorhersage? Wenn ja, was kann ich dagegen tun? Wenn überhaupt?
Ich werde nicht so tun , als würde ich die CIL verstehen , aber ich werde sie für jeden veröffentlichen, der dies tut, damit er versuchen kann, einige Informationen daraus herauszukratzen.
.method public hidebysig
instance class OptimalTreeSearch.ScTreeNode GetNodeForState (
int32 rootIndex,
float32[] inputs
) cil managed
{
// Method begins at RVA 0x2dc8
// Code size 67 (0x43)
.maxstack 2
.locals init (
[0] class OptimalTreeSearch.ScTreeNode node,
[1] class OptimalTreeSearch.BranchNodeData b
)
IL_0000: ldarg.0
IL_0001: ldfld class [mscorlib]System.Collections.Generic.List`1<class OptimalTreeSearch.ScRootNode> OptimalTreeSearch.ScSearchTree::RootNodes
IL_0006: ldarg.1
IL_0007: callvirt instance !0 class [mscorlib]System.Collections.Generic.List`1<class OptimalTreeSearch.ScRootNode>::get_Item(int32)
IL_000c: ldfld class OptimalTreeSearch.ScTreeNode OptimalTreeSearch.ScRootNode::TreeNode
IL_0011: stloc.0
IL_0012: br.s IL_0039
// loop start (head: IL_0039)
IL_0014: ldloc.0
IL_0015: ldfld class OptimalTreeSearch.BranchNodeData OptimalTreeSearch.ScTreeNode::BranchData
IL_001a: stloc.1
IL_001b: ldloc.1
IL_001c: ldfld class OptimalTreeSearch.ScTreeNode OptimalTreeSearch.BranchNodeData::Child2
IL_0021: stloc.0
IL_0022: ldarg.2
IL_0023: ldloc.1
IL_0024: ldfld int32 OptimalTreeSearch.BranchNodeData::SplitInputIndex
IL_0029: ldelem.r4
IL_002a: ldloc.1
IL_002b: ldfld float32 OptimalTreeSearch.BranchNodeData::SplitValue
IL_0030: bgt.un.s IL_0039
IL_0032: ldloc.1
IL_0033: ldfld class OptimalTreeSearch.ScTreeNode OptimalTreeSearch.BranchNodeData::Child1
IL_0038: stloc.0
IL_0039: ldloc.0
IL_003a: ldfld class OptimalTreeSearch.BranchNodeData OptimalTreeSearch.ScTreeNode::BranchData
IL_003f: brtrue.s IL_0014
// end loop
IL_0041: ldloc.0
IL_0042: ret
} // end of method ScSearchTree::GetNodeForState
Bearbeiten: Ich habe mich für einen Verzweigungstest entschieden. Ich habe innerhalb der Zeit einen identischen hinzugefügt, also haben wir
while (node.BranchData != null)
und
if (node.BranchData != null)
darin. Ich habe dann eine Leistungsanalyse durchgeführt, und es dauerte sechsmal länger, um den ersten Vergleich auszuführen, als um den zweiten Vergleich auszuführen, der immer true zurückgab. Es sieht also so aus, als wäre es tatsächlich ein Problem mit der Branchenvorhersage - und ich vermute, ich kann nichts dagegen tun?!
Noch eine Bearbeitung
Das obige Ergebnis würde auch auftreten, wenn node.BranchData für die while-Prüfung aus dem RAM geladen werden müsste - es würde dann für die if-Anweisung zwischengespeichert.
Dies ist meine dritte Frage zu einem ähnlichen Thema. Dieses Mal konzentriere ich mich auf eine einzelne Codezeile. Meine anderen Fragen zu diesem Thema sind:
while(true) { /* current body */ if(node.BranchData == null) return node; }
. Ändert es etwas?
while(true) { BranchNodeData b = node.BranchData; if(ReferenceEquals(b, null)) return node; node = b.Child2; if (inputs[b.SplitInputIndex] <= b.SplitValue) node = b.Child1; }
Dies würde node. BranchData
nur einmal abgerufen .
BranchNode
Immobilie. Bitte versuchen Sie zu ersetzennode.BranchData != null
ReferenceEquals(node.BranchData, null)
. Macht es einen Unterschied?