52 votes

Linux C++ : comment profiler le temps perdu à cause des ratés du cache ?

Je sais que je peux utiliser gprof pour évaluer mon code.

Cependant, j'ai un problème : j'ai un pointeur intelligent qui a un niveau supplémentaire d'indirection (pensez-y comme un objet proxy).

Par conséquent, j'ai cette couche supplémentaire qui affecte pratiquement toutes les fonctions et qui nuit à la mise en cache.

Existe-t-il un moyen de mesurer le temps que mon processeur perd à cause des ratés du cache ?

20voto

Taavi Points 201

Vous pouvez essayer cachemire et son frontal kcachegrind.

12voto

Potatoswatter Points 70305

Vous pouvez trouver un outil qui accède aux compteurs de performance du CPU. Il y a probablement un registre dans chaque noyau qui compte les manques de L1, L2, etc. Cachegrind peut également effectuer une simulation cycle par cycle.

Cependant, je ne pense pas que ce serait perspicace. Vos objets proxy sont vraisemblablement modifiés par leurs propres méthodes. Un profileur classique vous indiquera le temps que prennent ces méthodes. Aucun outil de profilage ne pourrait vous dire comment les performances s'amélioreraient sans cette source de pollution du cache. Il s'agit de réduire la taille et la structure de l'ensemble de travail du programme, ce qui n'est pas facile à extrapoler.

Une recherche rapide sur Google a permis de trouver boost::intrusive_ptr qui pourrait vous intéresser. Il ne semble pas soutenir quelque chose comme weak_ptr Mais la conversion de votre programme pourrait être triviale, et vous connaîtriez alors avec certitude le coût des comptages de références non intrusifs.

1 votes

En effet, il n'est pas possible d'utiliser un weak_ptr avec un compteur intrusif car un compteur intrusif est détruit avec l'objet... et donc le weak_ptr n'a aucun moyen de vérifier si l'objet est valide ou non sans y accéder réellement.

0 votes

