在同一个可执行文件中使用C/ c++进行不同的优化(plain、SSE、AVX)
Have different optimizations (plain, SSE, AVX) in the same executable with C/C++
我正在为我的3D计算开发优化,现在我有:
- 使用标准C语言库的"
plain
"版本, - 一个
SSE
优化版本,使用预处理器#define USE_SSE
编译, -
AVX
优化版本,使用预处理器#define USE_AVX
编译
是否有可能在3个版本之间切换,而不必编译不同的可执行文件(例如,具有不同的库文件并动态加载"正确"的一个,不知道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;
}
}
或者,如上所述,您可以使用具有公共接口的共享库,并动态加载正确的库。
当然,如果您将Matrix基类实现为"普通"类,则可以逐步细化并仅实现您实际认为有益的部分,并依赖基类来实现性能不是非常关键的功能。
编辑:你谈论内联,我认为你正在寻找错误的功能级别,如果是这样的话。你想要相当大的函数对相当多的数据做一些事情。否则,所有的努力都将用于将数据准备为正确的格式,然后执行一些计算指令,然后将数据放回内存。
我还会考虑如何存储数据。你是用X、Y、Z、W来存储一个数组的集合,还是把很多X、很多Y、很多Z和很多W存储在单独的数组中(假设我们做的是3D计算)?根据您的计算方式,您可能会发现使用其中一种或另一种方法会给您带来最大的好处。
我做了相当多的SSE和3DNow!几年前的优化,"技巧"通常更多的是关于如何存储数据,这样你就可以轻松地一次获取"一束"正确类型的数据。如果您以错误的方式存储数据,您将浪费大量时间"搅拌数据"(将数据从一种存储方式移动到另一种存储方式)。
一种方法是实现符合同一接口的三个库。使用动态库,您可以交换库文件,可执行文件将使用它找到的任何文件。例如,在Windows上,可以编译三个dll:
- PlainImpl.dll
- SSEImpl.dll
- AVXImpl.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和平面进行计算,并断言结果足够接近以给出信心。写简单的版本,不是为了速度,而是为了正确性;然后使用它的结果来验证你的聪明的优化版本得到正确的答案。
传奇人物John Carmack推荐后一种方法;他称之为"并行实现"。读他关于这方面的文章。
所以我建议你先写普通版本。然后,回头开始使用SSE或AVX加速重写应用程序的某些部分,并确保加速版本给出正确的答案。(有时候,普通版本可能有加速版本没有的bug。有两个版本并比较它们有助于发现任何一个版本中的bug。
- 空基优化子对象的地址
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 返回值优化:显式移动还是隐式
- 人脸跟踪arduino代码的优化
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 纯函数,为什么没有优化
- 为什么大多数 pair 实现默认不使用压缩(空基优化)?
- 如何以优化的方式同时迭代两个间距不相等的数组
- 小字符串优化(调试与发布模式)
- 浮点定向舍入和优化
- Visual Studio 调试优化如何工作?
- 为什么开关的优化方式与 c/c++ 中的链接不同?
- 线性优化目标函数中的绝对值
- GCC 会优化内联访问器吗?
- gcc 如何优化此循环?
- 如何防止 CUDA-GDB 中的<优化输出>值
- 为什么我的程序在 O0 和 O2 的优化级别返回不同的结果
- 这个C++编译器优化(在自身的实例上调用对象自己的构造函数)的名称是什么,它是如何工作的?
- 在同一个可执行文件中使用C/ c++进行不同的优化(plain、SSE、AVX)