在C ++中,将变量的限制存储在变量中比评估值更快吗?
例如:
使用
是一种较慢的方法for(int i=0; i<n*n+2*n; ++i)
{ .... }
而不是做以下事情?
for(int i=0, limit=n*n+2*n; i<limit; ++i)
{ .... }
为清楚起见,假设n
是某个给定变量,在循环过程中保持不变。
答案 0 :(得分:21)
如果n
是全局声明的非volatile
变量,那么
for (int i = 0; i < n * n + 2 * n; ++i)
未指定。即使另一个线程修改n * n + 2 * n
,也允许编译器优化n
以进行一次评估。此外,如果另一个线程能够修改n
,那么您应该采取措施避免同时读取和写入n
(其行为未定义)的可能性。请考虑使用std::atomic<int>
作为n
的类型。
因此,如果您希望在程序控制到达limit
循环时停止条件依赖于n
可观察的值,那么无论如何都要引入for
是合适的,无论性能如何注意事项。因此,请考虑
for (int i = 0, limit = n * n + 2 * n; i < limit; ++i)
的优点是limit
的范围不会泄露到周围的陈述中。
但是如果你能够,你总是可以向后运行循环:
for (int i = n * n + 2 * n - 1; i >= 0; --i)
如果采用unsigned
类型,请非常小心。
答案 1 :(得分:7)
在热点之外并不重要。我的意思是 - 是的,在没有编译器优化的情况下,在调试中只计算一次值会更快,并且至少在发布时速度快。但它通常并不重要。这样做更容易编写,阅读和维护。让我引用唐纳德克努特的名言:
真正的问题是程序员花了太多时间 担心在错误的地方和错误的时间效率; 过早优化是所有邪恶的根源(或至少大部分 它在编程中。
话虽如此,我最近也喜欢这种方式:
for (int i = 0, upperBound = n*n + 2*n /*or n*(n + 2)*/; i < upperBound; ++i)
{
}
这样upperBound
的范围仅限于for
语句本身,并且不会在不需要它的外部范围内流失。
答案 2 :(得分:2)
在速度之前你必须考虑正确性。假设您的上限取决于数组的大小,并在循环内更改该数组(添加,删除元素)。 这就是说它更“强大”写:
for (int i=0; i < n*n+2*n; i++)
因为您重新评估循环的不变量。如果我觉得存在性能问题,我更喜欢分析。
答案 3 :(得分:2)
答案取决于编译器优化限制计算的能力,即执行您在代码中建议的相同优化(通常,编译器将尝试执行此类简单的优化)。
如果编译器无法断言在循环执行期间限制是否更改或其计算是否具有全局副作用,则它无法执行此优化。在这种情况下,如果您知道(在编译时)实际上没有副作用且限制没有改变,那么预先计算限制是一种明智的优化。例子:
// file foo.cc
extern int non_local_int; // access can be optimized
extern volatile int volatile_int; // access must not be optimized
extern int bar(int); // may have global side effects
extern void take_addr(int&); // may store address
namespace {
int addr_never_taken_int=10; // never operand of address-of operator
int addr_taken_int=10; // used as operand of address-of op.
}
void foo(int n)
{
for(int i=0; i<n*n+n+n+1; ++i) // can be optimized
{ ... }
int local_int = bar(n);
for(int i=0; i<n*local_int; ++i) // can be optimized
{ ... }
for(int i=0; i<n*non_local_int; ++i) // can be optimized, but is not threadsafe
{ ... no calls to outside code }
for(int i=0; i<n*bar(n); ++i) // cannot be optimized
{ ... }
for(int i=0; i<addr_never_taken_int; ++i) // can be optimized
{ ... }
take_addr(addr_taken_int);
for(int i=0; i<addr_taken_int; ++i) // cannot be optimized
{ ... code that calls *any* outside function }
for(int i=0; i<n*volatile_int; ++i) // must not be optimized
{ ... }
for(int i=0; i<n; ++i) // can be optimized
{ ... code that calls *any* outside function }
take_addr(n);
for(int i=0; i<n; ++i) // cannot be optimized
{ ... code that calls *any* outside function }
}
已编辑以反映supercat发表的评论。请注意volatile
objects are suitable for communication within a single thread of execution (e.g. with a signal handler), but not with another thread。线程安全是程序员的责任。
答案 4 :(得分:1)
是的,第一个比第二个慢。因为它必须在第一种情况下计算每次迭代的限制,而在第二种情况下,它在开始时计算限制一次,然后将其用于所有迭代。
答案 5 :(得分:1)
在这种情况下,除非操作顺序重要,否则我总是倒数到零或另一个常数,例如
n
对我来说,这样可以更容易地看到循环需要多长时间,并且不太可能让一个人关闭一个&#39;错误。换句话说,行为是在循环开始时完全定义的,而不用担心i
是否会被更改或.hero {
position:relative;
}
.hero:after,
.hero:after {
z-index: -1;
position: absolute;
top: 98.1%;
left: 70%;
margin-left: -25%;
content: '';
width: 0;
height: 0;
border-bottom: solid 50px #e15915;
border-left: solid 50px transparent;
border-right: solid 50px transparent;
}
.hero{
transform:rotate(45deg);
}
是否会突破数组。