Question

Je suis nouveau à l'optimisation du code avec les instructions SSE / SSE2 et jusqu'à présent, je ne l'ai pas obtenu très loin. A ma connaissance une fonction SSE optimisé commune 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 puis-je déterminer correctement si les points de ptr de mémoire est aligné par exemple 16 Octets? Je pense que je dois inclure le chemin régulier de code C pour la mémoire non-alignés comme je ne peux pas vous assurer que chaque mémoire transmis à cette fonction sera alignée. Et en utilisant les intrinsics pour charger les données de la mémoire non alignée dans les registres SSE semble être horrible lent (encore plus lent que le code régulier C).

Merci d'avance ...

Était-ce utile?

La solution

EDIT: coulée à long est un moyen pas cher pour se protéger contre la possibilité la plus probable de int et pointeurs étant différentes tailles de nos jours.

Comme indiqué dans les commentaires ci-dessous, il y a de meilleures solutions si vous êtes prêt à inclure un en-tête ...

Un pointeur p est aligné sur un ((unsigned long)p & 15) == 0 ssi limite de 16 octets.

Autres conseils

#define is_aligned(POINTER, BYTE_COUNT) \
    (((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)

La distribution à void * (ou, equivalenty, char *) est nécessaire parce que la norme ne garantit une conversion inversible à uintptr_t pour void *.

Si vous voulez la 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 d'espoir pour les optimisations du compilateur si byte_count est une constante de compilation.

Pourquoi avons-nous besoin de convertir en void * ?

Le langage C permet des représentations différentes pour les différents types de pointeurs, par exemple, vous pourriez avoir un type de void * 64 bits (l'ensemble de l'espace d'adressage) et un type de foo * 32 bits (un segment).

Le foo * de conversion -> void * peut impliquer un calcul réel, par exemple en ajoutant un décalage. La norme laisse également à la mise en œuvre ce qui se passe lors de la conversion des pointeurs (arbitraires) en entiers, mais je soupçonne qu'il est souvent mis en œuvre comme noop.

Pour une telle mise en œuvre, foo * -> uintptr_t -> foo * fonctionnerait, mais foo * -> uintptr_t -> void * et void * -> uintptr_t -> foo * ne serait pas. Le calcul de l'alignement ne fonctionnera de manière fiable pas non plus parce que vous ne vérifier l'alignement par rapport au segment de décalage, ce qui pourrait ou pourrait ne pas être ce que vous voulez.

En conclusion:. Utilisez toujours void * pour obtenir le comportement de l'implémentation indépendante

D'autres réponses suggèrent une opération ET avec de faibles bits mis, et à comparer à zéro.

Mais un test plus straight-forward serait de faire un MOD avec la valeur d'alignement désiré et 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 faire

// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));

Ceci est essentiellement ce que j'utilise. En faisant l'entier un modèle, j'assure qu'il est étendu compilation, donc je ne vais pas finir avec une opération modulo lent tout ce que je fais.

Je aime toujours vérifier mon entrée, donc par conséquent l'affirmation de compilation. Si votre valeur d'alignement est mauvais, eh bien, il ne 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;

Pouvez-vous « et » le PTR avec 0x03 (aligné sur 4 s), 0x07 (aligné sur 8s) ou 0x0f (aligné sur 16S) pour voir si des bits les plus bas sont réglés?

Laissez-aux professionnels,

https : //www.boost.org/doc/libs/1_65_1/doc/html/align/reference.html#align.reference.functions.is_aligned

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

Que diriez-vous:

void *mem = malloc(1024+15); 
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top