web-dev-qa-db-fra.com

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

Je sais que je peux utiliser gprof pour comparer mon code.

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

En conséquence, j'ai cette couche supplémentaire qui affecte à peu près toutes les fonctions et les vis avec mise en cache.

Existe-t-il un moyen de mesurer le temps perdu par mon processeur en raison de manquements au cache?

Merci!

44
anon

Vous pouvez essayer cachegrind et c'est kcachegrind frontal.

18
Taavi

Vous pouvez trouver un outil qui accède aux compteurs de performances du processeur. Il y a probablement un registre dans chaque cœur qui compte les échecs L1, L2, etc. Cachegrind effectue également une simulation cycle par cycle.

Cependant, je ne pense pas que ce serait utile. Vos objets proxy sont probablement modifiés par leurs propres méthodes. Un profileur conventionnel vous dira combien de temps ces méthodes prennent. Aucun outil de profil ne vous dirait 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 est apparue boost::intrusive_ptr qui pourrait vous intéresser. Il ne semble pas prendre en charge quelque chose comme weak_ptr, mais la conversion de votre programme peut être triviale, et vous saurez alors avec certitude le coût des comptes de référence non intrusifs.

11
Potatoswatter

Linux prend en charge avec perf à partir de 2.6.31. Cela vous permet d'effectuer les opérations suivantes:

  • compilez votre code avec -g pour inclure les informations de débogage
  • exécutez votre code, par exemple l'utilisation du cache de dernier niveau manque les compteurs: perf record -e LLC-loads,LLC-load-misses yourExecutable
  • courir perf report
    • après avoir acquitté le message initial, sélectionnez le LLC-load-misses ligne,
    • puis par ex. la première fonction et
    • puis annotate. Vous devriez voir les lignes (dans le code d'assembly, entourées du code source d'origine) et un nombre indiquant quelle fraction du cache de dernier niveau manque pour les lignes où des échecs de cache se sont produits.
10
Andre Holzner

Poursuivant dans le sens de la réponse de @ Mike_Dunlavey:

Tout d'abord, obtenez un profil temporel à l'aide de votre outil préféré: VTune ou PTU ou OProf.

Ensuite, obtenez un profil d'échec de cache. Le cache L1 manque, ou le cache L2 manque, ou ...

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

Remarque: je "réduis" souvent les données en les résumant par fonction ou (si j'ai la technologie) par boucle. Ou par bacs de, disons, 64 octets. La comparaison des compteurs de programmes individuels n'est souvent pas utile, car les compteurs de performances sont flous - l'endroit où vous voyez un échec de cache être signalé est souvent plusieurs instructions différentes de l'endroit où cela s'est réellement produit.

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

Graphiques "Iceberg": l'axe X est PC, l'axe Y positif est le temps, l'accès Y négatif est le cache manque. Recherchez les endroits qui montent et descendent.

(Les graphiques "entrelacés" sont également utiles: même idée, l'axe X est PC, tracer à la fois le temps et le cache sur l'axe Y, mais avec des lignes verticales étroites de différentes couleurs, généralement rouge et bleu. Lieux où il y a beaucoup de temps et de cache les échecs passés auront des lignes rouges et bleues finement entrelacées, presque violettes. Cela s'étend aux échecs de cache L2 et L3, tous sur le même graphique. Soit dit en passant, vous voudrez probablement "normaliser" les nombres, soit jusqu'à% de l'âge total temps ou cache manque, ou, mieux encore,% âge du point de temps maximum de données ou cache manque. Si vous vous trompez d'échelle, vous ne verrez rien.)

graphiques XY: pour chaque bac d'échantillonnage (PC, ou fonction, ou boucle, ou ...) tracer un point dont la coordonnée X est le temps normalisé, et dont la coordonnée Y est la normalisée cache manque. Si vous obtenez un grand nombre de points de données dans le coin supérieur droit - grand% de temps d'âge ET grand% de cache d'âge manquant - 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. Enfin, j'ai vérifié que VTune ne le faisait pas pour vous. J'ai utilisé gnuplot et Excel. (Avertissement: Excel meurt au-dessus de 64 000 points de données.)


Plus de conseils:

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

Soit dit en passant, je soupçonne que votre problème n'est PAS avec le pointeur intelligent provoquant le cache du cache. À moins que vous ne fassiez smart_ptr <int> partout. Si vous faites 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 tant que ça.

Plus probablement, c'est le niveau supplémentaire d'indirection que le pointeur intelligent fait qui cause votre problème.

Par coïncidence, je parlais à un gars au déjeuner qui avait un pointeur intelligent compté par référence qui utilisait une poignée, c'est-à-dire un niveau d'indirection, quelque chose comme

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 il sert d'exposé.)

La double indirection this-> handle-> ptr peut être un gros problème de performances. Ou même une triple indirection, ce champ-> handle-> ptr->. Au moins, sur une machine avec 5 hits de cache L1 de cycle, chaque champ this-> handle-> ptr-> prendrait 10 cycles. Et être beaucoup plus difficile à chevaucher qu'une seule poursuite de pointeur. Mais, pire, si chacun est un échec de cache L1, même s'il n'y avait que 20 cycles vers le L2 ... eh bien, il est beaucoup plus difficile de masquer 2 * 20 = 40 cycles de latence de manque de cache, qu'un seul échec L1.

En général, il est conseillé d'éviter les niveaux d'indirection dans les pointeurs intelligents. Au lieu de pointer vers une poignée vers laquelle pointent tous les pointeurs intelligents, qui pointe elle-même vers l'objet, vous pouvez agrandir le pointeur intelligent en le faisant pointer sur l'objet ainsi que sur la poignée. (Ce qui n'est alors plus ce que l'on appelle communément un handle, mais qui ressemble plus à un objet info.)

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 horaire 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 peut distinguer un grand nombre de ratés L1 d'un petit nombre de ratés L2.

6
Krazy Glew

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

5
Paul R

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

5
Arthur Kalliokoski

oprofile est un autre outil de profilage basé sur le compteur de performances du processeur. Vous pouvez afficher ses résultats à l'aide de kcachegrind.

2
jpalecek

Mon conseil serait d'utiliser PTU (Performance Tuning Utility) d'Intel.

Cet utilitaire est le descendant direct de VTune et fournit le meilleur profileur d'échantillonnage disponible. Vous pourrez suivre où le CPU passe ou perd du temps (à l'aide des événements matériels disponibles), et cela sans ralentissement de votre application ni perturbation du profil. Et bien sûr, vous pourrez rassembler tous les événements de manque de ligne de cache que vous recherchez.

2
Fabien Hure

Voici une sorte de réponse générale .

Par exemple, si votre programme passe, disons, 50% de son temps dans le cache manque, alors 50% du temps lorsque vous le mettez en pause, le compteur du programme sera aux emplacements exacts où il attend les récupérations de la mémoire qui causent le cache manque.

2
Mike Dunlavey