在创建模板函数的每个实例时,是否会正确内联模板函数typedef说明符?

时间:2018-04-14 10:02:47

标签: c++ function templates math inline

同时制作了对多个数据流进行操作的功能,创建放到目标流的输出结果。它已经花了很多时间来优化这个函数的性能(openmp,intrinsics等等)。它表现得很漂亮。 这里涉及很多数学,不用说很长的功能。

现在我想用相同的函数实现每个实例的数学替换代码而不编写这个函数的每个版本。我想要仅使用#defines或内联函数来区分此函数的不同实例(每个版本中都必须内联代码)。

找到模板,但模板只允许类型说明符,并意识到#defines不能在这里使用。剩下的解决方案是内联数学函数,所以简化的想法是创建这样的标题:

'alm_quasimodo.h':

#pragma once

typedef struct ALM_DATA
{
  int l, t, r, b;
  int scan;
  BYTE* data;  
} ALM_DATA;

typedef BYTE (*MATH_FX)(BYTE&, BYTE&);
// etc

inline BYTE math_a1(BYTE& A, BYTE& B){ return ((BYTE)((B > A) ? B:A)); }
inline BYTE math_a2(BYTE& A, BYTE& B){ return ((BYTE)(255 - ((long)((long)(255 - A) * (255 - B)) >> 8))); }
inline BYTE math_a3(BYTE& A, BYTE& B){ return ((BYTE)((B < 128)?(2*(((long)A>>1)+64))*((float)B/255):(255-(2*(255-(((long)A>>1)+64))*(float)(255-B)/255)))); }
// etc

template <typename MATH>
inline int const template_math_av (MATH math, ALM_DATA& a, ALM_DATA& b) 
{ 
  // ultra simplified version of very complex code
  for (int y = a.t; y <= a.b; y++)
  {
    int yoffset = y * a.scan;
    for (int x = a.l; x <= a.r; x++)
    {
      int xoffset = yoffset + x;
      a.data[xoffset] = math(a.data[xoffset], b.data[xoffset]);
    }
  }
  return 0;
}

ALM_API int math_caller(int condition, ALM_DATA& a, ALM_DATA& b);

和math_caller在'alm_quasimodo.cpp'中定义如下:

#include "stdafx.h"
#include "alm_quazimodo.h"

ALM_API int math_caller(int condition, ALM_DATA& a, ALM_DATA& b)
{
  switch(condition)
  {
    case 1: return template_math_av<MATH_FX>(math_a1, a, b);
      break;
    case 2: return template_math_av<MATH_FX>(math_a2, a, b);
      break;
    case 3: return template_math_av<MATH_FX>(math_a3, a, b);
      break;
    // etc
  }
  return -1;
}

这里主要关注的是优化,主要是MATH函数代码的内嵌,而不是破坏原始代码的现有优化。当然没有为特定的数学运算编写每个函数实例;)

这个模板是否正确地内联所有数学函数? 以及如何优化此功能模板的任何建议?

如果没有,感谢您阅读这个冗长的问题。

1 个答案:

答案 0 :(得分:2)

这完全取决于您的编译器,优化级别以及定义math_a1math_a3函数的方式和位置。 通常,如果所讨论的函数在与其余代码相同的编译单元中是内联函数,则编译器可以对此进行优化。 如果您没有这样做,您可能需要考虑函子而不是函数。

Here是我尝试过的一些简单示例。您可以为您的函数执行相同的操作,并检查不同编译器的行为。

对于我的例子,GCC 7.3和clang 6.0非常适合优化输出函数调用(前提是他们看到函数的定义)。然而,有些令人惊讶的是,ICC 18.0.0只能优化仿函数和闭包。即使是内联函数也会给它带来一些麻烦。

只是在这里有一些代码以防链接在将来停止工作。 对于以下代码:

template <typename T, int size, typename Closure>
T accumulate(T (&array)[size], T init, Closure closure) {
    for (int i = 0; i < size; ++i) {
        init = closure(init, array[i]);
    }
    return init;
}

int sum(int x, int y) { return x + y; }
inline int sub_inline(int x, int y) { return x - y; }
struct mul_functor {
    int operator ()(int x, int y) const  { return x * y; }
};
extern int extern_operation(int x, int y);

int accumulate_function(int (&array)[5]) {
    return accumulate(array, 0, sum);
}
int accumulate_inline(int (&array)[5]) {
    return accumulate(array, 0, sub_inline);
}
int accumulate_functor(int (&array)[5]) {
    return accumulate(array, 1, mul_functor());
}
int accumulate_closure(int (&array)[5]) {
    return accumulate(array, 0, [](int x, int y) { return x | y; });
}
int accumulate_exetern(int (&array)[5]) {
    return accumulate(array, 0, extern_operation);
}

GCC 7.3(x86)生成以下程序集:

sum(int, int):
        lea     eax, [rdi+rsi]
        ret
accumulate_function(int (&) [5]):
        mov     eax, DWORD PTR [rdi+4]
        add     eax, DWORD PTR [rdi]
        add     eax, DWORD PTR [rdi+8]
        add     eax, DWORD PTR [rdi+12]
        add     eax, DWORD PTR [rdi+16]
        ret
accumulate_inline(int (&) [5]):
        mov     eax, DWORD PTR [rdi]
        neg     eax
        sub     eax, DWORD PTR [rdi+4]
        sub     eax, DWORD PTR [rdi+8]
        sub     eax, DWORD PTR [rdi+12]
        sub     eax, DWORD PTR [rdi+16]
        ret
accumulate_functor(int (&) [5]):
        mov     eax, DWORD PTR [rdi]
        imul    eax, DWORD PTR [rdi+4]
        imul    eax, DWORD PTR [rdi+8]
        imul    eax, DWORD PTR [rdi+12]
        imul    eax, DWORD PTR [rdi+16]
        ret
accumulate_closure(int (&) [5]):
        mov     eax, DWORD PTR [rdi+4]
        or      eax, DWORD PTR [rdi+8]
        or      eax, DWORD PTR [rdi+12]
        or      eax, DWORD PTR [rdi]
        or      eax, DWORD PTR [rdi+16]
        ret
accumulate_exetern(int (&) [5]):
        push    rbp
        push    rbx
        lea     rbp, [rdi+20]
        mov     rbx, rdi
        xor     eax, eax
        sub     rsp, 8
.L8:
        mov     esi, DWORD PTR [rbx]
        mov     edi, eax
        add     rbx, 4
        call    extern_operation(int, int)
        cmp     rbx, rbp
        jne     .L8
        add     rsp, 8
        pop     rbx
        pop     rbp
        ret