ETLCPP / etl

Embedded Template Library
https://www.etlcpp.com
MIT License
2.04k stars 371 forks source link

[BUG] list_ext does not respect the alignment of generic_pool #886

Closed jay-sridharan closed 1 month ago

jay-sridharan commented 2 months ago

I am building an application with ETL and Eigen. The actual application is of course more complex, but this representative example shows that using list_ext and generic_pool together causes memory to be allocated in places that do not conform with the requested memory alignment.


#include <etl/pool.h>
#include <etl/deque.h>
#include <etl/list.h>

#include <Eigen/Core>
#include <Eigen/Geometry>

struct point_t
{
    double x = 0;
    double y = 0;
    double z = 0;
    double a = 0;
};
class LinearSegment
{
    public:
        Eigen::Vector3d start_position;
        Eigen::Quaterniond start_orientation;
        Eigen::Quaterniond end_orientation;
};

typedef etl::list_ext<LinearSegment> SegmentList;
typedef etl::generic_pool<sizeof(SegmentList::pool_type), 32, 100> SegmentPool;
class Plan
{
    public:

        Plan(SegmentPool& pool_) : segments(pool_) {
            segments.emplace_back();
        };

    private:
        SegmentList segments;
};

class OOMotion
{
    public:      

        void run()
        {
            plans.emplace_back(line_pool);
        };

        double path_length = 1.0;   
        etl::deque<point_t, 50> points;
        SegmentPool line_pool;
        etl::deque<Plan, 50> plans;
};

int main(int argc, char* argv[])
{
    OOMotion motion;
    motion.run();
}

Running this code, I get the following output:

p_data_node: 0x0x7ffe79d25378 | p_data_node->value: 0x0x7ffe79d25388

motion: /usr/include/eigen3/Eigen/src/Core/DenseStorage.h:109: Eigen::internal::plain_array<T, Size, MatrixOrArrayOptions, 16>::plain_array() [with T = double; int Size = 4; int MatrixOrArrayOptions = 0]:
Assertion `(internal::UIntPtr(eigen_unaligned_array_assert_workaround_gcc47(array)) & (15)) == 0 
"this assertion is explained here: " "http://eigen.tuxfamily.org/dox-devel/group__TopicUnalignedArrayAssert.html" "
**** READ THIS WEB PAGE !!! ****"' failed.
Aborted (core dumped)

The first line is printed from this line I added to ilist::emplace_back:

      printf("p_data_node: 0x%p | p_data_node->value: 0x%p", p_data_node, &(p_data_node->value));

As you can see, neither of those pointers are 32 byte aligned.

The reason the byte alignment is important in this case is because of Eigen's optimizations for vector arithmetic:

A Eigen::Vector4d consists of 4 doubles, which is 256 bits. This is exactly the size of an AVX register, which makes it possible to use AVX for all sorts of operations on this vector. But AVX instructions (at least the ones that Eigen uses, which are the fast ones) require 256-bit alignment. Otherwise you get a segmentation fault. For this reason, Eigen takes care by itself to require 256-bit alignment for Eigen::Vector4d, by doing two things:\

  • Eigen requires 256-bit alignment for the Eigen::Vector4d's array (of 4 doubles). With [c++11] this is done with the [alignas] keyword, or compiler's extensions for c++98/03.
  • Eigen overloads the operator new of Eigen::Vector4d so it will always return 256-bit aligned pointers. (removed in [c++17])

I'm a bit unsure on how to debug this issue myself further, but I have a feeling it might have to do with this function:

    data_node_t* create_data_node()
    {
      data_node_t* (etl::ipool::*func)() = &etl::ipool::allocate<data_node_t>;
      return (p_node_pool->*func)();
    }

I'm not sure if the subclass routine is called or the parent class routine is called, and whether this affects whether alignment is respected or not.

Thanks for the help and the useful library!

jay-sridharan commented 2 months ago

I am using ETL version 20.35.14, but it looks like this may have been fixed in #767

jwellbelove commented 2 months ago

Yes, the later version may have fixed this, though I may relook at the defaults for the older C++03 code.