我在C中有这样的函数(在伪代码中,丢弃不重要的部分):
int func(int s, int x, int* a, int* r) {
int i;
// do some stuff
for (i=0;i<a_really_big_int;++i) {
if (s) r[i] = x ^ i;
else r[i] = x ^ a[i];
// and maybe a couple other ways of computing r
// that are equally fast individually
}
// do some other stuff
}
这个代码被调用得如此之多,以至于这个循环实际上是代码中的速度瓶颈。我想知道几件事:
由于开关s
是函数中的常量,好的编译器会优化循环,以便分支不会一直减慢速度吗?
如果没有,优化此代码的好方法是什么?
====
以下是更新的更新示例:
int func(int s,
int start,int stop,int stride,
double *x,double *b,
int *a,int *flips,int *signs,int i_max,
double *c)
{
int i,k,st;
for (k=start; k<stop; k += stride) {
b[k] = 0;
for (i=0;i<i_max;++i) {
/* this is the code in question */
if (s) st = k^flips[i];
else st = a[k]^flips[i];
/* done with code in question */
b[k] += x[st] * (__builtin_popcount(st & signs[i])%2 ? -c[i] : c[i]);
}
}
}
编辑2:
如果有人好奇,我最终重构代码并将整个内部for循环(使用i_max
)提升到外部,使really_big_int
循环更加简单,并且希望很容易进行矢量化! (并且还避免多次使用额外的逻辑)
答案 0 :(得分:4)
优化代码的一种显而易见的方法是在循环外拉出条件:
if (s)
for (i=0;i<a_really_big_int;++i) {
r[i] = x ^ i;
}
else
for (i=0;i<a_really_big_int;++i) {
r[i] = x ^ a[i];
}
精明的编译器可能一次可以将其更改为多个元素的r []赋值。
答案 1 :(得分:2)
<强>微优化强>
通常他们不值得花时间 - 审查更大的问题更有效。
然而,微观优化,尝试各种方法,然后分析它们以找到最佳方法可以做出适度的改进。
除了@wallyk和@kabanus精确答案之外,一些简单的编译器还可以使用以0结尾的循环。
// for (i=0;i<a_really_big_int;++i) {
for (i=a_really_big_int; --i; ) {
[编辑第2次优化]
OP增加了一个更具竞争力的例子。其中一个问题是编译器不能假设b
和其他人指向的内存不重叠。这可以防止某些优化。
假设它们实际上不重叠,请在restrict
上使用b
以允许优化。 const
对于那些没有推断出来的弱代编译器也有帮助。如果参考数据不重叠,其他人restrict
也可能会受益。
// int func(int s, int start, int stop, int stride, double *x,
// double *b, int *a, int *flips,
// int *signs, int i_max, double *c) {
int func(int s, int start, int stop, int stride, const double * restrict x,
double * restrict b, const int * restrict a, const int * restrict flips,
const int * restrict signs, int i_max, double *c) {
答案 2 :(得分:1)
所有命令都是循环中的快速O(1)命令。 if
绝对是优化的,如果您的所有命令都是r[i]=somethingquick
形式,那么你的for +也是如此。问题可能归结为你对大小的影响有多大?
从int main
到INT_MIN
的快速INT_MAX
总结为一个长变量,在Windows上的Ubuntu子系统上需要大约10秒钟。您的命令可能会乘以这几个,这很快就会达到一分钟。最重要的是,如果你真的在迭代一吨,这可能是不可避免的。
如果r[i]
是独立计算的,这将是线程/多处理的经典用法。
编辑:
我认为%
无论如何都会被编译器优化,但如果没有,请注意x & 1
对奇数/偶数检查要快得多。
答案 3 :(得分:1)
假设x86_64,您可以确保指针对齐到16个字节并使用intrinsics。如果它仅在具有AVX2的系统上运行,则可以使用__mm256变体(类似于avx512 *)
int func(int s, int x, const __m128i* restrict a, __m128i* restrict r) {
size_t i = 0, max = a_really_big_int / 4;
__m128i xv = _mm_set1_epi32(x);
// do some stuff
if (s) {
__m128i iv = _mm_set_epi32(3,2,1,0); //or is it 0,1,2,3?
__m128i four = _mm_set1_epi32(4);
for ( ;i<max; ++i, iv=_mm_add_epi32(iv,four)) {
r[i] = _mm_xor_si128(xv,iv);
}
}else{ /*not (s)*/
for (;i<max;++i){
r[i] = _mm_xor_si128(xv,a[i]);
}
}
// do some other stuff
}
答案 4 :(得分:0)
尽管if
语句将在任何体面的编译器上进行优化(除非您要求编译器不进行优化),但我会考虑编写优化(以防您在没有优化的情况下进行编译)。
此外,虽然编译器可能会优化&#34;绝对&#34; if
声明,我会考虑使用任何可用的内置或using bitwise操作手动优化它。
即
b[k] += x[st] *
( ((__builtin_popcount(st & signs[I]) & 1) *
((int)0xFFFFFFFFFFFFFFFF)) ^c[I] );
这将取popcount
的最后一位(1 ==奇数,0 ==偶数),乘以const(所有位1如果是奇数,所有位0如果为真)并且比XOR { {1}}值(与c[I]
或0-c[I]
相同。
这将避免在第二个~(c[I])
if语句未被优化的情况下跳转指令。
P.S。
我使用了一个8字节长的值,并通过将其转换为absolute
来截断它的长度。这是因为我不知道你的系统int
可能有多长(我的4个字节,int
)。