循环速度更快,限制固定

Posted

技术标签:

【中文标题】循环速度更快,限制固定【英文标题】:Loop is faster with fixed limit 【发布时间】:2019-09-03 05:03:32 【问题描述】:

这个循环:

long n = 0;
unsigned int i, j, innerLoopLength = 4;
for (i = 0; i < 10000000; i++) 
    for (j = 0; j < innerLoopLength; j++) 
        n += v[j];
    

在 0 毫秒内完成,而这个:

long n = 0;
unsigned int i, j, innerLoopLength = argc;
for (i = 0; i < 10000000; i++) 
    for (j = 0; j < innerLoopLength; j++) 
        n += v[j];
    

需要 35 毫秒。 不管 innerLoopLength 是多少,第一种方法总是很快,而第二种方法越来越慢。

有人知道为什么吗?有没有办法加快秒版本的速度?我感谢每一位女士。

完整代码:

#include <iostream>
#include <chrono>
#include <vector>
using namespace std;

int main(int argc, char *argv[]) 
    vector<long> v;
    cout << "argc: " << argc << endl;
    for (long l = 1; l <= argc; l++) 
        v.push_back(l);
    

    auto start = chrono::steady_clock::now();

    long n = 0;
    unsigned int i, j, innerLoopLength = 4;
    for (i = 0; i < 10000000; i++) 
        for (j = 0; j < innerLoopLength; j++) 
            n += v[j];
        
    

    auto end = chrono::steady_clock::now();
    cout << "duration: " << chrono::duration_cast<chrono::microseconds>(end - start).count() / 1000.0 << " ms" << endl;
    cout << "n: " << n << endl;
    return 0;

使用 -std=c++1z 和 -O3 编译。

【问题讨论】:

检查编译后的机器码确定,但编译器可能优化了第一个版本并展开了内部循环,而第二个版本无法以相同的方式优化 编译器正在完全优化第一个循环,因此在生成的代码中根本没有循环。 为了将来参考,我建议使用godbolt.org 逐步执行您的代码。如果您使用编译器选项复制粘贴您的代码,您会看到生成的程序集有所不同,其中显示了优化发生的位置 也许int main(const int argc, char*argv[]) 有帮助。 IE。告诉编译器argc 不会改变。 在godbolt上试过const int argc,和int argc没有区别 【参考方案1】:

由于loop unrolling,固定长度循环要快得多:

循环展开,也称为循环展开,是一种循环变换 试图优化程序执行速度的技术 其二进制大小的代价,这是一种称为时空的方法 权衡。可以手动进行转换 程序员或优化编译器。

循环展开的目标是通过以下方式提高程序的速度 减少或消除控制循环的指令,例如 每次迭代的指针算术和“循环结束”测试;减少 分行处罚;以及隐藏延迟,包括延迟 从内存中读取数据。为了消除这种计算开销, 循环可以重写为类似独立的重复序列 声明。

基本上,你的 C(++) 代码的内部循环在编译之前被转换为以下内容:

for (i = 0; i < 10000000; i++) 
    n += v[0];
    n += v[1];
    n += v[2];
    n += v[3];

如您所见,它有点快。

在您的具体情况下,还有另一个优化来源:您将相同值的 1000000 倍相加到 n。 gcc 可以从 3.* 左右检测到它,并将其转换为乘法。您可以检查,执行相同的循环100000000000 次将在 0 毫秒内同样准备好。您可以检查 ASM 级别 (g++ -S -o bench.s bench.c -O3),在循环中您只会看到乘法而不是加法。为避免这种情况,您应该添加一些不能轻易转换为乘法的东西。

在第二种情况下,它们都不能完成。因此,在 ASM 级别上,您将不得不处理大量条件表达式(条件跳转)。这些在现代 CPU 中代价高昂,因为它们的意外结果会导致 CPU 管道重置。

你能帮什么忙:

如果您从 innerLoopLength 中知道一些信息,例如,如果它总是可以被 4 整除,您可以自己展开循环 一些gcc(g++)优化标志,帮助他理解,这里需要fast代码。至少使用 -O3 -funroll-loops 编译。

【讨论】:

我认为分支预测在这里并不那么重要——现代 CPU 分支预测器可以毫不费力地找出每个第 4 个循环条件都返回 false。然而,与展开的指令相比,“幼稚”形式的内部循环仍然需要大约 3.5 到 4 倍的指令。还有更多内容(至少clang 展开了内部循环),但这对于这个评论来说太多了。

以上是关于循环速度更快,限制固定的主要内容,如果未能解决你的问题,请参考以下文章

如何让程序更快的运行

华为防火墙配置了限制一台主机只能访问固定域名和IP的安全策略后打开网站加载速度很慢半天打不开

如何优化限制查询以便从庞大的表中更快地访问数据?

Swarm自建RPC突破官方10万次限制

限制集

Elasticsearches 提高 搜索速度