Jak określić, czy pamięć jest wyrównana?

Jestem nowy w optymalizacji kodu za pomocą instrukcji SSE/SSE2 i do tej pory nie zaszedłem zbyt daleko. Według mojej wiedzy popularna funkcja zoptymalizowana pod kątem SSE wyglądałaby tak:

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

Jak jednak poprawnie określić, czy pamięć ptr wskazuje na jest wyrównana np. o 16 bajtów? Myślę, że muszę dołączyć zwykłą ścieżkę kodu C dla pamięci niezrównanej, ponieważ nie mogę upewnić się, że każda pamięć przekazywana do tej funkcji będzie wyrównana. I za pomocą iskrobezpiecznych do ładowania danych z niepodpisanych pamięć do rejestrów SSE wydaje się być strasznie wolna (nawet wolniejsza niż zwykły kod C).

Z góry dziękuję...
Author: jww, 2009-12-14

7 answers

EDIT: casting do long to tani sposób na zabezpieczenie się przed najbardziej prawdopodobną możliwością, że INT i wskaźniki będą w dzisiejszych czasach różnej wielkości.

Jak wspomniano w komentarzach poniżej, istnieją lepsze rozwiązania, jeśli chcesz dołączyć nagłówek...

Wskaźnik p jest wyrównany do 16-bajtowej granicy iff ((unsigned long)p & 15) == 0.

 24
Author: Pascal Cuoq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-12 17:11:05
#define is_aligned(POINTER, BYTE_COUNT) \
    (((uintptr_t)(const void *)(POINTER)) % (BYTE_COUNT) == 0)

Rzut do void * (lub, równoważnie, char *) jest konieczny, ponieważ norma gwarantuje jedynie konwersję odwracalną do uintptr_t dla void *.

Jeśli chcesz mieć bezpieczeństwo typu, rozważ użycie funkcji inline:

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

I nadzieję na optymalizację kompilatora, Jeśli byte_count jest stałą czasu kompilacji.

Dlaczego musimy konwertować na void * ?

Język C umożliwia różne reprezentacje dla różnych typów wskaźników, np. można mają typ 64-bitowy void * (cała przestrzeń adresowa) i typ 32-bitowy foo * (segment a).

Konwersja foo * -> void * może obejmować rzeczywiste obliczenia, np. dodanie offsetu. Standard pozostawia również do implementacji to, co dzieje się podczas konwersji (arbitralnych) wskaźników na liczby całkowite, ale podejrzewam, że często jest implementowany jako noop.

Dla takiej realizacji, foo * -> uintptr_t -> foo * zadziałałoby, ale foo * -> uintptr_t -> void * oraz void * -> uintptr_t -> foo * Nie. obliczenia wyrównania również nie będą działać niezawodnie, ponieważ sprawdzasz wyrównanie tylko względem offsetu segmentu, które może być lub nie być tym, czego chcesz.

Podsumowując: Zawsze używaj void *, aby uzyskać niezależne od implementacji zachowanie.

 42
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-26 11:24:10

Inne odpowiedzi sugerują operację an i z ustawionymi niskimi bitami i w porównaniu do zera.

Ale bardziej prostym testem byłoby wykonanie MOD z pożądaną wartością wyrównania i porównanie do zera.

#define ALIGNMENT_VALUE     16u

if (((uintptr_t)ptr % ALIGNMENT_VALUE) == 0)
{
    // ptr is aligned
}
 20
Author: Craig McQueen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-13 23:27:43

Z szablonem funkcji jak

#include <type_traits>

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

Możesz sprawdzić wyrównanie w czasie wykonywania, wywołując coś w rodzaju

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

Aby sprawdzić, czy złe wyrównanie nie powiedzie się, możesz zrobić

// would almost certainly fail
assert(is_aligned((foo_type*)(1 + (uintptr_t)(&foo)));
 7
Author: rubicks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-25 17:56:41

Czy możesz po prostu 'i' PST z 0x03 (wyrównane na 4s), 0x07 (wyrównane na 8s) lub 0x0f (wyrównane na 16s), aby sprawdzić, czy któryś z najniższych bitów jest ustawiony?

 2
Author: Paul Tomblin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-12-13 23:17:04

To jest w zasadzie to, czego używam. Czyniąc liczbę całkowitą szablonem, upewniam się, że jest to rozszerzony czas kompilacji, więc nie skończę z powolną operacją modulo, cokolwiek zrobię.

Zawsze lubię sprawdzać moje dane wejściowe, stąd twierdzenie o czasie kompilacji. Jeśli wartość wyrównania jest nieprawidłowa, nie będzie kompilowana...

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

Aby zobaczyć, co się dzieje, możesz użyć tego:

// 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;
 2
Author: atlaste,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-27 08:03:44

A może:

void *mem = malloc(1024+15); 
void *ptr =( (*(char*)mem) - (*(char *)mem % 16) );
 -3
Author: Ramana,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-04 09:03:37