Pergunta

Sou novo em otimizar o código com instruções SSE/SSE2 e até agora não cheguei muito longe. Que eu saiba, uma função otimizada com SSE comum ficaria assim:

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

No entanto, como faço para determinar corretamente se a memória ptr Os pontos para o está alinhado por 16 bytes? Eu acho que tenho que incluir o caminho regular do código C para a memória não alinhada, pois não posso garantir que toda memória passada para essa função seja alinhada. E usar os intrínsecos para carregar dados da memória inalinada nos registros SSE parece ser horrível lento (ainda mais lento que o código C regular).

Agradeço antecipadamente...

Foi útil?

Solução

Editar: lançar para long é uma maneira barata de se proteger contra a possibilidade mais provável de INT e ponteiros serem tamanhos diferentes hoje em dia.

Como apontado nos comentários abaixo, existem soluções melhores se você estiver disposto a incluir um cabeçalho ...

Um ponteiro p está alinhado em um limite de 16 bytes IFF ((unsigned long)p & 15) == 0.

Outras dicas

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

O elenco para void * (ou, equivalente, char *) é necessário porque o padrão apenas garante uma conversão invertível para uintptr_t por void *.

Se você deseja que o tipo de segurança, considere usar uma função embutida:

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

e esperança para otimizações do compilador se byte_count é uma constante de tempo de compilação.

Por que precisamos converter para void * ?

O idioma C permite representações diferentes para diferentes tipos de ponteiro, por exemplo, você pode ter um 64 bits void * tipo (todo o espaço de endereço) e um 32 bits foo * tipo (um segmento).

A conversão foo * -> void * pode envolver um cálculo real, por exemplo, adicionando um deslocamento. O padrão também deixa para a implementação o que acontece ao converter (arbitrário) ponteiros para números inteiros, mas suspeito que seja frequentemente implementado como um noop.

Para tal implementação, foo * -> uintptr_t -> foo * funcionaria, mas foo * -> uintptr_t -> void * e void * -> uintptr_t -> foo * não. O cálculo do alinhamento também não funcionaria de maneira confiável, porque você verifica apenas o alinhamento em relação ao deslocamento do segmento, o que pode ou não ser o que você deseja.

Em conclusão: sempre use void * para obter o comportamento independente da implementação.

Outras respostas sugerem uma e operação com conjunto de bits baixos e comparando com zero.

Mas um teste mais direto seria fazer um mod com o valor de alinhamento desejado e comparar com zero.

#define ALIGNMENT_VALUE     16u

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

Com um modelo de função como

#include <type_traits>

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

Você pode verificar o alinhamento no tempo de execução, invocando algo como

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

Para verificar que os maus alinhamentos falham, você pode fazer

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

Isso é basicamente o que estou usando. Ao fazer do número inteiro um modelo, garanto que é o tempo expandido de compilação, para não acabar com uma operação lenta do módulo, o que fizer.

Eu sempre gosto de verificar minha entrada, portanto, a afirmação do tempo de compilação. Se o seu valor de alinhamento estiver errado, bem, não será compilado ...

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

Para ver o que está acontecendo, você pode usar isso:

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

Você pode apenas 'e' o PTR com 0x03 (alinhado em 4s), 0x07 (alinhado em 8s) ou 0x0f (alinhado em 16s) para ver se algum dos bits mais baixos está definido?

Deixe isso para os profissionais,

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; 

exemplo:

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

Que tal:

void *mem = malloc(1024+15); 
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top