如果我要重复引用它们,我应该将C数组值存储在局部变量中吗?

时间:2009-03-19 15:50:01

标签: c arrays optimization

我正在编写一些优化的C代码,它基本上通过一个数组运行,并为每个元素做了一些事情。它的作用取决于元素的当前值,如下所示:

for (i=0; i < a_len; i++) {
    if (a[i] == 0) {
        a[i] = f1(a[i]);
    } else if (a[i] % 2 == 0) {
        a[i] = f2(a[i]);
    } else {
        a[i] = 0;
}

我在动态语言工作多年后回到C,我的做法是尝试编写简单的代码而不是为我可以直接引用的东西创建大量的局部变量,比如[i]以上。 我非常清楚最佳实践是编写可读代码并相信编译器比您更聪明,并且会做出很好的优化。

如果我在汇编程序中编写上面的代码,我会将[i]加载到寄存器中,然后每次只使用该值,因为我知道[]是私有内存,并且不会在引用之间进行更改。但是,即使是智能编译器也可能每次都加载,因为它无法确定内存是否已更改。 (或者我是否必须明确声明“a”volatile以使编译器不进行此优化?)。

所以,我的问题是:我是否应该通过使用像这样的局部变量重写来获得更好的性能:

for (i=0; i < a_len; i++) {
    val = a[i];
    if (val == 0) {
        a[i] = f1(val);
    } else if (val % 2 == 0) {
        a[i] = f2(val);
    } else {
        a[i] = 0;
}

或者像-O3这样的东西会自动为我处理吗?我正在优化的代码需要数天才能运行,因此即使是适度的改进也会产生影响。

9 个答案:

答案 0 :(得分:14)

显而易见的答案当然是首先以最易读/简单/可理解的方式编写它,然后使用尽可能多的优化进行编译,然后对其进行基准测试和分析。

如果它们是瓶颈,那么优化before you even know是没有意义的。如果编译器自动进行转换,那么你只是让代码变得更糟,花费时间,并且绝对没有任何回报。除了冷静的感觉,但随着时间的推移逐渐消失。 :)

答案 1 :(得分:7)

首先将其写为可读性。就个人而言,我发现所有下载都会伤害我的眼睛,所以我可能会更喜欢写它:

for (i=0; i < a_len; i++) {

    int val = a[i];  /* or whatever type */
    int result = 0;  /* default result */

    if (val == 0) {
        result = f1(val);
    } else if (val % 2 == 0) {
        result = f2(val);
    } 

    a[i] = result;
}

我猜测编译器会生成类似的代码并进行优化。但如果一个或另一个稍微(只是非常轻微)更好,我不会感到震惊。而且我敢打赌,如果有的话,就会使用本地人。

此外,通过使用索引更改数组中的步行来使用指针遍历它,您可能会获得非常小的改进。再次,这是编译器和情况依赖。

for (p=&a[0]; p < &a[a_len]; ++p) {

    int val = *p;    /* or whatever type */
    int result = 0;  /* default result */

    if (val == 0) {
        result = f1(val);
    } else if (val % 2 == 0) {
        result = f2(val);
    } 

    *p = result;
}

而且,是的,我知道这些都是微优化,通常甚至不应该担心(请代码的可读性和正确性首先) - 我只是指出一些选项因为可能需要进行微观优化(这些建议必须通过对特定情况的分析来支持)。

至于编译器是否会重复从[i]或类似的东西重新加载,这取决于控制流程以及被访问的对象是全局的还是已经将其地址转换并传递给其他东西。

如果对象是全局的或已经获取其地址并且您调用了一个函数,通常编译器必须假定该对象可能已被该函数修改并且必须重新加载它。当指针用于将信息传递给函数时,会发生类似的问题。使用本地可以帮助缓解此问题,因为编译器可以非常轻松地确定本地未被调用函数修改,除非采用本地地址。编译器也可以通过使用某种全局优化(例如MSVC在链接时执行的操作)来尝试解决此问题。

即使数组a是全局的,你的示例代码可能并没有真正解决这个问题,因为你在调用其中任何一个函数后没有重新读取数组中的值(你只是写信给它。)


我想知道为什么markdown会从代码格式的块中删除空行?

答案 2 :(得分:4)

只要打开-O或更高版本,两个版本都会在GCC中生成完全相同的代码。所以我的建议是做你喜欢的更好的方式(我更喜欢没有局部变量)。

答案 3 :(得分:3)

函数f1f2似乎共享相同的签名。他们的表现有多么不同?你真的需要外面的支票吗?或者,您可以将逻辑嵌入到一个函数中吗?

如果你有if-else梯形而不是只有两个这样的函数,请尝试使用函数指针数组。使用a[ i ]的值来索引该数组并调用正确的函数。

手工优化经常被证明是容易出错的微优化。最好将此任务留给编译器。如果您真的需要优化,请查看大图,考虑算法,设计,图层等。

关于你的问题:是的,如果a[ i ]未被声明为volatile,大多数编制者可能会优化内存读取。

答案 4 :(得分:1)

dirkGentley的回答:

  

是的,大多数编译器都可能   优化内存读取应该[   我]

有时编译器在处理“可能有别名”的指针时不会优化代码。在你的情况下Nick,如果你给“a”作为函数参数,函数(int * a),那么编译器可能会认为指向“a”的指针是别名的,因此不会优化。

如果您将指针的质量设置为“int * restrict a”,那么编译器将知道“a”没有别名并且它将进行优化。

100%知道编译器是否正在优化的唯一方法是check the assembly

答案 5 :(得分:0)

C中的数组本质上是一个指针。

局部变量很便宜。

我发现第一个例子有点简单易读,因为我不会质疑“val”是什么。如果“val”和“a”有更好的名字,我会冒昧地说第二个例子会提高可读性。

答案 6 :(得分:0)

你说“如果我在汇编程序中编写上面的代码......”所以我假设你知道汇编语言。

我的建议:看一下代码关键部分的编译器输出,看看到底发生了什么。

答案 7 :(得分:-1)

  1. 除非您知道必须
  2. ,否则不要进行优化
  3. 您的编译器可能会做正确的事
  4. 我发现后一版本更容易阅读
  5. (边缘)在多线程程序中将后一版本与副作用隔离开来更容易

答案 8 :(得分:-1)

优化提示

我很可能首先看一下将var作为指针而不是本地可能会更好。那么你也不要使用变量的双重存储

int* var;//Int or whatever type a[] is
for (i=0; i < a_len; i++) {
    val = &a[i];
    if (*val == 0) {
        f1(val);//// Set the valur inside f1
    } else if (*val % 2 == 0) {
        f2(val);// Set the valur inside f2
    } else {
        *val = 0;
}

优化代码的提示可能是在您对结果不感兴趣时​​避免使用'%'运算符。这取决于你的编译器,但事实证明这对我来说更快(使用宏来提高可读性):

#define is_divisible(dividend, divisor) ((((dividend)/(divisor)) * (divisor))==(dividend))

使用:

else if (is_divisible(val,2)) {

这更快,至少在我测试过的大多数情况下都是如此。

编辑:如果使用仅使用'%2'的模块计算时利润不是那么大但是如果你曾经用比2更大的calue进行模块化操作并且只对modula返回零感兴趣那么在我使用的所有编译器中,我的宏更快