,所以我正在尝试优化一些代码。我的功能具有可变大小的循环。但是,为了提高效率,我想用1、2和3尺寸的循环案例制作完全展开的特殊情况。到目前为止,我的方法是将循环大小声明为const参数,然后定义包装器函数,该功能将其称为主函数,将其传递为const值的字面函数。我包括了一个代码剪辑,说明了我想到的东西。
inline void someFunction (const int a)
{
for (int i=0; i<a; i++)
{
// do something with i.
}
}
void specialCase()
{
someFunction (3);
}
void generalCase(int a)
{
someFunction (a);
}
所以我的问题是我期望我的编译器(GCC)在特殊案件的内部展开for循环是合理的。我的意思是显然我可以复制 - 将某些功能的内容粘贴到特殊案例中,然后用3替换为3,但我宁愿仅处理我的代码中某种函数的定义。
为了提高效率,我想用1、2和3尺寸的循环特殊案例制作案例,这些案例完全展开。
您是否测量了这实际上更快?我怀疑它将(或编译器不会自动展开循环)。
到目前为止,我的方法是将循环大小声明为const参数,然后定义包装器函数,将其称为主函数,将其传递为const值的文字。
const
在这里没有任何意义。它不会影响编译器展开循环的能力。这只是意味着a
不能在功能主体内突变,但仍然是一个运行时参数。
如果要确保展开,请强制它。C 17很容易。
template <typename F, std::size_t... Is>
void repeat_unrolled_impl(F&& f, std::index_sequence<Is...>)
{
(f(std::integral_constant<std::size_t, Is>{}), ...);
}
template <std::size_t Iterations, typename F>
void repeat_unrolled(F&& f)
{
repeat_unrolled_impl(std::forward<F>(f),
std::make_index_sequence<Iterations>{});
}
Godbolt上的实时示例
如果您不喜欢模板并且不信任您的编译器,则总有此方法,该方法的启发是由过时的手动展开循环的方法,称为" Duff的设备":
void do_something(int i);
void do_something_n_times(int n)
{
int i = 0;
switch(n)
{
default:
while(n > 3) {
do_something(i++);
--n;
}
case 3: do_something(i++);
case 2: do_something(i++);
case 1: do_something(i++);
}
}
,但我认为值得一提的是,如果您不相信编译器做一些简单的事情,例如为您展开循环,那么可能是时候考虑一个新的编译器了。
请注意,Duff的设备最初是针对编译器编译的编译器的微观化策略发明的,这些编译器不会自动应用循环 - 未汇总优化。
它是由汤姆·达夫(Tom Duff)于1983年发明的。
https://en.wikipedia.org/wiki/duff'S_Device
它与现代编译器的使用值得怀疑。
如果您愿意使用所有流行编译器的力量(非标准)功能,我宁愿去这样。
__attribute__((always_inline))
void bodyOfLoop(int i) {
// put code here
}
void specialCase() {
bodyOfLoop(0);
bodyOfLoop(1);
bodyOfLoop(2);
}
void generalCase(int a) {
for (int i=0; i<a; i++) {
bodyOfLoop(i);
}
}
注意:这是GCC/clang解决方案。将__forceinline
用于MSVC。
这个c 20揭开式螺旋桨怎么样:
#pragma once
#include <utility>
#include <concepts>
#include <iterator>
template<size_t N, typename Fn>
requires (N >= 1) && requires( Fn fn, size_t i ) { { fn( i ) } -> std::same_as<void>; }
inline
void unroll( Fn fn )
{
auto unroll_n = [&]<size_t ... Indices>( std::index_sequence<Indices ...> )
{
(fn( Indices ), ...);
};
unroll_n( std::make_index_sequence<N>() );
}
template<size_t N, typename Fn>
requires (N >= 1) && requires( Fn fn ) { { fn() } -> std::same_as<void>; }
inline
void unroll( Fn fn )
{
auto unroll_n = [&]<size_t ... Indices>( std::index_sequence<Indices ...> )
{
return ((Indices, fn()), ...);
};
unroll_n( std::make_index_sequence<N>() );
}
template<size_t N, typename Fn>
requires (N >= 1) && requires( Fn fn, size_t i ) { { fn( i ) } -> std::convertible_to<bool>; }
inline
bool unroll( Fn fn )
{
auto unroll_n = [&]<size_t ... Indices>( std::index_sequence<Indices ...> ) -> bool
{
return (fn( Indices ) && ...);
};
return unroll_n( std::make_index_sequence<N>() );
}
template<size_t N, typename Fn>
requires (N >= 1) && requires( Fn fn ) { { fn() } -> std::convertible_to<bool>; }
inline
bool unroll( Fn fn )
{
auto unroll_n = [&]<size_t ... Indices>( std::index_sequence<Indices ...> ) -> bool
{
return ((Indices, fn()) && ...);
};
return unroll_n( std::make_index_sequence<N>() );
}
template<std::size_t N, typename RandomIt, typename UnaryFunction>
requires std::random_access_iterator<RandomIt>
&& requires( UnaryFunction fn, typename std::iterator_traits<RandomIt>::value_type elem ) { { fn( elem ) }; }
inline
RandomIt unroll_for_each( RandomIt begin, RandomIt end, UnaryFunction fn )
{
RandomIt &it = begin;
if constexpr( N > 1 )
for( ; it + N <= end; it += N )
unroll<N>( [&]( size_t i ) { fn( it[i] ); } );
for( ; it < end; ++it )
fn( *begin );
return it;
}
但是请注意,在这里展开的因素至关重要。展开并不总是有益的,有时甚至超出了最佳CPU特异性展开因素而不会在不展开的情况下进行。