C++:循环优化和循环展开(循环或不循环)

 陈苏女士 发布于 2023-02-13 15:00

关于展开循环的所有100次迭代是否有效,没有单一的答案.

对于没有代码缓存的"较小"系统,展开所有100次迭代的机会非常好,至少在执行速度方面是这样.另一方面,足够小以至于其CPU没有高速缓存的系统通常会在其他资源中受到足够的限制,这样做是非常不可取的.

如果系统确实有一个缓存,那么展开循环的所有100次迭代往往会导致执行速度变慢的可能性非常大.循环本身的开销几乎肯定比重新获取100次基本相同的代码花费更少的时间.

在典型情况下,循环展开在循环的几次迭代展开时最有效(但通常少于100次迭代).在典型情况下,您会看到大约4到16次迭代的广泛平台被展开.

然而,正如许多人首先尝试优化的典型情况一样,我猜你真的在寻找完全错误的方向.如果你想优化那个循环,很可能(到目前为止)最大的收益来自于你在循环中做的微小改变.我愿意打赌,从展开循环中获得的任何改进都会太小而无法可靠地测量,更不用说实际注意了(即使你将迭代次数从100增加到几百万).

另一方面,如果你重写循环以消除每次迭代不必要的缓冲区刷新:

for ( int i = 1; i <= 100; i++ ) 
    cout << i << "\n";

[如果你没有意识到:std::endl在流中插入一个新行刷新流.在大多数情况下(可能包括这个),缓冲区刷新是不必要的,可能是不可取的.去除它可以提高速度的很多由8:1的因素--improvement:1或10:1是相当常见]

有可能根本不需要太多来衡量速度的差异.有一个非常公平的机会,你可以在100次迭代中测量它,如果你尝试更多的迭代,差异很可能变得非常痛苦.

