我上了这堂课,
Approach 1:
typedef float v4sf __attribute__ (vector_size(16))
class Unit
{
public:
Unit(int num)
{
u = new float[num];
v = new float[num];
}
void update()
{
for(int i =0 ; i < num; i+=4)
{
*(v4sf*)&u[i] = *(v4sf*)&v[i] + *(v4sf*)&t[i];
//many other equations
}
}
float*u,*v,*t; //and many other variables
}
Approach 2:
与方法1相同 . 除了在方法2中, v
, u
和所有其他变量在堆上预分配的大块上分配,使用placement new
.
typedef float v4sf __attribute__ (vector_size(16))
class Unit
{
public:
Unit(int num)
{
buffer = new char[num*sizeof(*u) + sizeof(*v) /*..and so on for other variables..*/]
u = new(buffer) float[num];
v = new(buffer+sizeof(float)*num) float[num];
//And so on for other variables
}
void update()
{
for(int i =0 ; i < num; i+=4)
{
*(v4sf*)&u[i] = *(v4sf*)&v[i] + *(v4sf*)&t[i];
//many other equations
}
}
char* buffer;
float*u,*v,*t; //and many other variables
}
但是,方法2快2倍 . 这是为什么?
大约有12个浮点变量,num是500K . update()被称为 1k
次 . 速度不会影响内存分配 . 我测量这样的速度:
double start = getTime();
for( int i = 0; i < 1000; i++)
{
unit->update();
}
double end = getTime();
cout<<end - start;
这在方法2中快了约2倍 .
编译器选项: gcc -msse4 -o3 -ftree-vectorize.
L1缓存为256K,Ram为8GB,页面大小为4K .
编辑:更正了在方法2中分配变量时的错误 . 所有变量都在不同的部分中正确分配 . 处理器是Intel(R)Core(TM)i7-2600 CPU @ 3.40GHz
编辑:在这里添加了源 - Source . 方法1)给出69.58s,方法2)给出46.74s . 虽然速度不快2倍,但仍然很快 .
4 回答
可能是因为'Approach 2'有一个错误 - 所有变量
u
,v
,t
都位于内存中完全相同的位置(您将相同的地址传递给新的位置) .Edit :现在你没有...;)
没有分析就很难猜测,但它可能与默认分配器有关 . 如果在第一种方法中,您为每个变量单独调用new,则无法保证将为这些变量分配彼此接近的地址 . 另一方面,在第二种方法中,您确保它们彼此尽可能接近 . 这将最大化缓存利用率并限制缓存未命中 .
分解时间并查看构造函数中的哪个部分与
update
中的哪个部分是有用的 .由于
update
没有改变,唯一会影响它的时间的是缓存对数据的影响 . 这超过了2倍的差异 .普通新建实际上是分配建设,而新安置只是建设 .
因此,分配2构造自然比分配构造分配构建更快 .
而且,整数类型的构造是nop,所以在你的情况下它是2个分配对1个分配 .
我假设在方法2中,编译器能够识别出u和v的地址在调用之间不会改变,因此将for循环中的一些指针用于寄存器中的for循环 .