【问题标题】:How can I enforce 16-byte alignment for certain members of a class?如何为类的某些成员强制执行 16 字节对齐?
【发布时间】:2017-04-06 20:43:50
【问题描述】:

假设我有类似的东西:

class C
{
private:
    __m128i m0;
    __m128i m1;
    __m128i cm0;

    int s0;

...

}

这可能会导致问题,因为不能保证 m0、m1 和 cm0 是 16 字节对齐的,允许与对齐相关的访问冲突。如果我绝对想拥有可以保证对齐的私有 simd 类成员,我应该怎么做?

编辑:特别是在我的情况下,我正在使用具有 __m128i 成员(除了 32 位和 64 位整数之外)的类的 std::vector。是否可以强制我的类成员在 16 字节内对齐一个 std::vector?

【问题讨论】:

  • 保证类成员在本地或静态变量以及本地或静态数组中满足其对齐要求。如果您动态分配内存,您可能会遇到问题。我猜您正在尝试使用std::vector<C> 或类似名称?如果有,请说出来。
  • 你是对的,我正在尝试使用像这样的类的向量。有什么建议吗?
  • 如果不将整个类对齐到至少相同的值,您就无法“对齐成员”。否则,您可以通过类允许的对齐方式替换整个对象并破坏成员对齐方式。 (类布局当然是固定的,不是每个对象决定的。)

标签: c++ sse simd memory-alignment class-members


【解决方案1】:

这是我发现的一种解决方法,可以解决我自己的问题。

我做了两件事:

最初,我一直在使用类似的东西:

std::vector<C> myVector(mySize);

我选择这样做:

C* myVector = _aligned_malloc(sizeof(C) * mySize, 16);

其次,我在 C 类中添加了一些可变填充,使其以字节为单位的大小将是 16 的倍数。我不热衷于浪费的空间,所以我将来可能会重新设计(可能是一个块) simd 变量的内存和其他变量的单独块)。

【讨论】:

  • 让编译器为你填充它。您可以通过在代码中添加 int size = sizeof(C) 并查看 asm 输出(例如 like this on Godbolt)来看到这一点。 (请注意,Godbolt 的目标是 System V ABI,而不是 Windows ABI,它们可能并不总是在结构布局上达成一致,但它们总是会填充到所需对齐的倍数,以便数组的所有元素都具有所需的对齐。)
  • 但是是的,如果您不需要std::vector 的功能,避免它比传递一个分配对齐存储的自定义分配器更容易。 IDK 为什么std::vector 默认不使用满足对齐要求的分配器。 :(
【解决方案2】:

如果您不介意浪费一点 RAM,您可以创建一个对齐的_holder 包装类,以保证它所包含的对象将与您指定的任何对齐边界对齐。 (注意浪费的RAM量等于指定的对齐边界)

#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <new>

// Makes sure that its held object is always aligned to the specified alignment-value,
// at the cost of using up some extra RAM.
template<class T, int AlignTo=16> class aligned_holder
{
public:
   aligned_holder()
   {
      new (getAlignedPointer()) T();
   }

   ~aligned_holder()
   {
      getAlignedObject().~T();
   }

   T & getAlignedObject() {return *reinterpret_cast<T*>(getAlignedPointer());}
   const T & getAlignedObjectConst() const {return *reinterpret_cast<const T*>(getAlignedPointerConst());}

private:
   char * getAlignedPointer()
   {
      const ptrdiff_t offset = intptr_t(_buf)%AlignTo;
      return &_buf[(intptr_t)(AlignTo-offset)];
   }

   const char * getAlignedPointerConst() const
   {
      const ptrdiff_t offset = intptr_t(_buf)%AlignTo;
      return &_buf[(intptr_t)(AlignTo-offset)];
   }

   char _buf[AlignTo+sizeof(T)];
};

...和一个单元测试,以(希望)验证我没有搞砸示例:

class IWannaBeAligned
{
public:
   IWannaBeAligned()
   {
      const intptr_t iThis = (intptr_t)(this);
      if ((iThis % 16) != 0) printf("Error!  object %p is not 16-byte aligned!  (offset=%ld)\n", this, iThis%16);
                        else printf("Good, object %p is 16-byte aligned.\n", this);
   }

   ~IWannaBeAligned()
   {
      printf("Deleting object %p\n", this);
   }

   char buf[32];  // just to give it a more realistic object size than one byte
};

int main()
{
   {
      printf("First, let's try it without the aligned_holder:\n");
      IWannaBeAligned notAligned;
   }

   printf("\n");

   {
      printf("Now, we'll try it with the aligned_holder:\n");
      aligned_holder<IWannaBeAligned> isAligned;
   }

   return 0;
}

【讨论】:

    【解决方案3】:

    在 C++11 中,您可以使用 alignas。正如该页面上的示例所示,您的代码可以这样编写:

    class alignas(16) C
    {
    private:
        __m128i m0;
        __m128i m1;
        __m128i cm0;
    
        int s0;
    
    ...
    
    }
    

    【讨论】:

    • 那是多余的,__m128i 已经是 16 字节对齐的类型了。 OP 的实际问题是尝试将类与std::vector 一起使用,即不尊重alignof(C) 的分配器。任何尊重alignof(C) 的东西都可以在没有额外的alignas 的情况下工作。
    猜你喜欢
    • 2011-10-14
    • 2022-12-17
    • 2016-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多