C++ 优化此函数(在C+;+;)
我有一个cpu消耗代码,其中一些带有循环的函数被多次执行。该循环中的每一次优化都会带来显著的性能提升。问题:您将如何优化此循环(虽然没有太多要优化的内容 >#**公告**见[聊天](https://chat.stackoverflow.com/rooms/5056/discussion-between-sehe-and-jakub-m) >>\u嗨,Jakub,如果我找到一个使用启发式优化的版本,对于均匀分布的随机数据,将导致'int64\u t'的速度提高约3.2倍(使用'float'有效提高10.56倍),你会怎么说_ > 我还没有找到时间更新帖子,但是可以通过聊天找到解释和代码C++ 优化此函数(在C+;+;),c++,performance,optimization,g++,C++,Performance,Optimization,G++,我有一个cpu消耗代码,其中一些带有循环的函数被多次执行。该循环中的每一次优化都会带来显著的性能提升。问题:您将如何优化此循环(虽然没有太多要优化的内容 >#**公告**见[聊天](https://chat.stackoverflow.com/rooms/5056/discussion-between-sehe-and-jakub-m) >>\u嗨,Jakub,如果我找到一个使用启发式优化的版本,对于均匀分布的随机数据,将导致'int64\u t'的速度提高约3.2倍(使用'float'有效提高
>我使用相同的测试台代码(如下)来验证结果是否正确,是否与OP的原始实现完全匹配 **编辑**:讽刺的是。。。该测试台有一个致命的缺陷,导致结果无效:启发式版本实际上跳过了部分输入,但由于没有清除现有的输出,它似乎有正确的输出。。。(仍在编辑…)
好的,我已经发布了一个基于您的代码版本的基准测试,以及我建议使用的
partial\u sum
在此处查找所有代码
特征
对于的默认配置
#define MAGNITUDE 20
#define ITERATIONS 1024
#define VERIFICATION 1
#define VERBOSE 0
#define LIMITED_RANGE 0 // hide difference in output due to absense of overflows
#define USE_FLOATS 0
它将(参见此处的):
- 运行100 x 1024次迭代(即100个不同的随机种子)
- 数据长度为1048576(2^20)
- 随机输入数据在元素数据类型的整个范围内均匀分布(
)int64\t
- 通过生成输出数组的哈希摘要,并将其与OP中的参考实现进行比较,验证输出
#define LIMITED_RANGE 1
限制输入数据,以使溢出不会发生;注意,<代码> PrimalSUMIOUTION/<代码>版本显示等效C++无位算术运算,得到相同的不同结果:
显示基于STL的算法(partial\u sum\u incorrect
)在使用float
而不是int64\u t
(!!!)时运行速度大约快2.5倍
的命名仅与整数溢出有关,整数溢出不适用于浮点数;这可以从散列匹配的事实中看出,因此实际上它是partial\u sum\u float\u correct:)partial\u sum\u incorrect
- 当前的
实现做了双重工作,导致它在浮点模式下的性能很差。见项目符号3。partial\u sum\u correct
std::partial_sum(data.begin(), data.end(), output.begin(),
[](int64_t max, int64_t v) -> int64_t
{
max += v;
if (v > max) max = v;
return max;
});
代码看起来已经相当快了。根据in数组的性质,您可以尝试使用特殊的大小写,例如,如果您碰巧知道在一个特定调用中,所有输入数字都是正数,out[i]将等于累积和,不需要if分支。想到的唯一一件可能有点帮助的事情是在循环中使用指针而不是数组索引,比如
void theloop(int64_t in[], int64_t out[], size_t N)
{
int64_t max = in[0];
out[0] = max;
int64_t *ip = in + 1,*op = out+1;
for(uint32_t i = 1; i < N; i++) {
int64_t v = *ip;
ip++;
max += v;
if (v > max) max = v;
*op = max;
op++
}
}
void theloop(int64\u t in[],int64\u t out[],size\u t N)
{
int64_t max=in[0];
out[0]=最大值;
int64_t*ip=in+1,*op=out+1;
对于(uint32_t i=1;imax)max=v;
*op=最大值;
op++
}
}
这里的思想是,数组中的索引很容易编译为数组的基址,将元素的大小乘以索引,然后将结果相加得到元素地址。保持运行指针可以避免这种情况。我猜一个好的优化编译器已经可以做到这一点了,所以您需要研究当前的汇编程序输出。确保方法不是虚拟的,内联的,\u属性的((始终内联的))和-funroll循环似乎是很好的探索选择
只有通过对它们进行基准测试,我们才能确定它们是否值得在更大的程序中进行优化。首先,您需要查看生成的程序集。否则,您无法知道执行此循环时实际发生了什么 现在:这段代码是否在64位机器上运行?如果不是,这些64位的添加可能会有点伤害 这个循环似乎是使用SIMD指令的一个明显的候选者。SSE2支持许多用于整数算术的SIMD指令,包括一些用于两个64位值的指令 除此之外,请查看编译器是否正确展开循环,如果没有,请自行展开。展开循环的两个迭代,然后重新排序。将所有内存加载放在循环的顶部,以便尽早启动它们 对于
if
行,检查编译器是否正在生成条件移动,而不是分支
最后,查看编译器是否支持类似于restrict
/\uu restrict
关键字的内容。它在C++中不是标准的,但是对于编译器来说,在< <代码> > <代码>输出> /COD>不指向相同的地址,这是非常有用的。
尺码是多少
return max<0 ? v : max + v;
#define USE_FLOATS 0
std::partial_sum(data.begin(), data.end(), output.begin(),
[](int64_t max, int64_t v) -> int64_t
{
max += v;
if (v > max) max = v;
return max;
});
void theloop(int64_t in[], int64_t out[], size_t N)
{
int64_t max = in[0];
out[0] = max;
int64_t *ip = in + 1,*op = out+1;
for(uint32_t i = 1; i < N; i++) {
int64_t v = *ip;
ip++;
max += v;
if (v > max) max = v;
*op = max;
op++
}
}
max &= -max >> 63;
max += t_in0;
out[i+0] = max;
for(uint32_t i = 1; i < N; i++) {
max &= -max >> 63; // assuming >> would do arithmetic shift with sign extension
max += in[i];
out[i] = max;
}
int64_t max = 0, i;
for(i=N-1; i > 0; --i) /* Comparing with 0 is faster */
{
max = in[i] > 0 ? max+in[i] : in[i];
out[i] = max;
--i; /* Will reduce checking of i>=0 by N/2 times */
max = in[i] > 0 ? max+in[i] : in[i]; /* Reduce operations v=in[i], max+=v by N times */
out[i] = max;
}
if(0 == i) /* When N is odd */
{
max = in[i] > 0 ? max+in[i] : in[i];
out[i] = max;
}
int64_t v = in[i];
max += v;
if (v > max) max = v;
out[i] = max;
max = calc(in[i], max);
out[i] = max;
int64_t calc(int64_t const in, int64_t const max) {
int64_t const bumped = max + in;
return in > bumped ? in : bumped;
}
int64_t calc(int64_t const in, int64_t const max) {
return 0 > max ? in : max + in;
}
void copy(int64_t const in[], int64_t out[],
size_t const begin, size_t const end)
{
std::copy(in + begin, in + end, out + begin);
} // copy
void accumulate(int64_t const in[], int64_t out[],
size_t const begin, size_t const end)
{
assert(begin != 0);
int64_t max = out[begin-1];
for (size_t i = begin; i != end; ++i) {
max += in[i];
out[i] = max;
}
} // accumulate
void regular(int64_t const in[], int64_t out[],
size_t const begin, size_t const end)
{
assert(begin != 0);
int64_t max = out[begin - 1];
for (size_t i = begin; i != end; ++i)
{
max = 0 > max ? in[i] : max + in[i];
out[i] = max;
}
}
struct Slice {
enum class Type { Negative, Neutral, Positive };
Type type;
size_t begin;
size_t end;
};
typedef void (*Func)(int64_t const[], int64_t[], size_t, size_t);
Func select(Type t) {
switch(t) {
case Type::Negative: return ©
case Type::Neutral: return ®ular;
case Type::Positive: return &accumulate;
}
}
void theLoop(std::vector<Slice> const& slices, int64_t const in[], int64_t out[]) {
for (Slice const& slice: slices) {
Func const f = select(slice.type);
(*f)(in, out, slice.begin, slice.end);
}
}