c++ - 自定义数据大小以进行内存对齐

标签 c++ memory-management types memory-alignment

每个数据类型都有一个基于硬件的特定范围。例如,在32位计算机上,int的范围为-2147483648至2147483647。

C ++编译器“填充”对象内存以适合特定大小。我很确定它是2、4、8、16、32、64等。这也可能取决于计算机。

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


确定程序在哪台计算机上运行
确定填充大小
根据位大小设置自定义数据类型


我以前在Java中使用过位集,但是对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个字节,则很可能会确定“啊,我可以使用对齐的SSE指令来加载此数据,因为它的对齐方式为16字节”(或ARM,PowerPC等中的类似字符)。因此,如果您不满意代码中的对齐方式,则会导致程序出错(崩溃或误读数据,具体取决于体系结构)。

通常,对于编译器针对的任何体系结构,编译器使用和给出的对齐方式都是“正确的”。更改它通常会导致性能下降。当然,并非总是如此,但是在摆弄它之前,您最好确切地知道自己在做什么。并测量前后的性能,并彻底测试是否有任何损坏。

填充通常仅用于下一个“最大类型的最小对齐”,例如如果struct仅包含int和几个char变量,则将其填充为4个字节[根据需要在struct内部和末尾]。对于double,可以确保填充到8个字节,但是三个double通常将占用8 * 3个字节,而无需进一步填充。

另外,确定在编译(而不是运行)上执行(或将执行)的硬件可能更好。在运行时,您的代码将已经生成,并且代码已经加载。您现在还不能真正更改事物的偏移和对齐方式。

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

从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 ++是一种静态类型的语言:某些东西的类型在运行时确定-当然,可以从基类派生的类可以在运行时创建,但是对于任何给定的对象,它总是一个类型,永远永久直到不再分配。 。

最好在编译时做出这样的选择,因为这会使代码更直接地面向编译器,并且与在运行时做出选择相比,将提供更好的优化,因为您随后必须做出运行时决策才能使用一段代码的分支A或分支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个周期,如果我的“薄荷醇算术”正确运行,则约为7.5%。

当然,就像许多不同的事物一样,它实际上取决于确切的情况,对象的使用方式,高速缓存的大小是多少,究竟是什么处理器,在其周围其他地方还有多少其他代码和数据也使用高速缓存。 -空间。唯一可以确定的方法是尝试使用您的代码。

[我多次运行该代码,尽管我并非总是得到相同的结果,但我总是得到相似的比例结果]

关于c++ - 自定义数据大小以进行内存对齐,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33790934/

相关文章:

C++ 检测函数是否被 Hook (32 位机器)

c++ - 内存泄漏在哪里?二维数组类

types - PhantomData 和可变引用给出 "does not live long enough"错误

Mysql内存设置

haskell - 解析 `f = f (<*>) pure`的类型

types - 在 Rust 中重新定义类型并调用它们的函数

c++ - 哪种虚拟机调度方法更有效?

c++ - 在我的函数中重新创建一个微型垃圾收集系统是不是很疯狂?

c++ - 两个可以相互扩展的库

iphone - 关于保留计数需要澄清