Je travaille sur la mise en œuvre d'un tampon en anneau à producteur unique et à consommateur unique, et j'ai deux exigences :
- Aligner une instance unique allouée au tas d'un tampon circulaire sur une ligne de cache.
- Aligner un champ dans un tampon circulaire sur une ligne de cache (pour éviter un faux partage).
Ma classe ressemble à quelque chose comme :
#define CACHE_LINE_SIZE 64 // To be used later.
template<typename T, uint64_t num_events>
class RingBuffer { // This needs to be aligned to a cache line.
public:
....
private:
std::atomic<int64_t> publisher_sequence_ ;
int64_t cached_consumer_sequence_;
T* events_;
std::atomic<int64_t> consumer_sequence_; // This needs to be aligned to a cache line.
};
J'aborderai tout d'abord le point 1, à savoir alignement d'une instance unique allouée au tas de la classe. Il y a plusieurs façons de procéder :
-
Utilisez le c++ 11
alignas(..)
spécificateur :template<typename T, uint64_t num_events> class alignas(CACHE_LINE_SIZE) RingBuffer { public: .... private: // All the private fields. };
-
Utilisez
posix_memalign(..)
+ placementnew(..)
sans modifier la définition de la classe. Le problème est que cette méthode n'est pas indépendante de la plate-forme :void* buffer; if (posix_memalign(&buffer, 64, sizeof(processor::RingBuffer<int, kRingBufferSize>)) != 0) { perror("posix_memalign did not work!"); abort(); } // Use placement new on a cache aligned buffer. auto ring_buffer = new(buffer) processor::RingBuffer<int, kRingBufferSize>();
-
Utiliser l'extension GCC/Clang
__attribute__ ((aligned(#)))
template<typename T, uint64_t num_events> class RingBuffer { public: .... private: // All the private fields. } __attribute__ ((aligned(CACHE_LINE_SIZE)));
-
J'ai essayé d'utiliser le C++ 11 normalisé.
aligned_alloc(..)
au lieu de la fonctionposix_memalign(..)
mais GCC 4.8.1 sur Ubuntu 12.04 n'a pas pu trouver la définition dansstdlib.h
Sont-ils tous garantis de faire la même chose ? Mon objectif est l'alignement de la ligne de cache, donc toute méthode qui a des limites sur l'alignement (disons double mot) ne fera pas l'affaire. L'indépendance de la plateforme, qui indique l'utilisation de la norme alignas(..)
est un objectif secondaire.
Je ne sais pas si alignas(..)
et __attribute__((aligned(#)))
ont une certaine limite qui pourrait être inférieure à la ligne de cache de la machine. Je ne peux plus le reproduire, mais en imprimant des adresses, je pense que je n'ai pas toujours obtenu des adresses alignées sur 64 octets avec la fonction alignas(..)
. Au contraire posix_memalign(..)
semble toujours fonctionner. Une fois encore, je ne peux plus reproduire ce phénomène, alors peut-être ai-je fait une erreur.
Le deuxième objectif est de aligner un champ dans une classe/structure à une ligne de cache. Je fais cela pour éviter les faux partages. J'ai essayé les méthodes suivantes :
-
Utilisez le C++ 11
alignas(..)
spécificateur :template<typename T, uint64_t num_events> class RingBuffer { // This needs to be aligned to a cache line. public: ... private: std::atomic<int64_t> publisher_sequence_ ; int64_t cached_consumer_sequence_; T* events_; std::atomic<int64_t> consumer_sequence_ alignas(CACHE_LINE_SIZE); };
-
Utiliser l'extension GCC/Clang
__attribute__ ((aligned(#)))
template<typename T, uint64_t num_events> class RingBuffer { // This needs to be aligned to a cache line. public: ... private: std::atomic<int64_t> publisher_sequence_ ; int64_t cached_consumer_sequence_; T* events_; std::atomic<int64_t> consumer_sequence_ __attribute__ ((aligned (CACHE_LINE_SIZE))); };
Ces deux méthodes semblent s'aligner consumer_sequence
à une adresse située 64 octets après le début de l'objet, afin de savoir si consumer_sequence
est aligné sur le cache dépend du fait que l'objet lui-même est aligné sur le cache. Ma question est la suivante : existe-t-il de meilleures façons de procéder ?
EDIT :
La raison aligned_alloc
ne fonctionnait pas sur ma machine était que j'étais sur eglibc 2.15 (Ubuntu 12.04). Il a fonctionné sur une version plus récente d'eglibc.
De la page de manuel : _La fonction aligned_alloc()
a été ajouté à la glibc dans la version 2.16_ .
Cela le rend plutôt inutile pour moi puisque je ne peux pas avoir besoin d'une version aussi récente d'eglibc/glibc.
5 votes
Excellente question, voir l'article de Michael Spencer Conférence BoostCon 2013 . Je ne pense pas qu'il soit possible de s'aligner de manière portable sur plus de 16 octets (donc une ligne de cache de 64 octets et un alignement encore plus grand sur les pages de mémoire virtuelle ne sont pas supportés par la norme).
0 votes
@TemplateRex Merci pour le lien. L'exposé semble pertinent + 1.