I am trying to simdize particular computations. For example I'll take AVX instruction using __m256 type (alignment 32 bytes :- 8 floats).
I am compiling for c++11 so unfortunately I don't have the benefits of better new expressions from c++17 which takes care of all the alignment worries of over-aligned types in heap allocations. I have a cross-platform allocator and overloaded operator new but even using _mm_malloc would be sufficient for my case.
For my test units, I am observing rather interesting behavior with a particular program.
Declaring a class with virtual destructor always give it the proper alignment on the heap. However, any other type data members doesn't help. Stack-allocation always have the correct alignment. Can anyone provide some reasoning as to why this happens? I predicted that having a virtual Dtor would be same as having an extra pointer data member. Here is the program: (I am allocating an increasingly large array on the heap before my heap allocation in case I keep on using the same memory address)
// -*- C++ -*-
#include <iostream>
#include <array>
#include <cstdint>
#include <immintrin.h>
bool isAligned(void* ptr, std::size_t alignment)
{
std::size_t aligned = reinterpret_cast<std::uintptr_t>(ptr) % alignment;
return (aligned == 0);
}
int main()
{
{
constexpr std::size_t align_check_val{32} ;
class ArrayObj{
public:
virtual ~ArrayObj() {} ;
// void* ptr ;
// char c;
// int i ;
// float f ;
// double d ;
// long double ld ;
__m256 arr[4] ;
} ;
{
bool is_aligned_stack{true} ;
bool is_aligned_heap{true} ;
for(std::size_t idx{0} ; idx < 1000 ; ++idx) {
ArrayObj* tmp = new ArrayObj[idx] ;
ArrayObj tmp_stack ;
if(not isAligned(&(tmp_stack.arr[0]), align_check_val)) {
is_aligned_stack = false ;
}
ArrayObj* tmp_heap = new ArrayObj ;
if(not isAligned(&(tmp_heap->arr[0]), align_check_val)) {
is_aligned_heap = false ;
}
delete tmp_heap ;
delete[] tmp ;
}
std::cout << std::boolalpha ;
std::cout << "ArrayObj stack is aligned? "
<< is_aligned_stack << std::endl;
std::cout << "ArrayObj heap is aligned? "
<< is_aligned_heap << std::endl;
}
{
ArrayObj s ;
std::cout << "ArrayObj stack alignment: "
<< alignof(s) << std::endl;
std::cout << "ArrayObj stack size: "
<< sizeof(s) << std::endl;
}
{
ArrayObj* s = new ArrayObj ;
std::cout << "ArrayObj new alignment: "
<< alignof(*s) << std::endl;
std::cout << "ArrayObj new size: "
<< sizeof(*s) << std::endl;
std::cout << "Address:" << s << std::endl ;
std::cout << "Address to int:" << (std::uintptr_t)s << std::endl ;
std::cout << "Address arr:" << &(s->arr[0]) << std::endl ;
std::cout << "Address arr to int:" << reinterpret_cast<std::uintptr_t>(&(s->arr[0])) << std::endl ;
delete s ;
}
}
return 0;
}
Output with virtual Dtor
ArrayObj stack is aligned? true
ArrayObj heap is aligned? true
ArrayObj stack alignment: 32
ArrayObj stack size: 160
ArrayObj new alignment: 32
ArrayObj new size: 160
Address:0x55e8963ba2e0
Address to int:94457441264352
Address arr:0x55e8963ba300
Address arr to int:94457441264384
Output with Dtor commented out:
ArrayObj stack is aligned? true
ArrayObj heap is aligned? false
ArrayObj stack alignment: 32
ArrayObj stack size: 128
ArrayObj new alignment: 32
ArrayObj new size: 128
Address:0x564ef36802d0
Address to int:94897091117776
Address arr:0x564ef36802d0
Address arr to int:94897091117776
compiled using:
g++ -std=c++11 -mavx2 -fstrict-aliasing -Wall -Wextra -pedantic -Weffc++ alignment_check.cpp
How could I make sure that my __m256 arr[4] member of ArrayObj has the correct alignment all the time on the heap? I believe it will always have the correct alignment on the stack. One solution I can think of is using std::vector with a custom AlignedAllocator and declaring it like:
class ArrayObj{
public:
// virtual ~ArrayObj() {} ;
// void* ptr ;
// char c;
// int i ;
// float f ;
// double d ;
// long double ld ;
std::vector<__m256, AlignedAllocator<__m256>> arr{4} ;
} ;
But the size of arr is a compile-time constant. So I thought using raw arrays or std::array would be beneficial. Or does it not matter?