@Matthieu : Si le graphe de dépendance est connu pour être un cycle unique, je pense que vous pouvez utiliser le lien de chaque objet (il ne doit y en avoir qu'un seul) comme un indicateur de validité. Pour les besoins de la destruction en tout cas. Traverser un graphe aléatoire nécessiterait un stockage thread-local, mais ce n'est pas impossible.

6voto

Krazy Glew Points 2142

Dans la continuité de la réponse de @Mike_Dunlavey :

Tout d'abord, obtenez un profil temporel, en utilisant votre outil préféré : VTune ou PTU ou OProf.

Ensuite, obtenez un profil de manque de cache. Les ratés du cache L1, ou les ratés du cache L2, ou ...

C'est-à-dire que le premier profil associe un "temps passé" à chaque compteur de programme. Le second associe une valeur de "nombre d'échecs de cache" à chaque compteur de programme.

Note : Je "réduis" souvent les données, en les additionnant par fonction, ou (si j'ai la technologie) par boucle. Ou par tranches de, disons, 64 octets. Comparer les compteurs de programmes individuels n'est souvent pas utile, parce que les compteurs de performance sont flous - l'endroit où vous voyez un manque de cache être rapporté est souvent plusieurs instructions différentes de l'endroit où il s'est réellement produit.

OK, maintenant, faites un graphique de ces deux profils pour les comparer. Voici quelques graphiques que je trouve utiles :

Graphiques "Iceberg" : L'axe X représente le PC, l'axe Y positif représente le temps, l'axe Y négatif représente les échecs de cache. Cherchez des endroits qui vont à la fois vers le haut et vers le bas.

(Les graphiques "entrelacés" sont également utiles : même idée, l'axe X est le PC, le temps et les manques dans la mémoire cache sont représentés sur l'axe Y, mais avec des lignes verticales étroites de différentes couleurs, généralement rouge et bleu. Les endroits où l'on dépense beaucoup de temps et de manques de cache auront des lignes rouges et bleues finement entrelacées, ressemblant presque à du violet. Cela s'étend aux manques de cache L2 et L3, tous sur le même graphique. Au fait, vous voudrez probablement "normaliser" les chiffres, soit en % du temps total ou des ratés du cache, soit, encore mieux, en % du point de données maximum du temps ou des ratés du cache. Si vous vous trompez dans l'échelle, vous ne verrez rien).

Graphiques XY pour chaque case d'échantillonnage (PC, ou fonction, ou boucle, ou...) tracer un point dont dont la coordonnée X est le temps normalisé, et dont la coordonnée Y est les manques normalisés dans le cache . Si vous obtenez un grand nombre de points de données dans le coin supérieur droit - un pourcentage élevé de temps ET un pourcentage élevé de ratés du cache - c'est une preuve intéressante. Ou, oubliez le nombre de points - si la somme de tous les pourcentages dans le coin supérieur est grande...

Notez, malheureusement, que vous devez souvent effectuer ces analyses vous-même. Aux dernières nouvelles, VTune ne le fait pas pour vous. J'ai utilisé gnuplot et Excel. (Attention : Excel meurt au-delà de 64 mille points de données).


Plus de conseils :

Si votre pointeur intelligent est inlined, vous risquez d'avoir des comptes partout. Dans un monde idéal, vous seriez en mesure de retracer les PC jusqu'à la ligne de code source d'origine. Dans ce cas, vous voudrez peut-être différer un peu la réduction : regardez tous les PC individuels ; remontez-les jusqu'aux lignes de code source ; puis remontez-les jusqu'à la fonction d'origine. De nombreux compilateurs, par exemple GCC, ont des options de table de symboles qui vous permettent de faire cela.

D'ailleurs, je soupçonne que votre problème n'est PAS dû au pointeur intelligent qui cause des problèmes de cache. A moins que vous ne fassiez du smart_ptr<int> partout. Si vous faites des smart_ptr<Obj>, et que sizeof(Obj) + est supérieur à, disons, 4*sizeof(Obj*) (et si le smart_ptr lui-même n'est pas énorme), alors ce n'est pas si grave.

Il est plus probable que ce soit le niveau supplémentaire d'indirection du pointeur intelligent qui cause votre problème.

Par coïncidence, je discutais au déjeuner avec un type qui avait un pointeur intelligent compté en référence qui utilisait un handle, c'est-à-dire un niveau d'indirection, quelque chose du genre

template<typename T> class refcntptr {
    refcnt_handle<T> handle;
public:
    refcntptr(T*obj) {
        this->handle = new refcnt_handle<T>();
        this->handle->ptr = obj;
        this->handle->count = 1;
    }
};
template<typename T> class refcnt_handle {
    T* ptr;
    int count;
    friend refcnt_ptr<T>;
};

(Je ne le coderais pas de cette façon, mais cela sert à l'exposition).

La double indirection ce->handle->ptr peut être un gros problème de performance. Ou même une triple indirection, this->handle->ptr->field. Au minimum, sur une machine avec des hits de cache L1 de 5 cycles, chaque this->handle->ptr->field prendrait 10 cycles. Et serait beaucoup plus difficile à chevaucher qu'une simple poursuite de pointeur. Mais, pire encore, si chacun d'entre eux est un manque de cache L1, même s'il n'y a que 20 cycles vers la L2... eh bien, il est beaucoup plus difficile de cacher 2*20=40 cycles de latence de manque de cache, qu'un seul manque de cache L1.

En général, il est conseillé d'éviter les niveaux d'indirection dans les pointeurs intelligents. Au lieu de pointer vers un handle, vers lequel tous les pointeurs intelligents pointent, et qui lui-même pointe vers l'objet, vous pouvez rendre le pointeur intelligent plus grand en le faisant pointer vers l'objet ainsi que vers le handle. (Qui n'est alors plus ce qu'on appelle communément un handle, mais plutôt un objet d'information).

Par exemple

template<typename T> class refcntptr {
    refcnt_info<T> info;
    T* ptr;
public:
    refcntptr(T*obj) {
        this->ptr = obj;
        this->info = new refcnt_handle<T>();
        this->info->count = 1;
    }
};
template<typename T> class refcnt_info {
    T* ptr; // perhaps not necessary, but useful.
    int count;
    friend refcnt_ptr<T>;
};

Quoi qu'il en soit, un profil temporel est votre meilleur ami.


Oh, oui - le matériel Intel EMON peut également vous dire combien de cycles vous avez attendu sur un PC. Cela permet de distinguer un grand nombre de ratés de L1 d'un petit nombre de ratés de L2.

0 votes

+ Tant que nous sommes sur le sujet, voici ce que j'aimerais que le CPU fasse : Quand il entre dans un cache-wait (L1 ou L2), il enregistre le PC qui l'a déclenché. Ensuite, si une interruption a été demandée, il devrait permettre qu'on y réponde, soit pendant, soit juste après l'attente du cache, et laisser cette information être interrogée (ce n'est pas grave si cela provoque le redémarrage de l'attente). Personnellement, je recommande de ne pas faire la somme par région, car cela ne fait que masquer les endroits précis qui posent problème.

0 votes

Malheureusement, sur de nombreuses machines, le PC (Ip au pays des x86) de l'instruction qui a causé le manque de cache n'est pas disponible. Le PC (IP) de l'instruction qui bloque la retraite en attendant que le manque de cache revienne est disponible, mais pas le PC qu'il attend. Nous n'avons même pas, à portée de main, ce que l'instruction en attente attend - tout ce qu'elle sait, c'est qu'elle attend un registre d'entrée. // Maintenant, certains processeurs plus récents acheminent le PC jusqu'aux unités de mémoire, pour faire des choses comme la prédiction STLF. Si j'étais encore chez Intel, je l'aurais déjà fait.

0 votes

AMD IBS (Instruction ased Sampling) est pertinent ici.

5voto

Paul R Points 104036

Cela dépend du système d'exploitation et du processeur que vous utilisez. Par exemple, pour Mac OS X et x86 ou ppc, Requin fera le profilage des manques de cache. Idem pour Zoom sur Linux.

0 votes

Malheureusement, Shark n'est plus pris en charge par Apple, et il ne fonctionnera plus très longtemps avec les nouvelles versions d'OSX/iOS. Zoom est toujours activement développée/mise à jour et a reçu une version majeure il y a quelques mois seulement.

1 votes

@federal : en effet - c'est triste que Shark soit maintenant tombé dans un état d'abandon - Instruments n'est tout simplement pas à la hauteur pour le genre de tâches auxquelles Shark excellait. J'ai décidé de m'en tenir à Snow Leopard et Xcode 3.2.6 aussi longtemps que possible - peut-être que dans un an ou deux, Xcode 4.x et Instruments seront plus utiles.

0 votes

MISE À JOUR : Zoom (v3.0) présente maintenant des profils sur Mac OS X et Linux. Shark a officiellement disparu.

5voto

Si vous utilisez un processeur AMD, vous pouvez obtenir CodeAnalyst apparemment libre comme dans la bière.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X