当你处理一个不受I/O限制的循环,并且没有像这样明显的大规模改进时,循环展开可能会成为一个更有吸引力的选择.在这种情况下,您首先需要注意大多数编译器可以自动循环展开,因此尝试在源代码中执行此操作不太可能有很大帮助,除非这为其他优化提供了机会(例如,如果您有循环这甚至在迭代上做了一件事而在奇数迭代上做了另一件事,展开那两个迭代可以消除条件和跳跃等等,所以手工做可以提供有意义的改进,因为编译器可能没有"注意到"奇数/甚至模式,消除条件,跳跃等

还要注意,现代CPU可以(通常会)并行执行代码,并以推测方式执行代码,这可以消除循环的大部分开销.由于循环的分支几乎总是被占用(即,除了最后一次迭代之外的所有循环),CPU的分支预测器会将其预测为已采用,因此CPU可能同时"在飞行中"有几次迭代值的指令,即使你不要展开循环.循环本身的大多数代码(例如,递增i)可以与循环中的至少一些其他代码并行执行,因此循环的开销可能非常小.

编辑2:看看手头的具体问题,我认为我的工作方式有所不同.我没有将TTT板存储为2D阵列,而是将其存储为一对位图,一个用于X,另一个用于O. 这使您可以在单个操作中测试整个获胜组合,而不是三个单独的比较.由于每行是3位,因此对于常量使用八进制可能最容易:

static const std::array winners = {
    /* rows */      0007, 0070, 0700, 
    /* columns */   0111, 0222, 0444, 
    /* diagonals */ 0124, 0421
};

在这种情况下,我几乎肯定会使用循环:

char CheckForWinner(short X, short O) { 
    // `winners` definition from above goes here.

    for (int i=0; i

这里最大的问题是你是否真的想要单独传递X和O板,或者是否更有意义传递两个短路阵列.使用阵列的明显优势是更容易进入对方板.例如,要测试是否允许在一个板中移动,您需要检查该位是否在另一个板中设置.将电路板存储在一个阵列中,您可以通过n指示您要移动的电路板,并使用1-n另一个电路板,在那里您将检查该位是否已设置.

1 个回答
  • 关于展开循环的所有100次迭代是否有效,没有单一的答案.

    对于没有代码缓存的"较小"系统,展开所有100次迭代的机会非常好,至少在执行速度方面是这样.另一方面,足够小以至于其CPU没有高速缓存的系统通常会在其他资源中受到足够的限制,这样做是非常不可取的.

    如果系统确实有一个缓存,那么展开循环的所有100次迭代往往会导致执行速度变慢的可能性非常大.循环本身的开销几乎肯定比重新获取100次基本相同的代码花费更少的时间.

    在典型情况下,循环展开在循环的几次迭代展开时最有效(但通常少于100次迭代).在典型情况下,您会看到大约4到16次迭代的广泛平台被展开.

    然而,正如许多人首先尝试优化的典型情况一样,我猜你真的在寻找完全错误的方向.如果你想优化那个循环,很可能(到目前为止)最大的收益来自于你在循环中做的微小改变.我愿意打赌,从展开循环中获得的任何改进都会太小而无法可靠地测量,更不用说实际注意了(即使你将迭代次数从100增加到几百万).

    另一方面,如果你重写循环以消除每次迭代不必要的缓冲区刷新:

    for ( int i = 1; i <= 100; i++ ) 
        cout << i << "\n";
    

    [如果你没有意识到:std::endl在流中插入一个新行刷新流.在大多数情况下(可能包括这个),缓冲区刷新是不必要的,可能是不可取的.去除它可以提高速度的很多由8:1的因素--improvement:1或10:1是相当常见]

    有可能根本不需要太多来衡量速度的差异.有一个非常公平的机会,你可以在100次迭代中测量它,如果你尝试更多的迭代,差异很可能变得非常痛苦.

    当你处理一个不受I/O限制的循环,并且没有像这样明显的大规模改进时,循环展开可能会成为一个更有吸引力的选择.在这种情况下,您首先需要注意大多数编译器可以自动循环展开,因此尝试在源代码中执行此操作不太可能有很大帮助,除非这为其他优化提供了机会(例如,如果您有循环这甚至在迭代上做了一件事而在奇数迭代上做了另一件事,展开那两个迭代可以消除条件和跳跃等等,所以手工做可以提供有意义的改进,因为编译器可能没有"注意到"奇数/甚至模式,消除条件,跳跃等

    还要注意,现代CPU可以(通常会)并行执行代码,并以推测方式执行代码,这可以消除循环的大部分开销.由于循环的分支几乎总是被占用(即,除了最后一次迭代之外的所有循环),CPU的分支预测器会将其预测为已采用,因此CPU可能同时"在飞行中"有几次迭代值的指令,即使你不要展开循环.循环本身的大多数代码(例如,递增i)可以与循环中的至少一些其他代码并行执行,因此循环的开销可能非常小.

    编辑2:看看手头的具体问题,我认为我的工作方式有所不同.我没有将TTT板存储为2D阵列,而是将其存储为一对位图,一个用于X,另一个用于O. 这使您可以在单个操作中测试整个获胜组合,而不是三个单独的比较.由于每行是3位,因此对于常量使用八进制可能最容易:

    static const std::array<short, 8> winners = {
        /* rows */      0007, 0070, 0700, 
        /* columns */   0111, 0222, 0444, 
        /* diagonals */ 0124, 0421
    };
    

    在这种情况下,我几乎肯定会使用循环:

    char CheckForWinner(short X, short O) { 
        // `winners` definition from above goes here.
    
        for (int i=0; i<winners.size(); i++) {
            if (X & winners[i] == winners[i])
                return 'X';
            if (O & winners[i] == winners[i])
                return 'O';
        }
        return ' ';
    }
    

    这里最大的问题是你是否真的想要单独传递X和O板,或者是否更有意义传递两个短路阵列.使用阵列的明显优势是更容易进入对方板.例如,要测试是否允许在一个板中移动,您需要检查该位是否在另一个板中设置.将电路板存储在一个阵列中,您可以通过n指示您要移动的电路板,并使用1-n另一个电路板,在那里您将检查该位是否已设置.

    2023-02-13 15:02 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有