这是数组实现:
typedef float newVector3[3];
namespace vec3{
void add(const newVector3& first, const newVector3& second, newVector3& out_newVector3);
void subtract(const newVector3& first, const newVector3& second, newVector3& out_newVector3);
void dot(const newVector3& first, const newVector3& second, float& out_result);
void cross(const newVector3& first, const newVector3& second, newVector3& out_newVector3);
}
// implementations, nothing fancy...really
void add(const newVector3& first, const newVector3& second, newVector3& out_newVector3)
{
out_newVector3[0] = first[0] + second[0];
out_newVector3[1] = first[1] + second[1];
out_newVector3[2] = first[2] + second[2];
}
void subtract(const newVector3& first, const newVector3& second, newVector3& out_newVector3){
out_newVector3[0] = first[0] - second[0];
out_newVector3[1] = first[1] - second[1];
out_newVector3[2] = first[2] - second[2];
}
void dot(const newVector3& first, const newVector3& second, float& out_result){
out_result = first[0]*second[0] + first[1]*second[1] + first[2]*second[2];
}
void cross(const newVector3& first, const newVector3& second, newVector3& out_newVector3){
out_newVector3[0] = first[0] * second[0];
out_newVector3[1] = first[1] * second[1];
out_newVector3[2] = first[2] * second[2];
}
}
一个类实现:
class Vector3{
private:
float x;
float y;
float z;
public:
// constructors
Vector3(float new_x, float new_y, float new_z){
x = new_x;
y = new_y;
z = new_z;
}
Vector3(const Vector3& other){
if(&other != this){
this->x = other.x;
this->y = other.y;
this->z = other.z;
}
}
}
当然,它包含通常出现在Vector3类中的其他功能。
最后,结构实现:
struct s_vector3{
float x;
float y;
float z;
// constructors
s_vector3(float new_x, float new_y, float new_z){
x = new_x;
y = new_y;
z = new_z;
}
s_vector3(const s_vector3& other){
if(&other != this){
this->x = other.x;
this->y = other.y;
this->z = other.z;
}
}
同样,我省略了一些其他常见的Vector3功能。 现在,我让他们三个创建了9000000个新对象,并且做了9000000次交叉产品(我写了一大块数据数据,在其中一个完成后缓存,以避免缓存帮助它们。)
这是测试代码:
const int K_OPERATION_TIME = 9000000;
const size_t bigger_than_cachesize = 20 * 1024 * 1024;
void cleanCache()
{
// flush the cache
long *p = new long[bigger_than_cachesize];// 20 MB
for(int i = 0; i < bigger_than_cachesize; i++)
{
p[i] = rand();
}
}
int main(){
cleanCache();
// first, the Vector3 struct
std::clock_t start;
double duration;
start = std::clock();
for(int i = 0; i < K_OPERATION_TIME; ++i){
s_vector3 newVector3Struct = s_vector3(i,i,i);
newVector3Struct = s_vector3::cross(newVector3Struct, newVector3Struct);
}
duration = ( std::clock() - start ) / (double) CLOCKS_PER_SEC;
printf("The struct implementation of Vector3 takes %f seconds.\n", duration);
cleanCache();
// second, the Vector3 array implementation
start = std::clock();
for(int i = 0; i < K_OPERATION_TIME; ++i){
newVector3 newVector3Array = {i, i, i};
newVector3 opResult;
vec3::cross(newVector3Array, newVector3Array, opResult);
}
duration = ( std::clock() - start ) / (double) CLOCKS_PER_SEC;
printf("The array implementation of Vector3 takes %f seconds.\n", duration);
cleanCache();
// Third, the Vector3 class implementation
start = std::clock();
for(int i = 0; i < K_OPERATION_TIME; ++i){
Vector3 newVector3Class = Vector3(i,i,i);
newVector3Class = Vector3::cross(newVector3Class, newVector3Class);
}
duration = ( std::clock() - start ) / (double) CLOCKS_PER_SEC;
printf("The class implementation of Vector3 takes %f seconds.\n", duration);
return 0;
}
结果令人惊讶。
struct
和class
实现完成任务大约0.23秒,
而array
实现只需要0.08秒!
如果数组确实具有这样的显着性能优势,虽然它的语法很难看,但在很多情况下都值得使用。
所以我真的想确定,这应该发生什么?谢谢!
答案 0 :(得分:7)
简短回答:这取决于。如您所见,如果没有优化编译,则存在差异。
当我在(-O2
或-O3
)上进行优化编译(所有函数内联)时,没有区别(请继续阅读,看起来并不那么容易)。
Optimization Times (struct vs. array)
-O0 0.27 vs. 0.12
-O1 0.14 vs. 0.04
-O2 0.00 vs. 0.00
-O3 0.00 vs. 0.00
无法保证您的编译器可以/将要做什么优化,因此完整的答案是“它取决于您的编译器”。起初我会相信我的编译器会做正确的事情,否则我应该开始编程程序集。只有当代码的这一部分是真正的瓶颈时,才有必要考虑帮助编译器。
如果使用-O2
进行编译,则两个版本的代码都会花费0.0
秒,但这是因为优化器看到这些值根本没用,所以它只会丢掉整个代码!
让我们确保,这不会发生:
#include <ctime>
#include <cstdio>
const int K_OPERATION_TIME = 1000000000;
int main(){
std::clock_t start;
double duration;
start = std::clock();
double checksum=0.0;
for(int i = 0; i < K_OPERATION_TIME; ++i){
s_vector3 newVector3Struct = s_vector3(i,i,i);
newVector3Struct = s_vector3::cross(newVector3Struct, newVector3Struct);
checksum+=newVector3Struct.x +newVector3Struct.y+newVector3Struct.z; // actually using the result of cross-product!
}
duration = ( std::clock() - start ) / (double) CLOCKS_PER_SEC;
printf("The struct implementation of Vector3 takes %f seconds.\n", duration);
// second, the Vector3 array implementation
start = std::clock();
for(int i = 0; i < K_OPERATION_TIME; ++i){
newVector3 newVector3Array = {i, i, i};
newVector3 opResult;
vec3::cross(newVector3Array, newVector3Array, opResult);
checksum+=opResult[0] +opResult[1]+opResult[2]; // actually using the result of cross-product!
}
duration = ( std::clock() - start ) / (double) CLOCKS_PER_SEC;
printf("The array implementation of Vector3 takes %f seconds.\n", duration);
printf("Checksum: %f\n", checksum);
}
您将看到以下更改:
1e9
次迭代,以获得有意义的时间。通过此更改,我们可以看到以下时间(英特尔编译器):
Optimization Times (struct vs. array)
-O0 33.2 vs. 17.1
-O1 19.1 vs. 7.8
-Os 19.2 vs. 7.9
-O2 0.7 vs. 0.7
-O3 0.7 vs. 0.7
我有点失望,-Os
性能如此差,但你可以看到,如果优化,结构和数组之间没有区别!
就个人而言,我非常喜欢-Os
,因为它产生了我能够理解的装配,所以让我们来看一看,为什么它如此慢。
最明显的事情,没有查看生成的程序集:s_vector3::cross
返回一个s_vector3
- 对象,但是我们将结果分配给一个已经存在的对象,所以如果优化器没有看到,那么不再使用旧物体,他可能无法做RVO。所以让我们替换
newVector3Struct = s_vector3::cross(newVector3Struct, newVector3Struct);
checksum+=newVector3Struct.x +newVector3Struct.y+newVector3Struct.z;
with:
s_vector3 r = s_vector3::cross(newVector3Struct, newVector3Struct);
checksum+=r.x +r.y+r.z;
现在有结果:2.14 (struct) vs. 7.9
- 这是一个很大的进步!
我对它的看法:优化器做得很好,但如果需要,我们可以帮助它。
答案 1 :(得分:3)
在这种情况下,没有。就CPU而言;类,结构和数组只是内存布局,在这种情况下的布局是相同的。在非发布版本中,如果使用内联方法,则可以编译成实际函数(主要是为了帮助调试器进入方法),以便可以影响很小。
添加并不是测试Vec3类型性能的好方法。点和/或交叉产品通常是更好的测试方式。
如果你真的关心性能,你基本上想要采用数组结构方法(而不是你上面的结构数组)。这往往允许编译器应用自动矢量化。
即。而不是这个:
constexpr int N = 100000;
struct Vec3 {
float x, y, z;
};
inline float dot(Vec3 a, Vec3 b) { return a.x*b.x + a.y*b.y + a.z*b.z; }
void dotLots(float* dps, const Vec3 a[N], const Vec3 b[N])
{
for(int i = 0; i < N; ++i)
dps[i] = dot(a[i], b[i]);
}
你这样做:
constexpr int N = 100000;
struct Vec3SOA {
float x[N], y[N], z[N];
};
void dotLotsSOA(float* dps, const Vec3SOA& a, const Vec3SOA& b)
{
for(int i = 0; i < N; ++i)
{
dps[i] = a.x[i]*b.x[i] + a.y[i]*b.y[i] + a.z[i]*b.z[i];
}
}
如果使用-mavx2和-mfma进行编译,那么后一版本将非常好地进行优化。