我需要在 C++ 中对齐吗?
Do I need alignment in C++?
我确定我知道。由于对齐,矩阵库 Eigen3 可能比我自己的任何矩阵实现都快得多。
我最近开始研究使用 C++ 进行对齐。我偶然发现了 alignas 和 alignof 函数,并决定做一些测试。
我的第一个发现 是结构的成员会自动对齐。让我们以下面的结构为例:
struct MyStruct
{
char m_c; // 1 byte
double m_x; // 8 bytes
};
并与这个比较:
struct MyAlignedStruct
{
char m_c; // 1 byte
alignas(8) double m_x; // 8 bytes
};
这里我使用了 alignas 而不是添加一个 padding 成员 (char[7]),根据我的理解,这是等价的。
现在,两个结构的内存查看器显示如下:
62 00 00 00 8e a8 79 35 00 00 00 00 00 00 10 40 // MyStruct
62 ff ff ff 24 00 00 00 00 00 00 00 00 00 10 40 // MyAlignedStruct
第一个字节对应一个字符('b')。使用 Mystruct 时,接下来的 7 个字节用 something 填充,最后 8 个字节表示 double。使用 MyAlignedStruct 时,会发生非常相似的情况。 sizeof() 函数 returns 两个结构都是 16 个字节(我预计 MyStruct 是 9 个字节)。
所以我的第一个问题来了:如果编译器自行对齐,为什么我需要 alignas?
我的第二个发现 是 alignas(..) 没有加速我的程序。我的实验如下。想象一下下面的简单结构:
struct Point
{
double m_x, m_y, m_z;
};
如果我用该结构的实例填充一个向量,并假设第一个实例是 32 字节对齐的,则每个结构将占用 24 个字节,并且字节序列将不再是 32 字节对齐的。老实说,我不确定如何通过对齐来提高速度,否则,我很可能不会在这里写。不过,我使用 alignas 获得了以下结构:
alignas(32) struct Point
{
double m_x, m_y, m_z;
};
现在,Point 的连续实例将从 32 字节的倍数开始。我测试了两个版本:在用结构实例填充一个巨大的向量后,我将所有双精度值相加并记录时间。我发现 32 字节对齐的结构与另一个结构之间没有区别。
所以我的第二个问题与第一个问题相同:为什么我需要 alignas?
Why do I need alignas if the compiler aligns on its own?
我想到的几个原因:
编译器可能配置为打包结构,如下所述:
Force C++ structure to pack tightly
但您希望某些特定结构具有对齐的成员
您想要超出类型要求的对齐方式,例如
alignas(1024) struct MyStruct
{
char m_c; // 1 byte
alignas(32) double m_x; // 8 bytes
};
这可能是由于硬件限制,例如你有一张卡片,可以以 1024 页的分辨率从内存中抓取东西;然后,在那张卡上,数据访问以 32 字节为单位进行。该示例将满足这些要求,而不必插入虚拟字段。
类型punning/slicing:您可能会得到一个数组的地址:
struct s1
{
char m_c;
uint32_t m_d;
char m_e;
};
但我实际上使用的是
struct s2
{
char m_c;
mystery_type_with_size_64_bits m_d;
char m_e;
};
所以即使您想将 m_d
作为 uint32_t
使用,但您还需要正确使用 m_e
。
我确定我知道。由于对齐,矩阵库 Eigen3 可能比我自己的任何矩阵实现都快得多。
我最近开始研究使用 C++ 进行对齐。我偶然发现了 alignas 和 alignof 函数,并决定做一些测试。
我的第一个发现 是结构的成员会自动对齐。让我们以下面的结构为例:
struct MyStruct
{
char m_c; // 1 byte
double m_x; // 8 bytes
};
并与这个比较:
struct MyAlignedStruct
{
char m_c; // 1 byte
alignas(8) double m_x; // 8 bytes
};
这里我使用了 alignas 而不是添加一个 padding 成员 (char[7]),根据我的理解,这是等价的。
现在,两个结构的内存查看器显示如下:
62 00 00 00 8e a8 79 35 00 00 00 00 00 00 10 40 // MyStruct
62 ff ff ff 24 00 00 00 00 00 00 00 00 00 10 40 // MyAlignedStruct
第一个字节对应一个字符('b')。使用 Mystruct 时,接下来的 7 个字节用 something 填充,最后 8 个字节表示 double。使用 MyAlignedStruct 时,会发生非常相似的情况。 sizeof() 函数 returns 两个结构都是 16 个字节(我预计 MyStruct 是 9 个字节)。
所以我的第一个问题来了:如果编译器自行对齐,为什么我需要 alignas?
我的第二个发现 是 alignas(..) 没有加速我的程序。我的实验如下。想象一下下面的简单结构:
struct Point
{
double m_x, m_y, m_z;
};
如果我用该结构的实例填充一个向量,并假设第一个实例是 32 字节对齐的,则每个结构将占用 24 个字节,并且字节序列将不再是 32 字节对齐的。老实说,我不确定如何通过对齐来提高速度,否则,我很可能不会在这里写。不过,我使用 alignas 获得了以下结构:
alignas(32) struct Point
{
double m_x, m_y, m_z;
};
现在,Point 的连续实例将从 32 字节的倍数开始。我测试了两个版本:在用结构实例填充一个巨大的向量后,我将所有双精度值相加并记录时间。我发现 32 字节对齐的结构与另一个结构之间没有区别。
所以我的第二个问题与第一个问题相同:为什么我需要 alignas?
Why do I need alignas if the compiler aligns on its own?
我想到的几个原因:
编译器可能配置为打包结构,如下所述:
Force C++ structure to pack tightly
但您希望某些特定结构具有对齐的成员
您想要超出类型要求的对齐方式,例如
alignas(1024) struct MyStruct { char m_c; // 1 byte alignas(32) double m_x; // 8 bytes };
这可能是由于硬件限制,例如你有一张卡片,可以以 1024 页的分辨率从内存中抓取东西;然后,在那张卡上,数据访问以 32 字节为单位进行。该示例将满足这些要求,而不必插入虚拟字段。
类型punning/slicing:您可能会得到一个数组的地址:
struct s1 { char m_c; uint32_t m_d; char m_e; };
但我实际上使用的是
struct s2 { char m_c; mystery_type_with_size_64_bits m_d; char m_e; };
所以即使您想将
m_d
作为uint32_t
使用,但您还需要正确使用m_e
。