用于内存对齐的自定义数据大小

Custom data size for memory alignment

每种数据类型都有一定的范围,具体取决于硬件。例如,在 32 位机器上,int 的范围是 -2147483648 到 2147483647。

C++ 编译器 'pad' 适合特定大小的对象内存。我很确定它是 2、4、8、16、32、64 等。这也可能取决于机器。

我想手动对齐我的对象以满足填充要求。有没有办法:

我以前在 Java 中使用过 bitsets,但我不熟悉 C++。至于机器要求,我知道不同硬件集的程序通常在 C++ 中编译不同,所以我想知道它是否可能。

示例->

/*getHardwarePack size obviously doesn't exist, just here to explain. What I'm trying to get  
here would be the minimum alignment size for the machine the program is running on*/

#define PACK_SIZE = getHardwarePackSize();
#define MONTHS = 12;

class date{

    private:

           //Pseudo code that represents making a custom type
           customType monthType = MONTH/PACK_SIZE; 

           monthType.remainder  = MONTH % PACK_SIZE;

           monthType months = 12;
};

我们的想法是能够将每个变量放入最小位大小并跟踪剩余的位数。

从理论上讲,可以利用每个未使用的位并提高内存效率。显然这永远不会像这样工作,但这个例子只是为了解释这个概念。

这比您要描述的要复杂得多,因为需要对齐对象和对象中的项目。例如,如果编译器决定一个整数项是 structclass 中的 16 个字节,它很可能会决定 "ah, I can use an aligned SSE instruction to load this data, because it is aligned at 16 bytes"(或 ARM、PowerPC 等中的类似内容)。因此,如果您至少不满足代码中的对齐方式,则会导致程序出错(崩溃或误读数据,具体取决于体系结构)。

通常,对于编译器所针对的任何体系结构,编译器使用和给出的对齐方式都是 "right"。更改它通常会导致更差的性能。当然,并非总是如此,但在使用它之前,您最好确切地知道自己在做什么。fiddle。并测量性能 before/after,并彻底测试没有任何问题。

填充通常只是到下一个 "minimum alignment for the largest type" - 例如如果 struct 仅包含 int 和几个 char 变量,它将被填充到 4 个字节 [在结构内部和末尾,根据需要]。对于 double,填充到 8 个字节是为了确保,但是三个 double 通常会占用 8 * 3 个字节,没有进一步的填充。

此外,确定您正在(或将要在其上执行)的硬件可能在编译期间比在运行时更好地完成。在运行时,您的代码已经生成,并且代码已经加载。此时您无法真正更改事物的偏移量和对齐方式。

如果您使用的是 gcc 或 clang 编译器,则可以使用 __attribute__((aligned(n))),例如int x[4] __attribute__((aligned(32))); 将创建一个与 32 字节对齐的 16 字节数组。这可以在结构内部或 类 以及您正在使用的任何变量中完成。但这是一个编译时选项,不能在运行时使用。

从 C++11 开始,也可以找出类型或变量与 alignof 的对齐方式。

请注意,它给出了类型所需的对齐方式,所以如果你做一些愚蠢的事情,比如:

 int x;
 char buf[4 * sizeof(int)];
 int *p = (int *)buf + 7;
 std::cout << alignof(*p) << std::endl;

代码将打印 4,尽管 buf+7 的对齐可能是 3(7 模 4)。

无法在运行时选择类型。 C++ 是一种静态类型语言:某些东西的类型是在运行时确定的——当然,从基类派生的 类 可以在运行时创建,但对于任何给定的对象,它只有一种类型,永远永远直到它不再分配。

最好在编译时做出这样的选择,因为它使编译器的代码更加直接,并且比在运行时做出选择更能优化,因为你必须做出运行时决定使用某段代码的 b运行ch A 或 b运行ch B。

作为对齐访问与未对齐访问的示例:

#include <cstdio>
#include <cstdlib>
#include <vector>

#define LOOP_COUNT 1000

unsigned long long rdtscl(void)
{
    unsigned int lo, hi;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}

struct A
{
    long a;
    long b;
    long d;
    char c;
};

struct B 
{
    long a;
    long b;
    long d;
    char c;
} __attribute__((packed));

std::vector<A> arr1(LOOP_COUNT);
std::vector<B> arr2(LOOP_COUNT);


int main()
{
    for (int i = 0; i < LOOP_COUNT; i++)
    {
    arr1[i].a = arr2[i].a = rand();
    arr1[i].b = arr2[i].b = rand();
    arr1[i].c = arr2[i].c = rand();
    arr1[i].d = arr2[i].d = rand();
    }

    printf("align A %zd, size %zd\n", alignof(A), sizeof(A));
    printf("align B %zd, size %zd\n", alignof(B), sizeof(B));
    for(int loops = 0; loops < 10; loops++)
    {
    printf("Run %d\n", loops);
    size_t sum = 0;
    size_t sum2 = 0;
    unsigned long long before = rdtscl();
    for (int i = 0; i < LOOP_COUNT; i++)
        sum += arr1[i].a + arr1[i].b + arr1[i].c + arr1[i].d;
    unsigned long long after = rdtscl();
    printf("ARR1 %lld sum=%zd\n",(after - before),  sum);

    before = rdtscl();
    for (int i = 0; i < LOOP_COUNT; i++)
        sum2 += arr2[i].a + arr2[i].b + arr2[i].c + arr2[i].d;
    after = rdtscl();
    printf("ARR2 %lld sum=%zd\n",(after - before),  sum2);
    }
}

[部分代码取自另一个项目,因此它可能不是有史以来最简洁的 C++ 代码,但它让我免于从头开始编写与项目无关的代码]

那么结果:

$ ./a.out
align A 8, size 32
align B 1, size 25
Run 0
ARR1 5091 sum=3218410893518
ARR2 5051 sum=3218410893518
Run 1
ARR1 3922 sum=3218410893518
ARR2 4258 sum=3218410893518
Run 2
ARR1 3898 sum=3218410893518
ARR2 4241 sum=3218410893518
Run 3
ARR1 3876 sum=3218410893518
ARR2 4184 sum=3218410893518
Run 4
ARR1 3875 sum=3218410893518
ARR2 4191 sum=3218410893518
Run 5
ARR1 3876 sum=3218410893518
ARR2 4186 sum=3218410893518
Run 6
ARR1 3875 sum=3218410893518
ARR2 4189 sum=3218410893518
Run 7
ARR1 3925 sum=3218410893518
ARR2 4229 sum=3218410893518
Run 8
ARR1 3884 sum=3218410893518
ARR2 4210 sum=3218410893518
Run 9
ARR1 3876 sum=3218410893518
ARR2 4186 sum=3218410893518

如您所见,使用 arr1 对齐的代码需要大约 3900 个时钟周期,而使用 arr2 的代码需要大约 4200 个时钟周期。所以大约 4000 个周期中有 300 个周期,如果我的 "menthol arithmetic" 正常工作,大约 7.5%。

当然,就像很多不同的东西一样,这真的取决于具体情况,对象是如何使用的,缓存大小是多少,具体是什么处理器,其他地方有多少其他代码和数据围绕它也使用缓存-space。唯一可以确定的方法是试验您的代码。

[我 运行 代码多次,虽然我并不总是得到相同的结果,但我总是得到相似的比例结果]