Ho un albero decisionale binario critico per le prestazioni e vorrei concentrare questa domanda su una singola riga di codice. Il codice per l'iteratore dell'albero binario è di seguito con i risultati dell'esecuzione dell'analisi delle prestazioni rispetto ad esso.
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 è un campo, non una proprietà. L'ho fatto per evitare il rischio che non fosse inline.
La classe BranchNodeData è la seguente:
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;
}
Come puoi vedere, il ciclo while / controllo null è un enorme successo per le prestazioni. L'albero è enorme, quindi mi aspetto che la ricerca di una foglia richieda un po 'di tempo, ma mi piacerebbe capire la quantità sproporzionata di tempo trascorso su quella riga.
Ho provato:
- Separare il controllo Null dal tempo: è il controllo Null il risultato.
- Aggiungendo un campo booleano all'oggetto e confrontandolo, non ha fatto alcuna differenza. Non importa cosa viene confrontato, è il confronto il problema.
È un problema di previsione del ramo? In caso affermativo, cosa posso fare al riguardo? Se qualcosa?
Non pretendo di capire il CIL , ma lo pubblicherò per chiunque lo faccia in modo che possa provare a racimolare alcune informazioni da esso.
.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
Modifica: ho deciso di fare un test di previsione del ramo, ne ho aggiunto uno identico se nel frattempo, quindi abbiamo
while (node.BranchData != null)
e
if (node.BranchData != null)
dentro quello. Quindi ho eseguito l'analisi delle prestazioni rispetto a quella e ci sono voluti sei volte più tempo per eseguire il primo confronto come ha fatto per eseguire il secondo confronto che ha sempre restituito vero. Quindi sembra che sia davvero un problema di previsione del ramo - e immagino che non ci sia niente che io possa fare al riguardo ?!
Un'altra modifica
Il risultato di cui sopra si verificherebbe anche se node.BranchData dovesse essere caricato dalla RAM per il while check - verrebbe quindi memorizzato nella cache per l'istruzione if.
Questa è la mia terza domanda su un argomento simile. Questa volta mi sto concentrando su una singola riga di codice. Le mie altre domande su questo argomento sono:
while(true) { /* current body */ if(node.BranchData == null) return node; }
. Cambia qualcosa?
while(true) { BranchNodeData b = node.BranchData; if(ReferenceEquals(b, null)) return node; node = b.Child2; if (inputs[b.SplitInputIndex] <= b.SplitValue) node = b.Child1; }
questo recupererebbe node. BranchData
solo una volta.
BranchNode
proprietà. Prova a sostituirenode.BranchData != null
ReferenceEquals(node.BranchData, null)
. Fa qualche differenza?