Je suis nouveau dans l'optimisation du code avec les instructions SSE/SSE2 et jusqu'à présent je ne suis pas allé très loin. À ma connaissance, une fonction optimisée pour SSE ressemblerait à ceci:
void sse_func(const float* const ptr, int len){
if( ptr is aligned )
{
for( ... ){
// unroll loop by 4 or 2 elements
}
for( ....){
// handle the rest
// (non-optimized code)
}
} else {
for( ....){
// regular C code to handle non-aligned memory
}
}
}
Cependant, comment déterminer correctement si la mémoire ptr
pointe vers est alignée par exemple 16 octets? Je pense que je dois inclure le chemin de code C normal pour la mémoire non alignée car je ne peux pas m'assurer que chaque mémoire transmise à cette fonction sera alignée. Et utiliser l'intrinsèque pour charger des données de la mémoire non alignée dans les registres SSE semble être horriblement lent (même plus lent que le code C normal).
Merci d'avance...
EDIT: la conversion vers long
est un moyen peu coûteux de se protéger contre la possibilité la plus probable que int et les pointeurs soient de tailles différentes de nos jours.
Comme indiqué dans les commentaires ci-dessous, il existe de meilleures solutions si vous souhaitez inclure un en-tête ...
Un pointeur p
est aligné sur une limite de 16 octets ss ((unsigned long)p & 15) == 0
.
#define is_aligned(POINTER, BYTE_COUNT) \
(((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)
Le transtypage en void *
(Ou, en équivalence, char *
) Est nécessaire car la norme garantit uniquement une conversion inversible en uintptr_t
Pour void *
.
Si vous voulez une sécurité de type, envisagez d'utiliser une fonction en ligne:
static inline _Bool is_aligned(const void *restrict pointer, size_t byte_count)
{ return (uintptr_t)pointer % byte_count == 0; }
et espérons des optimisations du compilateur si byte_count
est une constante de temps de compilation.
Pourquoi devons-nous convertir envoid *
?
Le langage C permet différentes représentations pour différents types de pointeurs, par exemple, vous pouvez avoir un type void *
64 bits (tout l'espace d'adressage) et un type foo *
32 bits (un segment).
La conversion foo *
-> void *
Peut impliquer un calcul réel, par exemple en ajoutant un décalage. La norme laisse également à l'implémentation ce qui se passe lors de la conversion de pointeurs (arbitraires) en entiers, mais je soupçonne qu'elle est souvent implémentée en tant que noop.
Pour une telle implémentation, foo *
-> uintptr_t
-> foo *
Fonctionnerait, mais foo *
-> uintptr_t
-> void *
Et void *
-> uintptr_t
-> foo *
Ne le serait pas. Le calcul de l'alignement ne fonctionnerait pas non plus de manière fiable car vous ne vérifiez l'alignement que par rapport au décalage de segment, ce qui peut ou non être ce que vous voulez.
En conclusion: utilisez toujours void *
Pour obtenir un comportement indépendant de l'implémentation.
D'autres réponses suggèrent une opération ET avec un ensemble de bits faibles et une comparaison avec zéro.
Mais un test plus simple serait de faire un MOD avec la valeur d'alignement souhaitée et de comparer à zéro.
#define ALIGNMENT_VALUE 16u
if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
// ptr is aligned
}
Avec un modèle de fonction comme
#include <type_traits>
template< typename T >
bool is_aligned(T* p){
return !(reinterpret_cast<uintptr_t>(p) % std::alignment_of<T>::value);
}
vous pouvez vérifier l'alignement lors de l'exécution en invoquant quelque chose comme
struct foo_type{ int bar; }foo;
assert(is_aligned(&foo)); // passes
Pour vérifier que les mauvais alignements échouent, vous pouvez le faire
// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));
C'est essentiellement ce que j'utilise. En faisant de l'entier un modèle, je m'assure que le temps de compilation est étendu, donc je ne finirai pas avec une opération modulo lente quoi que je fasse.
J'aime toujours vérifier mes entrées, donc d'où l'assertion du temps de compilation. Si votre valeur d'alignement est fausse, eh bien, elle ne se compilera pas ...
template <unsigned int alignment>
struct IsAligned
{
static_assert((alignment & (alignment - 1)) == 0, "Alignment must be a power of 2");
static inline bool Value(const void * ptr)
{
return (((uintptr_t)ptr) & (alignment - 1)) == 0;
}
};
Pour voir ce qui se passe, vous pouvez utiliser ceci:
// 1 of them is aligned...
int* ptr = new int[8];
for (int i = 0; i < 8; ++i)
std::cout << IsAligned<32>::Value(ptr + i) << std::endl;
// Should give '1'
int* ptr2 = (int*)_aligned_malloc(32, 32);
std::cout << IsAligned<32>::Value(ptr2) << std::endl;
Laissez ça aux professionnels,
bool is_aligned(const void* ptr, std::size_t alignment) noexcept;
exemple:
char D[1];
assert( boost::alignment::is_aligned(&D[0], alignof(double)) ); // might fail, sometimes
Pouvez-vous simplement 'et' le ptr avec 0x03 (aligné sur 4s), 0x07 (aligné sur 8s) ou 0x0f (aligné sur 16s) pour voir si l'un des bits les plus bas est défini?