54

如果我想std::vector用 SSE 处理数据,我需要 16 字节对齐。我怎样才能做到这一点?我需要编写自己的分配器吗?或者默认分配器是否已经与 16 字节边界对齐?

4

8 回答 8

33

C++ 标准要求分配函数 (malloc()operator new()) 为任何标准类型分配适当对齐的内存。由于这些函数不接收对齐要求作为参数,在实践中这意味着所有分配的对齐是相同的,并且是具有最大对齐要求的标准类型的对齐,通常是long double和/或long long(参见boost max_align 联合)。

向量指令,如 SSE 和 AVX,比标准 C++ 分配函数提供的对齐要求更强(16 字节对齐用于 128 位访问,32 字节对齐用于 256 位访问)。posix_memalign()memalign()可用于满足具有更强对齐要求的此类分配。


在 C++17 中,分配函数接受一个额外的类型参数std::align_val_t

您可以像这样使用它:

#include <immintrin.h>
#include <memory>
#include <new>

int main() {
    std::unique_ptr<__m256i[]> arr{new(std::align_val_t{alignof(__m256i)}) __m256i[32]};
}

此外,在 C++17 中,标准分配器已更新为尊重类型的对齐,因此您可以简单地执行以下操作:

#include <immintrin.h>
#include <vector>

int main() {
    std::vector<__m256i> arr2(32);
}

或者(在 C++11 中不涉及和支持堆分配):

#include <immintrin.h>
#include <array>

int main() {
    std::array<__m256i, 32> arr3;
}
于 2011-12-10T12:27:53.280 回答
26

您应该使用带有std::容器的自定义分配器,例如vector. 不记得谁写了下面的,但我用了一段时间,它似乎工作(你可能需要更改_aligned_malloc_mm_malloc,取决于编译器/平台):

#ifndef ALIGNMENT_ALLOCATOR_H
#define ALIGNMENT_ALLOCATOR_H

#include <stdlib.h>
#include <malloc.h>

template <typename T, std::size_t N = 16>
class AlignmentAllocator {
public:
  typedef T value_type;
  typedef std::size_t size_type;
  typedef std::ptrdiff_t difference_type;

  typedef T * pointer;
  typedef const T * const_pointer;

  typedef T & reference;
  typedef const T & const_reference;

  public:
  inline AlignmentAllocator () throw () { }

  template <typename T2>
  inline AlignmentAllocator (const AlignmentAllocator<T2, N> &) throw () { }

  inline ~AlignmentAllocator () throw () { }

  inline pointer adress (reference r) {
    return &r;
  }

  inline const_pointer adress (const_reference r) const {
    return &r;
  }

  inline pointer allocate (size_type n) {
     return (pointer)_aligned_malloc(n*sizeof(value_type), N);
  }

  inline void deallocate (pointer p, size_type) {
    _aligned_free (p);
  }

  inline void construct (pointer p, const value_type & wert) {
     new (p) value_type (wert);
  }

  inline void destroy (pointer p) {
    p->~value_type ();
  }

  inline size_type max_size () const throw () {
    return size_type (-1) / sizeof (value_type);
  }

  template <typename T2>
  struct rebind {
    typedef AlignmentAllocator<T2, N> other;
  };

  bool operator!=(const AlignmentAllocator<T,N>& other) const  {
    return !(*this == other);
  }

  // Returns true if and only if storage allocated from *this
  // can be deallocated from other, and vice versa.
  // Always returns true for stateless allocators.
  bool operator==(const AlignmentAllocator<T,N>& other) const {
    return true;
  }
};

#endif

像这样使用它(如果需要,将 16 更改为另一个对齐方式):

std::vector<T, AlignmentAllocator<T, 16> > bla;

然而,这只确保内存块std::vector使用是 16 字节对齐的。如果sizeof(T)不是 16 的倍数,则某些元素将不会对齐。根据您的数据类型,这可能不是问题。如果Tint(4 字节),则仅加载索引为 4 的倍数的元素。如果是(8 字节),则仅加载double2 的倍数,以此类推。

真正的问题是,如果您使用 classes as T,在这种情况下,您必须在类本身中指定对齐要求(同样,取决于编译器,这可能会有所不同;示例适用于 GCC):

