我正在开发优化我的3D计算,我现在有:
- 一个“
plain
”版采用标准的C语言库, - 一个
SSE
优化版本,编译使用预处理器#define USE_SSE
, - 一个
AVX
优化版本,编译使用预处理器#define USE_AVX
是否有可能,而无需编写不同的可执行文件的三个版本之间切换(例如,具有不同的库文件和动态加载“正确”的一个,不知道是不是inline
函数是“正确的”为)? 我还考虑在有这种开关在软件的性能。
我正在开发优化我的3D计算,我现在有:
plain
”版采用标准的C语言库, SSE
优化版本,编译使用预处理器#define USE_SSE
, AVX
优化版本,编译使用预处理器#define USE_AVX
是否有可能,而无需编写不同的可执行文件的三个版本之间切换(例如,具有不同的库文件和动态加载“正确”的一个,不知道是不是inline
函数是“正确的”为)? 我还考虑在有这种开关在软件的性能。
这有几种解决方案。
一种是基于C ++,在那里你会创建多个类 - 通常情况下,你实现一个接口类,并使用工厂函数给你一个正确的类的对象。
如
class Matrix
{
virtual void Multiply(Matrix &result, Matrix& a, Matrix &b) = 0;
...
};
class MatrixPlain : public Matrix
{
void Multiply(Matrix &result, Matrix& a, Matrix &b);
};
void MatrixPlain::Multiply(...)
{
... implementation goes here...
}
class MatrixSSE: public Matrix
{
void Multiply(Matrix &result, Matrix& a, Matrix &b);
}
void MatrixSSE::Multiply(...)
{
... implementation goes here...
}
... same thing for AVX...
Matrix* factory()
{
switch(type_of_math)
{
case PlainMath:
return new MatrixPlain;
case SSEMath:
return new MatrixSSE;
case AVXMath:
return new MatrixAVX;
default:
cerr << "Error, unknown type of math..." << endl;
return NULL;
}
}
或者,正如上面所建议的,可以使用具有通用的接口共享库,并动态加载这是正确的图书馆。
当然,如果实现了矩阵基类的“普通”类,你可以做逐步细化和实施只有你实际找到的部件是有益的,并依靠基类来实现性能不是非常crticial的功能。
编辑:请您谈一下在线,我认为你是在寻找的功能错误的水平,如果是这样的话。 你要相当大的函数,做一些事情上相当多的数据。 否则,你的努力将在数据制作成正确的格式,然后做了几个计算的指令,然后将数据返回到内存中度过。
我还要考虑如何存储你的数据。 你存储多组阵列与X,Y,Z,W,或者是你存储大量X,大量的Y,大量的Z和许多W的单独的阵列[假设我们正在做3D运算] 根据您的计算是如何工作的,你会发现,做一个或其他方式会给你最好的效益。
我已经做SSE和3DNow公平一点! 优化技术几年前,和“绝招”往往更多有关如何存储数据,所以你可以很容易地抢了正确的数据的“捆绑式”一次。 如果你有数据存储走错了路,你会浪费很多时间“混写数据”(从存储到另一个的一种方式移动数据)。
一种方式是实现符合相同的接口三个库。 对于动态库,你可以交换库文件和可执行文件会使用任何发现。 例如在Windows上,你可以编译3个DLL文件:
然后进行对可执行文件的链接Impl.dll
。 现在只是把三个具体的DLL一个到同一目录中.exe
,其重命名为Impl.dll
,并且将使用该版本。 同样的原则也应该主要适用于类UNIX操作系统。
下一步将是编程方式加载库,这可能是最灵活的,但它是操作系统特定的,需要一些更多的工作(如打开库,获取函数指针等)
编辑:不过,当然,你可以只实现了功能三次,并选择一个在运行时,根据一些参数/配置文件的设置等,在其他的答案内衬出来。
当然,这是可能的。
要做到这一点,最好的办法是有做完整的工作职能,并在运行当中进行选择。 这会工作,但不是最优的:
typedef enum
{
calc_type_invalid = 0,
calc_type_plain,
calc_type_sse,
calc_type_avx,
calc_type_max // not a valid value
} calc_type;
void do_my_calculation(float const *input, float *output, size_t len, calc_type ct)
{
float f;
size_t i;
for (i = 0; i < len; ++i)
{
switch (ct)
{
case calc_type_plain:
// plain calculation here
break;
case calc_type_sse:
// SSE calculation here
break;
case calc_type_avx:
// AVX calculation here
break;
default:
fprintf(stderr, "internal error, unexpected calc_type %d", ct);
exit(1);
break
}
}
}
每次通过此循环,对代码正在执行一个switch
语句,这仅仅是开销。 一个真正聪明的编译器可以从理论上为您解决问题,不过最好还是自己解决吧。
相反,写三个独立的功能,一个是平原,一为SSE,一个用于AVX。 然后决定在运行时一个要运行的。
对于加分,在“调试”建设,做计算同时与上交所和平原,并断言,结果是足够接近放弃的信心。 写普通版本,而不是速度,而是正确性; 然后用其结果来验证你的聪明的优化版本得到正确的答案。
传说中的约翰·卡马克建议后一种方法; 他将其称为“并行实现”。 阅读他的文章吧。
所以,我建议你先写普通的版本。 然后,回去使用SSE或AVX加速启动应用程序的重写部分,并确保加速版本给出正确的答案。 (有时,普通版本可能有加速的版本没有的错误。有两个版本,并比较它们有助于使虫子来在任一版本的光。)