Alignment of array type data member of a heap allocated object

64 views Asked by At

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?

0

There are 0 answers