是否应评估或存储循环限制? 在C++中,将变量的限制存储在变量中比评估值更快吗?

是否应评估或存储循环限制? 在C++中,将变量的限制存储在变量中比评估值更快吗?,c++,optimization,C++,Optimization,例如: 这是一种较慢的使用方法吗 for(int i=0; i<n*n+2*n; ++i) { .... } for(int i=0;i在热点之外并不重要。我的意思是-是的,在没有编译器优化的情况下,只计算一次值在调试时会更快,在发布时至少会一样快。但这通常不重要。以更易于编写、阅读和维护的方式进行。让我引用Donald Knuth的名言: 真正的问题是程序员花了太多的时间 在错误的地点和错误的时间担心效率; 过早优化是万恶之源(或者至少是大多数问题的根源) 它)在编程中 话虽如此,我还

例如:

这是一种较慢的使用方法吗

for(int i=0; i<n*n+2*n; ++i)
{ .... }

for(int i=0;i在热点之外并不重要。我的意思是-是的,在没有编译器优化的情况下,只计算一次值在调试时会更快,在发布时至少会一样快。但这通常不重要。以更易于编写、阅读和维护的方式进行。让我引用Donald Knuth的名言:

真正的问题是程序员花了太多的时间 在错误的地点和错误的时间担心效率; 过早优化是万恶之源(或者至少是大多数问题的根源) 它)在编程中

话虽如此,我还是喜欢这种方式,因为最近:

for (int i = 0, upperBound = n*n + 2*n /*or n*(n + 2)*/; i < upperBound; ++i)
{
}
for(int i=0,上限=n*n+2*n/*或n*(n+2)*/;i

通过这种方式,
上限
的范围被限制在
for
语句本身,并且不会流入不需要的外部范围。

是的,第一个范围比第二个范围慢。因为在第一种情况下,它必须在每次迭代中计算限制,而在第二种情况下,它计算限制在开始时使用一次,然后将其用于所有迭代。

如果
n
是一个全局声明的非
易失性
变量,则

for(int i=0;i

未指定。允许编译器优化
n*n+2*n
,以便在另一个线程修改
n
时进行一次计算。此外,如果另一个线程能够修改
n
,则应采取步骤避免同时读写
n
(行为不明确)。考虑使用<代码> STD::原子< /代码>作为<代码> n>代码> < < /P>的类型。 因此,不管程序的控制是否达到<代码> > 循环,不管你是否考虑到性能上的考虑,都要适当地引入<代码>限制>代码>。
for(int i=0,limit=n*n+2*n;i

它的优点是
limit
的范围不会泄漏到周围的语句中

但是如果您能够,您可以始终反向运行循环:

for(int i=n*n+2*n-1;i>=0;--i)


非常小心,如果你采用了这个想法。如果你采用了这个想法。

在速度之前你必须考虑正确性。假设你的上限取决于数组的大小,并且在循环中改变那个数组(添加,删除元素)。 也就是说,它更“健壮”写:

for(int i=0;i

因为您重新评估了循环的不变量。如果我觉得有一些性能问题,我更喜欢分析。

答案取决于编译器优化极限计算的能力,即执行您在代码中建议的相同优化(通常,编译器会尝试执行这些琐碎的优化)

如果编译器无法断言限制是否在循环执行期间更改,或者其计算是否具有全局副作用,则它无法执行此优化。在这种情况下,如果您知道(在编译时)事实上,没有副作用,并且限制没有改变,那么预计算限制是一种明智的优化。示例:

// 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 }
}
//文件foo.cc
extern int non_local_int;//可以优化访问
extern volatile int volatile\u int;//不能优化访问
extern int bar(int);//可能有全局副作用
extern void take_addr(int&);//可以存储地址
名称空间{
int addr\u never\u take\u int=10;//运算符地址的never操作数
int addr_take_int=10;//用作op地址的操作数。
}
无效foo(int n)
{

对于(inti=0;i在这种情况下,我总是倒计时到零或另一个常数,除非操作顺序很重要,例如

for (int i = n*n + 2*n - 1; i >= 0; --i)
{ ... }

对我来说,这样可以更容易地看到循环需要多长时间,并且不太可能出现任何“一个接一个”的错误。换句话说,行为在循环开始时就已经完全定义好了,而不必担心
n
是否会被更改,或者
i
是否会从一个数组中中断。

好吧……测量它。如何测量?请以我的身份解释我是一个新手。我认为我们需要更多地了解代码。
limit
与循环有什么关系?这确实是一个错误的问题。如果你有速度问题,这可能是你最不希望改进的地方。优化的第一条规则:除非速度太慢,否则不要进行优化。(如果您不知道它是否太慢,无法满足您的需求,那么您还没有准备好进行优化。)现在,如果您对两者之间的行为是否不同感兴趣,这可能会影响程序的逻辑。如何对程序进行基准测试是特定于计算机和操作系统的。请阅读更多信息。在Linux上,您可以使用该命令。请确保在编译时启用(例如,使用
g++-Wall-O2
编译,如果使用…)现在还不清楚克努斯的话在这里有什么关系。为什么这个微优化会是邪恶的?@Walter:任何微优化在没有必要的时候都是邪恶的。这类问题正是克努斯所说的。@Walter:语义上的好理解:)我认为如果OP知道优化是必要的,他会告诉我们。而且,他一开始就不需要问这个问题。根据这个推理,这应该是没有必要的。很高兴看到一个明智的答案。@Walter“微观优化”它们几乎总是不必要的,而且从人们试图阅读代码的角度来看,它们几乎总是使代码变得更糟糕
// 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 }
}
for (int i = n*n + 2*n - 1; i >= 0; --i)
{ ... }