使模板化优化更加可维护
Making templatized optimization more maintainable
有时,通过使用不变的模板化内部实现,编译器可以更好地优化一块代码。例如,如果您的图像中有许多频道数量,而不是做类似:
的事情Image::doOperation() {
for (unsigned int i = 0; i < numPixels; i++) {
for (unsigned int j = 0; i j mChannels; j++) {
// ...
}
}
}
您可以做到这一点:
template<unsigned int c> Image::doOperationInternal() {
for (unsigned int i = 0; i < numPixels; i++) {
for (unsigned int j = 0; j < c; j++) {
// ...
}
}
}
Image::doOperation() {
switch (mChannels) {
case 1: doOperation<1>(); break;
case 2: doOperation<2>(); break;
case 3: doOperation<3>(); break;
case 4: doOperation<4>(); break;
}
}
允许编译器为不同的频道计数生成不同的展开循环(反过来可以极大地提高运行时效率,并打开不同的优化(例如SIMD指令))。
但是,这通常可以扩展到一些相当大的案例语句中,并且以这种方式优化的任何方法都必须具有展开的案例陈述。因此,假设我们为已知的图像格式(枚举的值恰好映射到通道计数)的enum Format
。由于枚举只有一定范围的已知值范围,因此有一个诱惑来尝试以下操作:
template<Image::Format f> Image::doOperationInternal() {
for (unsigned int i = 0; i < numPixels; i++) {
for (unsigned int j = 0; j < static_cast<unsigned int>(f); j++) {
// ...
}
}
}
Image::doOperation() {
const Format f = mFormat;
doOperationInternal<f>();
}
但是,在这种情况下,编译器(理所当然)抱怨F是一个恒定的表达式,即使它只有有限范围,从理论上讲,编译器可以生成switch
逻辑以覆盖所有枚举值。
所以,我的问题是:是否有一种替代方法,该方法将允许编译器生成不变 - 值优化的代码,而无需每个功能调用开关案例爆炸?
制作跳台阵列,然后调用。目标是创建各种功能的数组,然后进行数组查找并调用您想要的一个。
首先,我要做C 11。C 1Y包含其自己的积分序列类型,并且易于编写auto
返回类型:C 11一个将返回void
。
我们的函子类看起来像这样:
struct example_functor {
template<unsigned N>
static void action(double d) const {
std::cout << N << ":" << d << "n"; // or whatever, N is a compile time constant
}
};
在C 11中,我们需要一些样板:
template<unsigned...> struct indexes {};
template<unsigned Max, unsigned... Is> struct make_indexes:make_indexes< Max-1, Max-1, Is... > {};
template<unsigned... Is> struct make_indexes<0, Is...>:indexes<Is...> {};
创建和模式匹配索引包。
接口看起来像:
template<typename Functor, unsigned Max, typename... Ts>
void invoke_jump( unsigned index, Ts&&... ts );
,被称为:
invoke_jump<example_functor, 10>( 7, 3.14 );
我们首先创建一个助手:
template<typename Functor, unsigned... Is, typename... Ts>
void do_invoke_jump( unsigned index, indexes<Is...>, Ts&&... ts ) {
static auto table[]={ &(Functor::template action<Is>)... };
table[index]( std::forward<Ts>(ts)... )
}
template<typename Functor, unsigned Max, typename... Ts>
void invoke_jump( unsigned index, Ts&&... ts ) {
do_invoke_jump( index, make_indexes<Max>(), std::forward<Ts>(ts)... );
}
创建static
的Functor::action
表,然后对其进行查找并调用它。
在C 03中,我们没有...
语法,因此我们必须手动做更多的事情,而不是完美的转发。我要做的就是创建一个std::vector
表。
首先,一个可爱的小程序,以i在[begin,end)的顺序上运行 Functor.action<I>()
:
template<unsigned Begin, unsigned End, typename Functor>
struct ForEach:ForEach<Begin, End-1, Functor> {
ForEach(Functor& functor):
ForEach<Begin, End-1, Functor>(functor)
{
functor->template action<End-1>();
}
};
template<unsigned Begin, typename Functor>
struct ForEach<Begin,Begin,Functor> {};
我承认的过于可爱(链是由构造依赖器隐式创建的)。
然后,我们使用它来构建vector
。
template<typename Signature, typename Functor>
struct PopulateVector {
std::vector< Signature* >* target; // change the signature here to whatever you want
PopulateVector(std::vector< Signature* >* t):target(t) {}
template<unsigned I>
void action() {
target->push_back( &(Functor::template action<I>) );
}
};
然后,我们可以将两个挂钩:
template<typename Signature, typename Functor, unsigned Max>
std::vector< Signature* > make_table() {
std::vector< Signature* > retval;
retval.reserve(Max);
PopulateVector<Signature, Functor> worker(&retval);
ForEach<0, Max>( worker ); // runtime work basically done on this line
return retval;
}
将我们的跳桌构建为std::vector
。
我们可以轻松地调用跳台的ITH元素。
struct example_functor {
template<unsigned I>
static void action() {
std::cout << I << "n";
}
};
void test( unsigned i ) {
static std::vector< void(*)() > table = make_table< void(), example_functor, 100 >();
if (i < 100)
table[i]();
}
通过整数i
打印并打印一个新线。
表中功能的签名可以是您想要的,因此您可以将指针传递给类型并调用方法,而I
是编译时常数。action
方法确实必须是static
,但是它可以调用基于非static
的参数方法。
C 03中的很大差异是,您需要不同的代码来构建跳台的不同签名,很多机械(和std::vector
而不是静态数组)才能构建跳台。
进行严重的图像处理时,您需要以这种方式生成扫描线函数,并且每个像素操作可能嵌入其中的某个地方的扫描线函数中。通常每次扫描线一次跳转一次,除非您的图像宽1像素宽,否则数十亿像素。
上述代码仍然需要审核以进行正确性:它是编写而没有编译的。
yakk的C 11/1Y技术很棒,但是如果C 03版本对您来说有点太多模板欺骗复制和粘贴开关语句,仅给您一个开关语句以维护:
#include<iostream>
using namespace std;
struct Foo {
template<unsigned int c>
static void Action() {
std::cout << "c: " << c << endl;
}
};
template<typename F>
void Dispatch(unsigned int c) {
switch (c) {
case 1: F::Action<1>(); break;
case 2: F::Action<2>(); break;
case 3: F::Action<3>(); break;
}
}
int main() {
for (int i = 0; i < 4; ++i)
Dispatch<Foo>(i);
}
只是为了完整,这是我所使用的(临时)解决方案:
#define DISPATCH_TEMPLATE_CALL(func, args) do {
switch (mChannels) {
case 1: func<1> args; break;
case 2: func<2> args; break;
case 3: func<3> args; break;
case 4: func<4> args; break;
default: throw std::range_error("Unhandled format");
}
} while (0)
template<unsigned int n> void Image::doSomethingInternal(a, b, c) {
// ...
}
void Image::doSomething(a, b, c) {
DISPATCH_TEMPLATE_CALL(doSomethingInternal, (a, b, c));
}
这显然不是一种可取的方法。但它有效。
- 空基优化子对象的地址
- 关闭||运算符优化
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 返回值优化:显式移动还是隐式
- 人脸跟踪arduino代码的优化
- 如何维护资源管理器项目视图中当前可见的项目列表
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 维护unordered_map但同时每一步都需要最低的映射值
- 纯函数,为什么没有优化
- 为什么大多数 pair 实现默认不使用压缩(空基优化)?
- 如何以优化的方式同时迭代两个间距不相等的数组
- 小字符串优化(调试与发布模式)
- 浮点定向舍入和优化
- Visual Studio 调试优化如何工作?
- 为什么开关的优化方式与 c/c++ 中的链接不同?
- 线性优化目标函数中的绝对值
- GCC 会优化内联访问器吗?
- gcc 如何优化此循环?
- GCC,CMake,预编译标头和维护依赖项
- 使模板化优化更加可维护