class __attribute__ ((aligned (16))) Foo {
    __attribute__ ((aligned (16))) double u[2];
};

我们快完成了!如果您使用Visual C++(至少 2010 版),您将无法使用std::vector您指定对齐方式的类,因为std::vector::resize.

编译时,如果出现以下错误:

C:\Program Files\Microsoft Visual Studio 10.0\VC\include\vector(870):
error C2719: '_Val': formal parameter with __declspec(align('16')) won't be aligned

您将不得不破解您的stl::vector header文件:

  1. 找到vector头文件 [C:\Program Files\Microsoft Visual Studio 10.0\VC\include\vector]
  2. 找到void resize( _Ty _Val )方法 [VC2010 上的第 870 行]
  3. 将其更改为void resize( const _Ty& _Val ).
于 2011-12-17T14:28:34.990 回答
19

不用像之前建议的那样编写自己的分配器,您可以像这样使用boost::alignment::aligned_allocatorfor std::vector

#include <vector>
#include <boost/align/aligned_allocator.hpp>

template <typename T>
using aligned_vector = std::vector<T, boost::alignment::aligned_allocator<T, 16>>;
于 2017-08-23T07:37:38.283 回答
2

编写自己的分配器。allocate并且deallocate是重要的。这是一个例子:

pointer allocate( size_type size, const void * pBuff = 0 )
{
    char * p;

    int difference;

    if( size > ( INT_MAX - 16 ) )
        return NULL;

    p = (char*)malloc( size + 16 );

    if( !p )
        return NULL;

    difference = ( (-(int)p - 1 ) & 15 ) + 1;

    p += difference;
    p[ -1 ] = (char)difference;

    return (T*)p;
}

void deallocate( pointer p, size_type num )
{
    char * pBuffer = (char*)p;

    free( (void*)(((char*)p) - pBuffer[ -1 ] ) );
}
于 2011-12-10T14:17:14.657 回答
2

简短的回答:

如果sizeof(T)*vector.size() > 16那么是的。
假设您的向量使用普通分配器

警告:只要alignof(std::max_align_t) >= 16这是最大对齐。

长答案:

2017 年 8 月 25 日更新新标准n4659

如果它与大于 16 的任何东西对齐,它也与 16 正确对齐。

6.11 对齐(第 4/5 段)

对齐表示为 std::size_t 类型的值。有效对齐仅包括由基本类型的 alignof 表达式返回的值以及附加的实现定义的值集,这些值可能为空。每个对齐值应为 2 的非负整数幂。

对齐具有从弱到强或更严格的对齐顺序。更严格的对齐具有更大的对齐值。满足对齐要求的地址也满足任何较弱的有效对齐要求。

new 和 new[] 返回对齐的值,以便对象正确对齐它们的大小:

8.3.4 新(第 17 段)

[ 注意:当分配函数返回一个非空值时,它必须是一个指向已为对象保留空间的存储块的指针。假定存储块已适当对齐并具有请求的大小。如果对象是数组,则创建的对象的地址不一定与块的地址相同。——尾注]

请注意,大多数系统都有最大对齐。动态分配的内存不需要对齐到大于此的值。

6.11 对齐(第 2 段)

基本对齐由小于或等于所有上下文中实现支持的最大对齐的对齐表示,它等于 alignof(std::max_align_t) (21.2)。当一个类型用作完整对象的类型和用作子对象的类型时,类型所需的对齐方式可能会有所不同。

因此,只要分配的向量内存大于 16 字节,它就会在 16 字节边界上正确对齐。

于 2011-12-10T18:18:32.203 回答
0

declspec(align(x,y))按照英特尔矢量化教程中的说明使用, http://d3f8ykwhia686p.cloudfront.net/1live/intel/CompilerAutovectorizationGuide.pdf

于 2015-07-13T15:26:49.853 回答
-3

不要假设任何关于 STL 容器的事情。他们的界面/行为是定义的,但不是他们背后的东西。如果您需要原始访问权限,则必须编写自己的实现来遵循您想要的规则。

于 2011-12-10T11:40:32.973 回答
-3

该标准要求newnew[]返回与任何数据类型对齐的数据,其中应包括 SSE。MSVC 是否真的遵循该规则是另一个问题。

于 2011-12-10T11:41:10.343 回答