J'ai un arbre de décision binaire critique pour les performances, et j'aimerais concentrer cette question sur une seule ligne de code. Le code de l'itérateur d'arborescence binaire est ci-dessous avec les résultats de l'exécution de l'analyse des performances par rapport à celui-ci.
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 est un champ, pas une propriété. J'ai fait cela pour éviter le risque de ne pas être intégré.
La classe BranchNodeData est la suivante:
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;
}
Comme vous pouvez le voir, la vérification de la boucle while / null est un énorme impact sur les performances. L'arbre est énorme, donc je m'attendrais à ce que la recherche d'une feuille prenne un certain temps, mais j'aimerais comprendre le temps disproportionné passé sur cette ligne.
J'ai essayé:
- Séparer la vérification Null du while - c'est la vérification Null qui est le coup.
- L'ajout d'un champ booléen à l'objet et la vérification par rapport à cela n'a fait aucune différence. Peu importe ce qui est comparé, c'est la comparaison qui est le problème.
S'agit-il d'un problème de prédiction de branche? Si oui, que puis-je y faire? Si quelque chose?
Je ne prétendrai pas comprendre le CIL , mais je le posterai pour que n'importe qui le fasse afin qu'il puisse essayer d'en extraire des informations.
.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
Edit: J'ai décidé de faire un test de prédiction de branche, j'ai ajouté un même si dans le temps, donc nous avons
while (node.BranchData != null)
et
if (node.BranchData != null)
à l'intérieur. J'ai ensuite effectué une analyse des performances par rapport à cela, et il a fallu six fois plus de temps pour exécuter la première comparaison que pour exécuter la deuxième comparaison qui retournait toujours vrai. Il semble donc que ce soit effectivement un problème de prédiction de branche - et je suppose que je ne peux rien y faire?!
Une autre modification
Le résultat ci-dessus se produirait également si node.BranchData devait être chargé à partir de la RAM pendant le contrôle while - il serait alors mis en cache pour l'instruction if.
C'est ma troisième question sur un sujet similaire. Cette fois, je me concentre sur une seule ligne de code. Mes autres questions à ce sujet sont:
while(true) { /* current body */ if(node.BranchData == null) return node; }
. Cela change-t-il quelque chose?
while(true) { BranchNodeData b = node.BranchData; if(ReferenceEquals(b, null)) return node; node = b.Child2; if (inputs[b.SplitInputIndex] <= b.SplitValue) node = b.Child1; }
Cela ne récupérerait node. BranchData
qu'une seule fois.
BranchNode
propriété. Veuillez essayer de remplacernode.BranchData != null
ReferenceEquals(node.BranchData, null)
. Cela fait-il une différence?