Bonjour,
Je suis en train d’écrire un programme C++ dans lequel je souhaite avoir une plage mémoire partagé entre 2 threads: un thread (W) écrit des données petit à petit et l’autre thread ® va lire ces données de temps en temps. La mémoire représente une image où chaque pixel est stocké dans un entier 32 bits (RGBA).
La contrainte de cohérence que j’ai est à peine plus forte que celle d'eventual consistency:
- si W s’arrête d’écrire dans la mémoire, il doit exister un instant t à partir duquel pour tout t′>t, R doit lire la dernière valeur écrite par W dans chaque pixel.
- Toute valeur lu par R doit avoir été écrite par W.
Évidemment, je souhaiterais que W ait des performances les plus proches possible de celles qu’il aurait si R n’existait pas et qu’il y ait le moins de délai possible entre le moment où W écrit dans un pixel et celui où R peut lire cette valeur.
Pour l’implémentation, le plus simple serait évidemment de ne pas avoir de synchronisation du tout. Seulement, ce n’est pas légal en C++ puisqu’avoir une même zone mémoire écrite et lu par 2 threads différents est une data race à moins que les opérations soient atomiques ou que l’une arrive avant l’autre (en suivant la définition C++ du terme)1. Et une data race, c’est un comportement indéterminé et je voudrais éviter ça. Utiliser le arrive avant nécessite une synchronisation entre les deux threads (via mutex, sémaphore, barrière ou autre), ce qui a typiquement un impact négatif assez fort sur les performances. Il ne me reste donc que l’option d’utiliser des opérations atomiques.
Pour l’instant, mon implémentation se résume à ceci:
// Initialize data
auto pixels = std::make_unique<std::vector<std::atomic<uint32_t>>>(num_pixels);
for (auto& pixel : *pixels) {
pixel = 0;
}
// Thread R
while (true) {
// Read data
for (int i = 0; i < pixels->size(); ++i) {
const uint32_t pixel = (*pixels)[i].load(std::memory_order_relaxed);
// Use pixel
}
// Do something else
}
// Thread W
while (true) {
const size_t i = ...; // Find the next pixel to write
const uint32_t color = ...; // Find the color of that pixel
(*pixels)[i].store(color, std::memory_order_relaxed);
}
Le programme en question fonctionne sans problème. En revanche, je suis un peu déçu du fait que le compilo (clang++ 11.1.0) refuse de vectoriser la boucle de lecture à cause de l’opération atomique: remark: loop not vectorized: read with atomic ordering or volatile read [-Rpass-analysis=loop-vectorize]
.
De ce que je comprends, std::memory_order_relaxed
spécifie uniquement que l’opération doit être atomique, mais ne spécifie rien pour la synchronisation, ce qui est exactement ce que je veux. Est-ce que quelqu’un sait d’où viens ce manque de vectorisation? Du compilo trop prudent? De mon manque de compréhension de l’opération atomique que j’utilise? Autre?
Au passage, si quelqu’un à une meilleur idée de comment implémenter mon programme, je suis preneur.