Domanda

Sono nuovo di ottimizzazione del codice con le istruzioni / SSE2 SSE e fino ad ora non ho ottenuto molto lontano. A mia conoscenza una funzione comune SSE-ottimizzato sarebbe simile a questa:

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
        }
    }
}

Tuttavia, come faccio correttamente determinare se i punti ptr memoria è allineato per esempio 16 byte? Penso di avere per includere il percorso regolare codice C per la memoria non allineati come non posso fare in modo che ogni memoria passata a questa funzione sarà allineato. E utilizzando le intrinseci per caricare i dati dalla memoria non allineati nei registri SSE sembra essere orribile lento (anche più lento rispetto al codice normale C).

Grazie in anticipo ...

È stato utile?

Soluzione

EDIT: fusione per long è un modo economico per proteggersi contro la più probabile possibilità di int e puntatori che sono diverse dimensioni al giorno d'oggi.

Come sottolineato nei commenti qui sotto, ci sono soluzioni migliori se si è disposti a includere un'intestazione ...

Un p puntatore è allineato su un confine di 16 byte se e solo se ((unsigned long)p & 15) == 0.

Altri suggerimenti

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

Il getto di void * (o, equivalenty, char *) è necessaria perché lo standard garantisce solo una conversione invertibile a uintptr_t per void *.

Se si desidera la sicurezza di tipo, considerare l'utilizzo di una funzione inline:

static inline _Bool is_aligned(const void *restrict pointer, size_t byte_count)
{ return (uintptr_t)pointer % byte_count == 0; }

e di speranza per le ottimizzazioni del compilatore se byte_count è una costante della fase di compilazione.

Perché abbiamo bisogno per convertire in void *

Il linguaggio C permette rappresentazioni diversi per i diversi tipi di puntatore, ad esempio, si potrebbe avere un tipo a 64 bit void * (l'intero spazio di indirizzamento) e un tipo foo * a 32 bit (un segmento).

Il foo * conversione -> void * potrebbe comportare un calcolo effettivo, ad esempio aggiungendo un offset. Lo standard lascia anche fino alla realizzazione cosa succede quando la conversione di puntatori (arbitrario) a numeri interi, ma ho il sospetto che spesso è implementato come un noop.

Per tale implementazione, foo * -> uintptr_t -> foo * avrebbe funzionato, ma foo * -> uintptr_t -> void * e void * -> uintptr_t -> foo * non sarebbe. Il calcolo di allineamento potrebbe anche non funzionare correttamente a causa di controllare solo allineamento relativo al segmento offset, che potrebbe o non potrebbe essere quello che vuoi.

In conclusione:. Utilizzare sempre void * per ottenere un comportamento implementazione-indipendente

Altre risposte suggeriscono un'operazione AND con bassi bit impostati, e il confronto a zero.

Ma un test più diretto sarebbe quello di fare un MOD con il valore di allineamento desiderato e confronta a zero.

#define ALIGNMENT_VALUE     16u

if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
    // ptr is aligned
}

Con un modello di funzione come

#include <type_traits>

template< typename T >
bool is_aligned(T* p){
    return !(reinterpret_cast<uintptr_t>(p) % std::alignment_of<T>::value);
}

si potrebbe verificare l'allineamento in fase di esecuzione invocando qualcosa come

struct foo_type{ int bar; }foo;
assert(is_aligned(&foo)); // passes

Per controllare che i cattivi allineamenti non riescono, si potrebbe fare

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

Questo è fondamentalmente ciò che sto usando. Rendendo il numero intero un modello, mi assicurarsi che sia ampliato tempo di compilazione, quindi non voglio finire con un'operazione lenta modulo qualunque cosa io faccia.

mi piace sempre controllare il mio ingresso, in modo da qui il tempo asserzione di compilazione. Se il valore di allineamento è sbagliato, beh allora non compila ...

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;
    }
};

Per vedere che cosa sta succedendo, è possibile utilizzare questo:

// 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;

Si può solo 'e' Il PTR con 0x03 (allineato a 4s), 0x07 (allineato a 8s) o 0x0F (allineato a 16 anni) per vedere se uno dei bit più bassi sono impostati?

Lascia che ai professionisti,

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; 

Esempio:

        char D[1];
        assert( boost::alignment::is_aligned(&D[0], alignof(double)) ); //  might fail, sometimes

Come su:

void *mem = malloc(1024+15); 
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top