数组结构和内存访问模式
struct of arrays and memory access patterns
这是对这个原始问题的跟进,添加了一些新信息。如果您有兴趣,请在此处查看第一部分:
我第一次尝试为简单的 class 设置数组结构时似乎存在很多问题。主要是为指针分配过多的内存,以及在上一个问题中从 vec3_b 分配这些指针可能导致内存泄漏。
我考虑如何使用指针重新排列数据 w/o,这需要我首先为我的数据桶的大小设置一些常量变量,这样就没有像指针这样的无限值,但也减少了数量记忆固定的东西。
const size_t batch_size = 100;
struct vec3_c
{
size_t x[batch_size];
size_t y[batch_size];
size_t z[batch_size];
};
struct vec3_c vec3_c(size_t x, size_t y, size_t z, size_t index)
{
struct vec3_c v;
v.x[index] = x;
v.y[index] = y;
v.z[index] = z;
return v;
}
struct vec3_c vc3;
for(int i = 0; i < batch_size; i++)
{
vc3 = vec3_c(i+1, i*i, i*10, i);
//printf("vec3c x:%zu, y:%zu, z:%zu\n",vc3.x[i], vc3.y[i], vc3.z[i]);
printf("vec3c x:%p, y:%p, z:%p\n",(void*)&vc3.x[i], (void*)&vc3.y[i], (void*)&vc3.z[i]);
}
---------------x-----------------|----------------y-----------------|----------------z-----------------|
0| 0x7fff57489f40 : 140734657765184 | 0x7fff5748a260 : 140734657765984 | 0x7fff5748a580 : 140734657766784
1| 0x7fff57489f48 : 140734657765192 | 0x7fff5748a268 : 140734657765992 | 0x7fff5748a588 : 140734657766792
2| 0x7fff57489f50 : 140734657765200 | 0x7fff5748a270 : 140734657766000 | 0x7fff5748a590 : 140734657766800
使用这个更新的代码,我必须有一个固定的桶大小,所以我将它设置为 batch_size of 100 只是为了简单的数字。用一些数据填充 vec3c 并做了类似的测试,这次似乎每个值都以 8 字节块对齐。
例如:
size of vec3 : 24 bytes
size of vec3a : 24 bytes
size of vec3b : 24 bytes
size of vec3c : 2400 bytes
size of size_t : 8 bytes
size of int : 4 bytes
size of 16 int : 64 bytes
vec3c x:0x7fff592d2f40, y:0x7fff592d3260, z:0x7fff592d3580
vec3c x:0x7fff592d2f48, y:0x7fff592d3268, z:0x7fff592d3588
vec3c x:0x7fff592d2f50, y:0x7fff592d3270, z:0x7fff592d3590
vec3c x:0x7fff592d2f58, y:0x7fff592d3278, z:0x7fff592d3598
vec3c x:0x7fff592d2f60, y:0x7fff592d3280, z:0x7fff592d35a0
vec3c x:0x7fff592d2f68, y:0x7fff592d3288, z:0x7fff592d35a8
vec3c x:0x7fff592d2f70, y:0x7fff592d3290, z:0x7fff592d35b0
vec3c x:0x7fff592d2f78, y:0x7fff592d3298, z:0x7fff592d35b8
vec3c x:0x7fff592d2f80, y:0x7fff592d32a0, z:0x7fff592d35c0
vec3c x:0x7fff592d2f88, y:0x7fff592d32a8, z:0x7fff592d35c8
都是8个字节隔开
这应该可以解决内存泄漏和指针所需的额外内存问题。
这是新布局,像 sizeof(vc3[0].x) 这样的东西会 return 8 字节。
回到原来的问题:
我的 struct vec3_c
实现是设置数组结构的正确方法吗?
vec_3c 批量大小为 100 它显示 2400 字节大但每个单独的元素只有 8 个字节并且正确对齐,所以我现在实际上可以在 1 个现代 cpu缓存行?
如果将传递给我的数据转换成结构数组的典型格式,是否会超过处于缓存友好状态并且能够在每个指令调用上操作多个数据点的性能优势?需要注意的是,第 1 点和第 2 点都是正确的。
ex 计算两个向量的点积:
这意味着我可以得到每个指令周期 2 vec3_c 的点积?
编辑
还有一个问题,添加额外的 8 字节数据以使该结构成为 32 字节的倍数并且可能将额外的 8 字节用作临时 space 还是将其留空会更好吗?
编辑
有人向我指出,我的初始初始化函数只是把事情搞得一团糟。我将其更新为这种形式:
struct vec3_c* vec3_c()
{
struct vec3_c *v = (struct vec3_c*)malloc(sizeof(struct vec3_c));
v->index = 0;
return v;
}
struct vec3_c* v3 = vec3_c();
for(size_t i = 0; i < batch_size; i++)
{
v3->x[i] = i + 1;
v3->y[i] = i * i;
v3->z[i] = i * 10;
printf("index:%d\tvec3c x:%zu, y:%zu, z:%zu\n",i,v3->x[i], v3->y[i], v3->z[i]);
printf("index:%zu\tvec3c x:%p, y:%p, z:%p\n",i,(void*)&v3->x[i], (void*)&v3->y[i], (void*)&v3->z[i]);
}
如果您将拥有 lots 个这些 xyz 点并且您希望能够一次对所有 x 执行操作,那么将更有意义所有 x 加在一起:
struct PointBatch {
size_t x[batchsize];
size_t y[batchsize];
size_t z[batchsize];
}
// More efficient for things like
// - find the point with the largest X
// - find the sum of all the points as [xsum, ysum, zsum]
如果您通常对单个数据点的 x、y 和 z 进行操作,那么将每个点放在一起作为一个结构更有意义。
struct Point {
size_t x;
size_t y;
size_t z;
}
struct Point pointBatch[batchsize];
// Better for things like
// - plot all the points on a graph
// - determine which points satisfy the equation: x^2 + y^2 < z^2
N.B.
请注意,在性能 不是 问题的情况下,您可能会发现 Point
/pointBatch
方法使您的代码更易于编写且更具可读性,因为 struct PointBatch
没有方便的方法来引用或绕过单个点。
您的这部分代码至少有一个问题:
struct vec3_c vec3_c(size_t x, size_t y, size_t z, size_t index)
{
struct vec3_c v;
v.x[index] = x;
v.y[index] = y;
v.z[index] = z;
return v;
}
您在堆栈上创建了一个新的 vec3_v
结构并 return 它。这意味着每次调用都将 return 一个新结构,其中除 index
行之外的所有元素都未初始化。
稍后当您执行(100 次)vc3 = vec3_c(i+1, i*i, i*10, i);
时,您复制了 300 个值(结构的大小),其中 297 个未初始化:这真的很低效并且会调用未定义的行为!
这是对这个原始问题的跟进,添加了一些新信息。如果您有兴趣,请在此处查看第一部分:
我第一次尝试为简单的 class 设置数组结构时似乎存在很多问题。主要是为指针分配过多的内存,以及在上一个问题中从 vec3_b 分配这些指针可能导致内存泄漏。
我考虑如何使用指针重新排列数据 w/o,这需要我首先为我的数据桶的大小设置一些常量变量,这样就没有像指针这样的无限值,但也减少了数量记忆固定的东西。
const size_t batch_size = 100;
struct vec3_c
{
size_t x[batch_size];
size_t y[batch_size];
size_t z[batch_size];
};
struct vec3_c vec3_c(size_t x, size_t y, size_t z, size_t index)
{
struct vec3_c v;
v.x[index] = x;
v.y[index] = y;
v.z[index] = z;
return v;
}
struct vec3_c vc3;
for(int i = 0; i < batch_size; i++)
{
vc3 = vec3_c(i+1, i*i, i*10, i);
//printf("vec3c x:%zu, y:%zu, z:%zu\n",vc3.x[i], vc3.y[i], vc3.z[i]);
printf("vec3c x:%p, y:%p, z:%p\n",(void*)&vc3.x[i], (void*)&vc3.y[i], (void*)&vc3.z[i]);
}
---------------x-----------------|----------------y-----------------|----------------z-----------------|
0| 0x7fff57489f40 : 140734657765184 | 0x7fff5748a260 : 140734657765984 | 0x7fff5748a580 : 140734657766784
1| 0x7fff57489f48 : 140734657765192 | 0x7fff5748a268 : 140734657765992 | 0x7fff5748a588 : 140734657766792
2| 0x7fff57489f50 : 140734657765200 | 0x7fff5748a270 : 140734657766000 | 0x7fff5748a590 : 140734657766800
使用这个更新的代码,我必须有一个固定的桶大小,所以我将它设置为 batch_size of 100 只是为了简单的数字。用一些数据填充 vec3c 并做了类似的测试,这次似乎每个值都以 8 字节块对齐。
例如:
size of vec3 : 24 bytes
size of vec3a : 24 bytes
size of vec3b : 24 bytes
size of vec3c : 2400 bytes
size of size_t : 8 bytes
size of int : 4 bytes
size of 16 int : 64 bytes
vec3c x:0x7fff592d2f40, y:0x7fff592d3260, z:0x7fff592d3580
vec3c x:0x7fff592d2f48, y:0x7fff592d3268, z:0x7fff592d3588
vec3c x:0x7fff592d2f50, y:0x7fff592d3270, z:0x7fff592d3590
vec3c x:0x7fff592d2f58, y:0x7fff592d3278, z:0x7fff592d3598
vec3c x:0x7fff592d2f60, y:0x7fff592d3280, z:0x7fff592d35a0
vec3c x:0x7fff592d2f68, y:0x7fff592d3288, z:0x7fff592d35a8
vec3c x:0x7fff592d2f70, y:0x7fff592d3290, z:0x7fff592d35b0
vec3c x:0x7fff592d2f78, y:0x7fff592d3298, z:0x7fff592d35b8
vec3c x:0x7fff592d2f80, y:0x7fff592d32a0, z:0x7fff592d35c0
vec3c x:0x7fff592d2f88, y:0x7fff592d32a8, z:0x7fff592d35c8
都是8个字节隔开
这应该可以解决内存泄漏和指针所需的额外内存问题。
这是新布局,像 sizeof(vc3[0].x) 这样的东西会 return 8 字节。
回到原来的问题:
我的
struct vec3_c
实现是设置数组结构的正确方法吗?vec_3c 批量大小为 100 它显示 2400 字节大但每个单独的元素只有 8 个字节并且正确对齐,所以我现在实际上可以在 1 个现代 cpu缓存行?
如果将传递给我的数据转换成结构数组的典型格式,是否会超过处于缓存友好状态并且能够在每个指令调用上操作多个数据点的性能优势?需要注意的是,第 1 点和第 2 点都是正确的。
ex 计算两个向量的点积: 这意味着我可以得到每个指令周期 2 vec3_c 的点积?
编辑 还有一个问题,添加额外的 8 字节数据以使该结构成为 32 字节的倍数并且可能将额外的 8 字节用作临时 space 还是将其留空会更好吗?
编辑 有人向我指出,我的初始初始化函数只是把事情搞得一团糟。我将其更新为这种形式:
struct vec3_c* vec3_c()
{
struct vec3_c *v = (struct vec3_c*)malloc(sizeof(struct vec3_c));
v->index = 0;
return v;
}
struct vec3_c* v3 = vec3_c();
for(size_t i = 0; i < batch_size; i++)
{
v3->x[i] = i + 1;
v3->y[i] = i * i;
v3->z[i] = i * 10;
printf("index:%d\tvec3c x:%zu, y:%zu, z:%zu\n",i,v3->x[i], v3->y[i], v3->z[i]);
printf("index:%zu\tvec3c x:%p, y:%p, z:%p\n",i,(void*)&v3->x[i], (void*)&v3->y[i], (void*)&v3->z[i]);
}
如果您将拥有 lots 个这些 xyz 点并且您希望能够一次对所有 x 执行操作,那么将更有意义所有 x 加在一起:
struct PointBatch {
size_t x[batchsize];
size_t y[batchsize];
size_t z[batchsize];
}
// More efficient for things like
// - find the point with the largest X
// - find the sum of all the points as [xsum, ysum, zsum]
如果您通常对单个数据点的 x、y 和 z 进行操作,那么将每个点放在一起作为一个结构更有意义。
struct Point {
size_t x;
size_t y;
size_t z;
}
struct Point pointBatch[batchsize];
// Better for things like
// - plot all the points on a graph
// - determine which points satisfy the equation: x^2 + y^2 < z^2
N.B.
请注意,在性能 不是 问题的情况下,您可能会发现 Point
/pointBatch
方法使您的代码更易于编写且更具可读性,因为 struct PointBatch
没有方便的方法来引用或绕过单个点。
您的这部分代码至少有一个问题:
struct vec3_c vec3_c(size_t x, size_t y, size_t z, size_t index)
{
struct vec3_c v;
v.x[index] = x;
v.y[index] = y;
v.z[index] = z;
return v;
}
您在堆栈上创建了一个新的 vec3_v
结构并 return 它。这意味着每次调用都将 return 一个新结构,其中除 index
行之外的所有元素都未初始化。
稍后当您执行(100 次)vc3 = vec3_c(i+1, i*i, i*10, i);
时,您复制了 300 个值(结构的大小),其中 297 个未初始化:这真的很低效并且会调用未定义的行为!