2013-05-04 14 views
24

Oto kod normalnie korzystać, aby dostosować pamięć z Visual Studio i GCCnajlepszą metodą cross-platform, aby dostosować pamięć

inline void* aligned_malloc(size_t size, size_t align) { 
    void *result; 
    #ifdef _MSC_VER 
    result = _aligned_malloc(size, align); 
    #else 
    if(posix_memalign(&result, align, size)) result = 0; 
    #endif 
    return result; 
} 

inline void aligned_free(void *ptr) { 
    #ifdef _MSC_VER 
     _aligned_free(ptr); 
    #else 
     free(ptr); 
    #endif 

} 

jest ten kod grzywny w ogóle? Widziałem także ludzi używających _mm_malloc, _mm_free. W większości przypadków potrzebuję wyrównanej pamięci, aby używać SSE/AVX. Czy mogę ogólnie korzystać z tych funkcji? Ułatwiłoby to mój kod.

Wreszcie, łatwo jest utworzyć własną funkcję wyrównywania pamięci (patrz poniżej). Dlaczego więc istnieje tak wiele różnych wspólnych funkcji, aby uzyskać wyrównaną pamięć (wiele z nich działa tylko na jednej platformie)?

Ten kod wykonuje wyrównanie 16 bajtów.

float* array = (float*)malloc(SIZE*sizeof(float)+15); 

// find the aligned position 
// and use this pointer to read or write data into array 
float* alignedArray = (float*)(((unsigned long)array + 15) & (~0x0F)); 

// dellocate memory original "array", NOT alignedArray 
free(array); 
array = alignedArray = 0; 

Patrz: http://www.songho.ca/misc/alignment/dataalign.html i How to allocate aligned memory only using the standard library?

Edit: W przypadku ktoś obchodzi, mam pomysł dla funkcji z Eigen moim aligned_malloc() (Eigen/src/Rdzeń/util/Memory.h)

Edytuj: Właśnie odkryłem, że posix_memalign jest niezdefiniowany dla MinGW. Jednak _mm_malloc działa dla Visual Studio 2012, GCC, MinGW i kompilatora Intel C++, więc wydaje się być najbardziej wygodnym rozwiązaniem w ogóle. Wymaga to również użycia własnej funkcji _mm_free, chociaż w niektórych implementacjach można przekazywać wskaźniki od _mm_malloc do standardu free/delete.

+0

Chociaż "unsigned long" obsada adresu może działać w praktyce, może nie być przenośny między modelami danych ILP32/LP64/LLP64 (win64). –

Odpowiedz

4

Pierwsza proponowana przez ciebie funkcja rzeczywiście działa dobrze.

Funkcja "homebrew" również działa, ale ma tę wadę, że jeśli wartość jest już wyrównana, właśnie zmarnowałeś 15 bajtów. Czasami nie ma to znaczenia, ale system operacyjny może być w stanie zapewnić pamięć, która jest poprawnie przydzielona bez żadnych odpadów (i jeśli trzeba ją wyrównać do 256 lub 4096 bajtów, ryzykujesz marnowanie dużej ilości pamięci przez dodanie "wyrównania-1" bajty).

+0

Ale co z _mm_malloc? Czy to jest w porządku, jeśli używam SSE/AVX? Jeśli o to chodzi, dlaczego nie używać go, nawet jeśli nie używam SSE/AVX? –

+2

Jeśli kompilator obsługuje _mm_malloc(), jest to również poprawne rozwiązanie. Musisz również użyć '_mm_free()'. Oczywiście tak, można użyć '_mm_malloc()' do dowolnej alokacji pamięci - ale oczywiście małe przydziały spowodują marnowanie więcej miejsca niż "zwykłe". –

+0

okej, myślę, że zacznę używać _mm_malloc() i _mm_free(). Przynajmniej odpowiedzi na SO, gdy potrzebuję pamięci wyrównanej. To sprawia, że ​​kod jest znacznie prostszy. –

8

Dopóki nie masz nic przeciwko konieczności wywołania specjalnej funkcji do uwolnienia, twoje podejście jest w porządku. Zrobiłbym twoje #ifdef s w odwrotnym kierunku: zacznij od określonych przez standardy opcji i wracaj do specyficznych dla platformy. Na przykład:

  1. Jeśli __STDC_VERSION__ >= 201112L użyjesz aligned_alloc.
  2. Jeśli _POSIX_VERSION >= 200112L użyj .
  3. Jeśli zdefiniowano _MSC_VER, użyj rzeczy systemu Windows.
  4. ...
  5. Jeśli wszystko inne zawiedzie, po prostu użyć malloc/free i wyłączyć kod SSE/AVX.

