Je travaille sur une mise en œuvre de tampon circulaire unique pour un seul producteur. J’ai deux exigences:
1) Alignez une seule instance de mémoire tampon en anneau allouée sur une ligne de cache.
2) Aligner un champ dans un tampon circulaire sur une ligne de cache (pour éviter un partage erroné).
Ma classe ressemble à quelque chose comme:
#define CACHE_LINE_SIZE 64 // To be used later. template class RingBuffer { // This needs to be aligned to a cache line. public: .... private: std::atomic publisher_sequence_ ; int64_t cached_consumer_sequence_; T* events_; std::atomic consumer_sequence_; // This needs to be aligned to a cache line. };
Permettez-moi d’abord d’aborder le point 1, c’est-à-dire d’ aligner une instance unique allouée au tas . Il y a plusieurs façons:
1) Utilisez le alignas(..)
c ++ 11 alignas(..)
:
template class alignas(CACHE_LINE_SIZE) RingBuffer { public: .... private: // All the private fields. };
2) Utilisez posix_memalign(..)
+ placement new(..)
sans modifier la définition de la classe. Cela souffre de ne pas être indépendant de la plate-forme:
void* buffer; if (posix_memalign(&buffer, 64, sizeof(processor::RingBuffer)) != 0) { perror("posix_memalign did not work!"); abort(); } // Use placement new on a cache aligned buffer. auto ring_buffer = new(buffer) processor::RingBuffer();
3) Utilisez l’extension GCC / Clang __atsortingbute__ ((aligned(#)))
template class RingBuffer { public: .... private: // All the private fields. } __atsortingbute__ ((aligned(CACHE_LINE_SIZE)));
4) J’ai essayé d’utiliser la fonction posix_memalign(..)
normalisée C ++ 11 au lieu de posix_memalign(..)
mais GCC 4.8.1 sur Ubuntu 12.04 n’a pas trouvé la définition dans stdlib.h
Tous ces éléments garantissent-ils de faire la même chose? Mon objective est l’alignement de la ligne de cache, de sorte que toute méthode comportant des limites d’alignement (disons un double mot) ne fonctionnera pas. L’indépendance de la plate-forme qui indiquerait l’utilisation des alignas(..)
normalisés alignas(..)
est un objective secondaire.
Je ne sais pas si les alignas(..)
et __atsortingbute__((aligned(#)))
ont une limite qui pourrait être inférieure à la ligne de cache sur la machine. Je ne peux plus le reproduire mais en imprimant des adresses, je pense que je n’ai pas toujours eu les adresses alignées de 64 octets avec alignas(..)
. Au contraire, posix_memalign(..)
semblait toujours fonctionner. Encore une fois, je ne peux plus reproduire cela, alors peut-être que je me suis trompé.
Le second objective est d’ aligner un champ dans une classe / structure avec une ligne de cache. Je le fais pour empêcher le faux partage. J’ai essayé les manières suivantes:
1) Utilisez le alignas(..)
C ++ 11 alignas(..)
:
template class RingBuffer { // This needs to be aligned to a cache line. public: ... private: std::atomic publisher_sequence_ ; int64_t cached_consumer_sequence_; T* events_; std::atomic consumer_sequence_ alignas(CACHE_LINE_SIZE); };
2) Utilisez l’extension GCC / Clang __atsortingbute__ ((aligned(#)))
template class RingBuffer { // This needs to be aligned to a cache line. public: ... private: std::atomic publisher_sequence_ ; int64_t cached_consumer_sequence_; T* events_; std::atomic consumer_sequence_ __atsortingbute__ ((aligned (CACHE_LINE_SIZE))); };
Ces deux méthodes semblent aligner consumer_sequence
sur une adresse de 64 octets après le début de l’object, de sorte que consumer_sequence
soit aligné sur le cache, selon que l’object lui-même est aligné dans le cache. Voici ma question: existe-t-il de meilleures façons de faire la même chose?
EDIT: La raison pour laquelle align_alloc ne fonctionnait pas sur ma machine était que j’étais sur eglibc 2.15 (Ubuntu 12.04). Il a travaillé sur une version ultérieure de eglibc.
A partir de la page de manuel : The function aligned_alloc() was added to glibc in version 2.16
.
Cela me rend inutile car je ne peux pas exiger une version aussi récente de eglibc / glibc.
Malheureusement, le meilleur que j’ai trouvé est d’allouer de l’espace supplémentaire, puis d’utiliser la partie “alignée”. Ainsi, le RingBuffer new
peut demander 64 octets supplémentaires, puis renvoyer la première partie alignée de 64 octets. Cela gaspille de l’espace mais vous donne l’alignement dont vous avez besoin. Vous devrez probablement définir la mémoire avant ce qui est renvoyé à l’adresse d’allocation réelle pour la désallouer.
[Memory returned][ptr to start of memory][aligned memory][extra memory]
(en supposant pas d’inheritance de RingBuffer) quelque chose comme:
void * RingBuffer::operator new(size_t request) { static const size_t ptr_alloc = sizeof(void *); static const size_t align_size = 64; static const size_t request_size = sizeof(RingBuffer)+align_size; static const size_t needed = ptr_alloc+request_size; void * alloc = ::operator new(needed); void *ptr = std::align(align_size, sizeof(RingBuffer), alloc+ptr_alloc, request_size); ((void **)ptr)[-1] = alloc; // save for delete calls to use return ptr; } void RingBuffer::operator delete(void * ptr) { if (ptr) // 0 is valid, but a noop, so prevent passing negative memory { void * alloc = ((void **)ptr)[-1]; ::operator delete (alloc); } }
Pour la deuxième exigence d’avoir un membre de données de RingBuffer
également aligné sur 64 octets, pour cela si vous savez que le début de this
est aligné, vous pouvez appuyer pour forcer l’alignement pour les membres de données.
La réponse à votre problème est std :: alignment_storage . Il peut être utilisé au plus haut niveau et pour les membres individuels d’une classe.
Après quelques recherches supplémentaires, mes pensées sont:
1) Comme @TemplateRex, il ne semble pas y avoir de méthode standard pour aligner plus de 16 octets. Donc, même si nous utilisons les alignas(..)
normalisés alignas(..)
il n’y a aucune garantie à moins que la limite d’alignement soit inférieure ou égale à 16 octets. Je devrai vérifier que cela fonctionne comme prévu sur une plate-forme cible.
2) __atsortingbute ((aligned(#)))
ou alignas(..)
ne peut pas être utilisé pour aligner un object alloué au tas comme je le pensais, par exemple new()
ne fait rien avec ces annotations. Ils semblent fonctionner pour des objects statiques ou des allocations de stack avec les mises en garde de (1).
Soit posix_memalign(..)
(non standard) ou aligned_alloc(..)
(normalisé mais ne pouvant pas fonctionner sur GCC 4.8.1) + placement new(..)
semble être la solution. Ma solution quand j’ai besoin de code indépendant de la plate-forme est des macros spécifiques au compilateur 🙂
3) L’alignement des champs struct / class semble fonctionner à la fois avec __atsortingbute ((aligned(#)))
et alignas()
comme indiqué dans la réponse. Encore une fois je pense que les mises en garde de (1) sur les garanties sur l’alignement sont debout.
Donc, ma solution actuelle consiste à utiliser posix_memalign(..)
+ placement new(..)
pour aligner une instance allouée au tas de ma classe car ma plate-forme cible est à présent Linux uniquement. J’utilise également les alignas(..)
pour aligner les champs car ils sont standardisés et au moins fonctionnent sur Clang et GCC. Je serai heureux de le changer si une meilleure réponse arrive.
Je ne sais pas si c’est le meilleur moyen d’aligner la mémoire allouée avec un nouvel opérateur, mais c’est certainement très simple!
C’est comme cela que cela se passe dans la désinfection des threads dans GCC 6.1.0
#define ALIGNED(x) __atsortingbute__((aligned(x))) static char myarray[sizeof(myClass)] ALIGNED(64) ; var = new(myarray) myClass;
Eh bien, dans sanitizer_common / sanitizer_internal_defs.h, il est également écrit
// Please only use the ALIGNED macro before the type. // Using ALIGNED after the variable declaration is not portable!
Je ne sais donc pas pourquoi l’ALIGNED est utilisé après la déclaration de variable. Mais c’est une autre histoire.