重复执行一段代码固定次数

13

我想重复一段代码,但不使用条件语句,仍然可以指定重复的次数。

基本上,就像这样:

repeat(50)
{
    //Do stuff here.
}

除了复制粘贴50次之外,有没有其他方法可以做到这一点?

我这样做是因为我想如果我知道我想重复多少次某件事情,那么比每次检查条件要快。这准确吗?还是我仍然会检查它已经重复了多少次?

基本上,这样做会更快吗?


你是在寻找一个硬重复的代码吗?比如执行50次代码而不带任何条件语句? - WhozCraig
1
据我所知,编译器通常会将基于字面量大小(而非变量)的for循环优化为与写下该代码x次相同的结果代码。因此,repeat(50)for(char i=0; i<50; i++)是相同的(我使用char类型,因为万一它没有被优化掉,它不会为int分配4个字节,而只会为char分配1个字节)。 - Sellorio
1
@MrUniverse - 循环展开是一个微妙的问题,“优化”不仅仅是消除循环开销指令的问题。循环展开会创建额外的指令(循环体的副本),这意味着展开后的代码现在会占用更多缓存,这意味着您可能希望保留在缓存中的其他代码必须被逐出以容纳展开后的循环代码,而这些代码可能只执行一次(即不再需要)。如果您保留循环逻辑,则缓存突然变得非常有意义,并且循环开销成为一种福音而不是诅咒。 - phonetagger
@phonetagger 很好的评论,都是真的 :) 感谢上帝我不是编译器,否则生活会变得非常复杂。 - Sellorio
在几乎所有情况下,如果你关注循环条件的性能影响,而不是实际的循环体代码,那么你的优先级是错误的。我认为这是一点典型硬件知识很有用,但这只是基本直觉吧?比较两个数字肯定比执行实际工作要便宜得多。任何真正执行有价值工作的循环都会有一个完全超越简单条件检查成本的循环体。在这种情况下,为大量重复展开循环是愚蠢的,因为这样会导致代码臃肿,而仅带来微不足道的速度增加。 - underscore_d
5个回答

21

你试图通过使用某些构造来优化循环(包括手动剪切和粘贴代码)以优化循环的执行速度是不明智的。不要这样做;它可能会降低执行速度,而不是提高它。

在我遇到的任何C++实现中(MSVC 6.0、2003、2005、2010,GCC各个版本,Diab各个版本),假设函数中已经分配了其他变量,分配一个循环计数变量绝对不需要时间。对于一个简单的循环,如果没有函数调用,循环计数变量甚至可能永远不会到达内存;它可能在整个生命周期中完全保存在单个CPU寄存器中。即使它存储在内存中,它也会在运行时堆栈上,为它(和任何其他局部变量)分配空间将在单个操作中一次性完成,无论在堆栈上分配的变量数量多少,所需的时间都是相同的。像循环计数变量这样的局部变量在堆栈上分配,而堆栈分配是非常便宜的,与堆分配相比。

堆栈上的示例循环计数变量分配:

for (int i=0; i<50; ++i) {
    ....
}

另一个例子是在堆栈上分配循环计数器变量:

int i = 0;
for (; i<50; ++i) {
    ....
}

在堆上分配的循环计数变量示例(不要这样做;这是愚蠢的):

int* ip = new int;
for (*ip=0; *ip<50; ++(*ip)) {
    ....
}
delete ip;

现在来解决手动复制和粘贴以优化循环的问题,而不是使用循环和计数器:

你正在考虑的是手动展开循环。循环展开是编译器有时用于减少循环中开销的一种优化方法。只有当循环的迭代次数可以在编译时知道时(即迭代次数是一个常数,即使常数基于其他常数的计算),编译器才能这样做。在某些情况下,编译器可能确定展开循环是值得的,但通常它不会完全展开它。例如,在您的示例中,编译器可以确定从50次迭代到仅10次迭代,并复制5次循环体将加快速度。循环变量仍然存在,但现在代码只需要执行10次循环计数器的比较,而不是50次。这是一种权衡,因为循环体的5个副本在高速缓存中占用了5倍的空间,这意味着加载那些额外的相同指令的副本会强制高速缓存驱逐(丢弃)已经在高速缓存中并且您可能希望保留在高速缓存中的指令数量。此外,从主内存加载循环体指令的那4个额外副本比在不展开循环的情况下直接获取已加载指令从高速缓存中花费的时间要长得多。