Problem jest trudniejszy, jeśli chcesz przekazać przydzieloną wartość do free; Jest to poprawne dla wszystkich standardowych interfejsów, ale nie dla Windows i niekoniecznie ze starszą wersją memalign, jaką mają niektóre systemy podobne do uniksowych.

1

Jeśli obsługuje go kompilator, C++ 11 dodaje funkcję std::align do wyrównania wskaźnika środowiska wykonawczego.Można zaimplementować własną malloc/free tak (niesprawdzone):

template<std::size_t Align> 
void *aligned_malloc(std::size_t size) 
{ 
    std::size_t space = size + (Align - 1); 
    void *ptr = malloc(space + sizeof(void*)); 
    void *original_ptr = ptr; 

    char *ptr_bytes = static_cast<char*>(ptr); 
    ptr_bytes += sizeof(void*); 
    ptr = static_cast<void*>(ptr_bytes); 

    ptr = std::align(Align, size, ptr, space); 

    ptr_bytes = static_cast<void*>(ptr); 
    ptr_bytes -= sizeof(void*); 
    std::memcpy(ptr_bytes, original_ptr, sizeof(void*)); 

    return ptr; 
} 

void aligned_free(void* ptr) 
{ 
    void *ptr_bytes = static_cast<void*>(ptr); 
    ptr_bytes -= sizeof(void*); 

    void *original_ptr; 
    std::memcpy(&original_ptr, ptr_bytes, sizeof(void*)); 

    std::free(original_ptr); 
} 

Wtedy nie trzeba zachować wokół oryginalną wartość wskaźnika, aby go uwolnić. Czy to jest w 100% przenośne, nie jestem pewien, ale mam nadzieję, że ktoś mnie poprawi, jeśli nie!

+0

Zobacz moją poprawioną wersję poniżej. Naprawia błąd kompilatora wykonując arytmetykę wskaźnika na void *, a memcpy w aligned_malloc teraz poprawnie kopiuje wartość. – speps

+0

Ta wersja zawsze powoduje marnowanie miejsca, nawet jeśli dostępne są alokatory wyrównane. -1. –

0

Oto moje 2 centy:

temp = new unsigned char*[num]; 
AlignedBuffers = new unsigned char*[num]; 
for (int i = 0; i<num; i++) 
{ 
    temp[i] = new unsigned char[bufferSize +15]; 
    AlignedBuffers[i] = reinterpret_cast<unsigned char*>((reinterpret_cast<size_t> 
         (temp[i% num]) + 15) & ~15);// 16 bit alignment in preperation for SSE 
} 
2

Tutaj jest stałą próbki user2093113, w bezpośrednim kod nie budować dla mnie (void * nieznany rozmiar). Umieszczam go również w szablonie klasy przesłaniającej operator new/delete, więc nie musisz robić nowej alokacji i umieszczania połączeń.

#include <memory> 

template<std::size_t Alignment> 
class Aligned 
{ 
public: 
    void* operator new(std::size_t size) 
    { 
     std::size_t space = size + (Alignment - 1); 
     void *ptr = malloc(space + sizeof(void*)); 
     void *original_ptr = ptr; 

     char *ptr_bytes = static_cast<char*>(ptr); 
     ptr_bytes += sizeof(void*); 
     ptr = static_cast<void*>(ptr_bytes); 

     ptr = std::align(Alignment, size, ptr, space); 

     ptr_bytes = static_cast<char*>(ptr); 
     ptr_bytes -= sizeof(void*); 
     std::memcpy(ptr_bytes, &original_ptr, sizeof(void*)); 

     return ptr; 
    } 

    void operator delete(void* ptr) 
    { 
     char *ptr_bytes = static_cast<char*>(ptr); 
     ptr_bytes -= sizeof(void*); 

     void *original_ptr; 
     std::memcpy(&original_ptr, ptr_bytes, sizeof(void*)); 

     std::free(original_ptr); 
    } 
}; 

Używaj go tak:

class Camera : public Aligned<16> 
{ 
}; 

Nie przetestować cross-platform-ności tego kodu jeszcze.

+1

Możesz uprościć 'delete' używając tylko jednej instrukcji:' if (ptr) std :: free (static_cast (ptr) [- 1]); ' – bit2shift

+0

a' static_assert (Alignment> sizeof (void *)) ' bądź dobrym pomysłem, lub użyj 'min', aby w razie potrzeby zwiększyć" przestrzeń ". Powinieneś zdecydowanie użyć '# ifdef', aby użyć wyrównanych alokatorów, jeśli to możliwe, zamiast marnować przestrzeń. (na przykład C11's aligned_alloc, który zapewnia wiele kompilatorów C++). Zobacz także http://stackoverflow.com/questions/32612190/how-to-solve-the-32-byte-alignment-issue-for-avx-load-store-operations –

Powiązane problemy