具有不同的优化(普通纸,SSE,AVX)在相同的可执行与C / C ++(Have differen

2019-07-18 10:06发布

我正在开发优化我的3D计算,我现在有:

  • 一个“ plain ”版采用标准的C语言库,
  • 一个SSE优化版本,编译使用预处理器#define USE_SSE
  • 一个AVX优化版本,编译使用预处理器#define USE_AVX

是否有可能,而无需编写不同的可执行文件的三个版本之间切换(例如,具有不同的库文件和动态加载“正确”的一个,不知道是不是inline函数是“正确的”为)? 我还考虑在有这种开关在软件的性能。

Answer 1:

这有几种解决方案。

一种是基于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公平一点! 优化技术几年前,和“绝招”往往更多有关如何存储数据,所以你可以很容易地抢了正确的数据的“捆绑式”一次。 如果你有数据存储走错了路,你会浪费很多时间“混写数据”(从存储到另一个的一种方式移动数据)。



Answer 2:

一种方式是实现符合相同的接口三个库。 对于动态库,你可以交换库文件和可执行文件会使用任何发现。 例如在Windows上,你可以编译3个DLL文件:

  • PlainImpl.dll
  • SSEImpl.dll
  • AVXImpl.dll

然后进行对可执行文件的链接Impl.dll 。 现在只是把三个具体的DLL一个到同一目录中.exe ,其重命名为Impl.dll ,并且将使用该版本。 同样的原则也应该主要适用于类UNIX操作系统。

下一步将是编程方式加载库,这可能是最灵活的,但它是操作系统特定的,需要一些更多的工作(如打开库,获取函数指针等)

编辑:不过,当然,你可以只实现了功能三次,并选择一个在运行时,根据一些参数/配置文件的设置等,在其他的答案内衬出来。



Answer 3:

当然,这是可能的。

要做到这一点,最好的办法是有做完整的工作职能,并在运行当中进行选择。 这会工作,但不是最优的:

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加速启动应用程序的重写部分,并确保加速版本给出正确的答案。 (有时,普通版本可能有加速的版本没有的错误。有两个版本,并比较它们有助于使虫子来在任一版本的光。)



文章来源: Have different optimizations (plain, SSE, AVX) in the same executable with C/C++