总而言之,通常更有利的做法是只使用一个循环体的副本并继续保留循环逻辑(即根本不进行任何循环展开)。


第二个循环永远不会被执行。我猜你的意思是 int i = 0; - Aleph
@AnotherTest - 你为代码同行评审做出了充分的论证!谢谢。 - phonetagger
不错,思路清晰的回答。也许OP只是在寻找更优雅的C++方式,例如在Perl中,你只需写"for (1..50) {...}"或者甚至"print 'hello' for (1..50)"。 - MarkR
@MarkR - 感谢您的赞美。根据OP的最后一句话,“基本上,它是否更快?”我认为他们对性能感兴趣,而不是优雅。 - phonetagger

4
如果您想要编写repeat(x) {}这样漂亮的句法结构,那么您可以使用宏。
类似以下内容:
```#define repeat(x) for (int i = 0; i < x; i++)```

#include <iostream>

#define repeat(x) for(int i = x; i--;)

int main()
{
    repeat(10) 
    {
        std::cout << i << std::endl;
    }

    return 0;
}

这里的实现方式在for循环中使用了与小于号不同的零比较方式,这种方式可能会稍微更快一些

任何一个好的编译器都会识别标准的循环,它会循环一定次数,因此与零进行比较并不能使事情更快。这里有一个例子:https://godbolt.org/z/rYWsGdWf6。编译器展开了第一个循环,并将第二个循环改为倒数计数。在这些情况下使用宏只会使代码对于不熟悉您的宏的其他人难以阅读,并引起奇怪的错误。如果您定义了一个名为“i”的变量并使用您的宏,在循环中,“i”将意外地指代隐藏的循环变量而不是现有的“i”。 - eesiraed

3
可以在语言中加入“repeat(x)”的功能,但出于某些原因,C和C++的设计有些遵循处理器的能力限制。我曾经使用过大约10种不同的处理器结构,没有一种处理器可以在没有“检查是否达到次数”的情况下执行“循环这么多次”的操作。
所以,你需要编写一些检查重复次数的代码(或者确定还有多少次要执行——x86指令中就有一个叫做“loop”的指令,用于倒计时,如果计数器不为零,则跳转到循环开始处)。
如果编译器希望“展开”循环,因为它有一个常数次数的迭代,并且它决定“展开它更快”[编译器经常做出这样的决策,并且通常是正确的],那么编译器可能会这样做。但你仍然需要编写“检查”代码。

好的,谢谢你提供处理器方面的信息,很有趣。我完全忘记了编译器优化这个问题。 - Serdnad

-4
我认为最大的速度提升应该是您不需要分配迭代变量,但您所要求的无论如何都需要检查条件。本质上,您所拥有的内容如下所示。
int i = 0;
for(; i< 50; i++)
{
    Do Something.
}

我将它移出了for循环的原因是为了说明它可以是在循环之前初始化的任何变量。这与下面的内容完全相同:
for(int i =0; i< 50; i++)
{
}

为什么不使用 for(int i = 0; i < ...) - Mats Petersson
它和上面的代码完全一样吗?只是将初始化移到了 for 循环外。只是说明它可以是在循环之前的任何时候创建的变量。 - Nomad101
1
变量其实并不会每次都被分配,因此这段代码等同于在循环内部声明i - Hunter McMillen
我知道,然而如果他想要节省一个分配,他可以使用一个不同的变量,而不一定使用一个新的变量。这就是我所说的全部内容。 - Nomad101
1
最好让编译器每次循环都使用一个“新鲜”的变量,因为编译器不必弄清楚您是否需要稍后从早期的某个地方获取变量... - Mats Petersson
显示剩余2条评论

-4

这个怎么样:

typedef std::vector<int> times;
for (auto count : times(5))
{
    // execute the loop 5 times
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接