我正在编写一些优化的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这样的东西会自动为我处理吗?我正在优化的代码需要数天才能运行,因此即使是适度的改进也会产生影响.
显而易见的答案当然是首先以最易读/简单/可理解的方式编写它,然后使用尽可能多的优化进行编译,然后对其进行基准测试和分析.
在你知道它们是否是瓶颈之前,优化事物是没有意义的.如果编译器自动进行转换,那么你只是让代码变得更糟,花费时间,并且绝对没有任何回报.除了冷静的感觉,但随着时间的推移逐渐消失.:)
首先将其写为可读性.就个人而言,我发现所有下载都会伤害我的眼睛,所以我可能会更喜欢写下:
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会从代码格式的块中删除空行?