我试图使用英特尔内在函数来击败编译器优化代码。有时候我可以做到,有时候我可以做到。
我想问题是,为什么我有时会击败编译器,但有时候不是?我使用英特尔内在函数获得了operator+=
以下0.006秒的时间(使用裸C ++时为0.009),但使用内在函数的operator+
时间为0.07秒,而裸C ++仅为0.03秒。
#include <windows.h>
#include <stdio.h>
#include <intrin.h>
class Timer
{
LARGE_INTEGER startTime ;
double fFreq ;
public:
Timer() {
LARGE_INTEGER freq ;
QueryPerformanceFrequency( &freq ) ;
fFreq = (double)freq.QuadPart ;
reset();
}
void reset() { QueryPerformanceCounter( &startTime ) ; }
double getTime() {
LARGE_INTEGER endTime ;
QueryPerformanceCounter( &endTime ) ;
return ( endTime.QuadPart - startTime.QuadPart ) / fFreq ; // as double
}
} ;
inline float randFloat(){
return (float)rand()/RAND_MAX ;
}
// Use my optimized code,
#define OPTIMIZED_PLUS_EQUALS
#define OPTIMIZED_PLUS
union Vector
{
struct { float x,y,z,w ; } ;
__m128 reg ;
Vector():x(0.f),y(0.f),z(0.f),w(0.f) {}
Vector( float ix, float iy, float iz, float iw ):x(ix),y(iy),z(iz),w(iw) {}
//Vector( __m128 val ):x(val.m128_f32[0]),y(val.m128_f32[1]),z(val.m128_f32[2]),w(val.m128_f32[3]) {}
Vector( __m128 val ):reg( val ) {} // 2x speed, above
inline Vector& operator+=( const Vector& o ) {
#ifdef OPTIMIZED_PLUS_EQUALS
// YES! I beat it! Using this intrinsic is faster than just C++.
reg = _mm_add_ps( reg, o.reg ) ;
#else
x+=o.x, y+=o.y, z+=o.z, w+=o.w ;
#endif
return *this ;
}
inline Vector operator+( const Vector& o )
{
#ifdef OPTIMIZED_PLUS
// This is slower
return Vector( _mm_add_ps( reg, o.reg ) ) ;
#else
return Vector( x+o.x, y+o.y, z+o.z, w+o.w ) ;
#endif
}
static Vector random(){
return Vector( randFloat(), randFloat(), randFloat(), randFloat() ) ;
}
void print() {
printf( "%.2f %.2f %.2f\n", x,y,z,w ) ;
}
} ;
int runs = 8000000 ;
Vector sum ;
// OPTIMIZED_PLUS_EQUALS (intrinsics) runs FASTER 0.006 intrinsics, vs 0.009 (std C++)
void test1(){
for( int i = 0 ; i < runs ; i++ )
sum += Vector(1.f,0.25f,0.5f,0.5f) ;//Vector::random() ;
}
// OPTIMIZED* runs SLOWER (0.03 for reg.C++, vs 0.07 for intrinsics)
void test2(){
float j = 27.f ;
for( int i = 0 ; i < runs ; i++ )
{
sum += Vector( j*i, i, i/j, i ) + Vector( i, 2*i*j, 3*i*j*j, 4*i ) ;
}
}
int main()
{
Timer timer ;
//test1() ;
test2() ;
printf( "Time: %f\n", timer.getTime() ) ;
sum.print() ;
}
为什么我这样做? VS 2012剖析器告诉我我的矢量算术运算可以使用一些调整。
答案 0 :(得分:5)
如Mysticial所述,工会黑客是test2
中最可能的罪魁祸首。它强制数据通过L1缓存,虽然速度很快,但有一些延迟远远大于矢量代码提供的2个周期的增益(见下文)。
但也要考虑CPU可以无序并行地运行多个指令(超标量CPU)。例如,Sandy Bridge有6个执行单元,p0-p5,浮点乘法/除法在p0上运行,浮点加法和整数乘法在p1上运行。而且,除了乘法/加法之外,除法需要3-4倍的周期,并且不是流水线的(即,执行除法时执行单元不能启动另一个指令)。所以在test2
中,当向量代码等待昂贵的除法和一些乘法在单元p0上完成时,标量代码可以在p1上执行额外的2个加法指令,这很可能会消除向量指令的任何优点
test1
不同,常量向量可以存储在xmm
寄存器中,在这种情况下,循环只包含add指令。但是代码并不像预期的那样快3倍。原因是流水线指令:每个添加指令有延迟3个周期,但CPU可以在每个周期彼此独立时启动一个新指令。这是每分量矢量加法的情况。因此,向量代码在每个循环迭代中执行一个添加指令,具有3个循环延迟,并且标量代码执行3个添加指令,仅执行5个循环(1个启动/循环,第3个具有延迟3:2 + 3 = 5)。 / p>
关于CPU架构和优化的非常好的资源是http://www.agner.org/optimize/