Cette réponse n'est pas une réponse, mais plutôt un ensemble de notes.
Premièrement, le processeur a tendance à fonctionner sur des lignes de cache, et non sur des octets/mots/dwords individuels. Cela signifie que si vous lisez/écrivez séquentiellement un tableau d'entiers, le premier accès à une ligne de cache peut provoquer un échec du cache, mais les accès suivants à différents entiers dans cette même ligne de cache ne le feront pas. Pour les lignes de cache de 64 octets et les entiers de 4 octets, cela signifie que vous n'obtiendrez un échec de cache qu'une seule fois tous les 16 accès ; ce qui diluera les résultats.
Deuxièmement, le processeur dispose d'un "pré-récupérateur matériel". S'il détecte que les lignes de cache sont lues de manière séquentielle, le pré-récupérateur matériel pré-récupère automatiquement les lignes de cache dont il prédit qu'elles seront nécessaires ensuite (pour tenter de les récupérer dans le cache avant qu'elles ne soient nécessaires).
Troisièmement, le processeur fait d'autres choses (comme "l'exécution dans le désordre") pour masquer les coûts de récupération. La différence de temps (entre l'accès au cache et l'échec du cache) que vous pouvez mesurer est le temps pendant lequel le processeur n'a pas pu se cacher et non le coût total de la récupération.
Ces 3 choses combinées signifient que; pour lire séquentiellement un tableau d'entiers, il est probable que le processeur pré-extrait la ligne de cache suivante pendant que vous effectuez 16 lectures à partir de la ligne de cache précédente ; et tous les coûts de manque de cache ne seront pas perceptibles et peuvent être entièrement masqués. Pour empêcher cela; vous voudriez accéder "au hasard" à chaque ligne de cache une fois, pour maximiser la différence de performances mesurée entre "l'ensemble de travail tient dans le/les cache/s" et "l'ensemble de travail ne tient pas dans le/les cache/s".
Enfin, d'autres facteurs peuvent influencer les mesures. Par exemple, pour un système d'exploitation qui utilise la pagination (par exemple Linux et presque tous les autres systèmes d'exploitation modernes), il y a toute une couche de mise en cache au-dessus de tout cela (TLB/Translation Look-aside Buffers), et TLB manque une fois que l'ensemble de travail dépasse une certaine taille; qui devrait être visible comme une quatrième "étape" dans le graphique. Il y a aussi des interférences du noyau (IRQ, défauts de page, changements de tâche, plusieurs processeurs, etc.); qui peuvent être visibles sous forme d'erreurs/statiques aléatoires dans le graphique (à moins que les tests ne soient répétés souvent et que les valeurs aberrantes soient rejetées). Il existe également des artefacts de la conception du cache (associativité du cache) qui peuvent réduire l'efficacité du cache d'une manière qui dépend de la ou des adresses physiques allouées par le noyau ; ce qui pourrait être vu comme les "étapes" du graphique se déplaçant vers différents endroits.
Y a-t-il quelque chose qui ne va pas avec ma méthode ?
Peut-être, mais sans voir votre code réel auquel il est impossible de répondre.
-
Votre description de ce que fait votre code ne dit pas si vous lisez le tableau une ou plusieurs fois.
-
Le tableau peut ne pas être assez grand ... selon votre matériel. (Certaines puces modernes n'ont-elles pas un cache de 3e niveau de quelques mégaoctets ?)
-
Dans le cas de Java en particulier, vous devez faire beaucoup de choses de la bonne manière pour implémenter un micro-benchmark significatif.
Dans le cas C :
-
Vous pouvez essayer d'ajuster les commutateurs d'optimisation du compilateur C.
-
Étant donné que votre code accède au tableau en série, le compilateur peut être en mesure d'ordonner les instructions afin que le processeur puisse suivre le rythme, ou le processeur peut pré-extraire de manière optimiste ou effectuer des extractions étendues. Vous pouvez essayer de lire les éléments du tableau dans un ordre moins prévisible.
-
Il est même possible que le compilateur ait entièrement optimisé la boucle car le résultat du calcul de la boucle n'est utilisé pour rien.
(Selon ce Q&A - Combien de temps faut-il pour extraire un mot de la mémoire ?, une extraction depuis le cache L2 est d'environ 7 nanosecondes et une extraction depuis la mémoire principale est d'environ 100 nanosecondes. Mais vous obtenez environ 2 nanosecondes. Quelque chose d'intelligent doit se dérouler ici pour qu'il fonctionne aussi vite que vous l'observez.)