【问题标题】:Time complexity of a loop with few operations具有少量操作的循环的时间复杂度
【发布时间】:2020-10-13 10:23:15
【问题描述】:

众所周知,while 循环(例如:while(n--))或 for 循环(例如:for(i=0; i<n; i++))的执行时间取决于变量 n,即O(n)。此外,根据在线判断,10^7 次操作 ≈ 1s。 但是我尝试用很少的操作为n > 10^9 执行一个while循环和一个for循环,它似乎在1秒内很容易运行。我很好奇为什么会这样?

#include 
使用命名空间标准;
#define ll long long
诠释主要(){
    ll t = 1e18;
    ll cnt = 0;
    而(t--){
        cnt++;
    }
    cout 
    

【问题讨论】:

  • 时间复杂度不变,保持O(n)。但是 CPU 可能会缓存指令,因此会“非常快”地执行它们。或者编译器已经优化了整个循环,只分配了cnt= 1e18;
  • 条件10^7 operations ≈ 1s 永远不会是静态的,并且在不同平台上有所不同

标签: c++


【解决方案1】:

您编写的代码不是针对您的 cpu 的指令,而是针对您的编译器为您的 cpu 生成指令的指令。在这种特定情况下,很容易看出这一点

long long  t = 1e18;
long long cnt = 0;
while(t--){
    cnt++;
}
cout << cnt << '\n';

可以替换为

long long cnt = 1e18;
cout << cnt << '\n';

不改变程序的可观察行为。

【讨论】:

  • 还有哪些不依赖循环的操作?
  • @AbhishekKumar 对不起,我不明白你的问题。在您的代码中,没有什么取决于循环,因为它所做的只是cnt = 1e18;t = 0;,但您没有在循环之后使用t,所以循环是否减少它并不重要
【解决方案2】:

TL;DR:编译器只是丢弃了你的循环,清除了t,并将 1e18 卡在了cnt 中。如果你想知道我是如何得出这个结论的,请继续阅读:

在我的电脑和g++ -O1/-O0 上,程序需要永远运行,但使用-O2 它立即完成(与提问者的情况相同),所以我将使用-S -O2 汇编代码:

leaq    _ZSt4cout(%rip), %rdi
    movabsq $1000000000000000000, %rsi
    movq    %fs:40, %rax
    movq    %rax, 8(%rsp)
    xorl    %eax, %eax
    call    _ZNSo9_M_insertIxEERSoT_@PLT
    leaq    7(%rsp), %rsi
    movl    $1, %edx
    movb    $10, 7(%rsp)
    movq    %rax, %rdi
    call    _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l@PLT

显然编译器只是将 1e18 插入 cnt (movabsq XXX %rsi),将 t 设置为 0(使用 xorl %eax %eax 的魔法清除 %eax)然后跳过循环(没有 jmpje 或类似的命令,然后转到cout(通过call _XXX_insert_XXX 和后来的_ostream_insertXXX

【讨论】:

    【解决方案3】:

    一个简单循环的时间复杂度(与哪种类型无关)是 O(n),这很清楚。


    但是您应该知道,CPU 在其经常使用的快速访问缓存中存储了一些信息。但是,编译器通过识别可以更改哪些指令(代码)来优化代码,从而在保留逻辑的同时生成更少的指令。虽然编译器优化是一个巨大的话题,但循环是主要目标,因为它们一遍又一遍地重复相同的指令集。


    让我们考虑一下这样一个事实,即现代 CPU 以大约 30 亿次操作/秒的速度执行一组高度优化的编译指令,将在微秒内完成指令。

    【讨论】:

    • 我不确定是编译器通过识别哪些指令(代码)需要缓存来完成这项优化任务。编译器的指令不能指示 CPU 缓存哪些指令。 CPU 自己做。
    • @PaulOgilvie ,看来你是对的。 CPU 有自己的硬件和指令来管理缓存。在阅读您的评论后,我只记得 TLB 和缓存写入策略的使用。 CPU 处理缓存,但代码优化主要由编译器完成。
    猜你喜欢
    • 2021-10-07
    • 1970-01-01
    • 1970-01-01
    • 2013-12-08
    • 2015-06-17
    • 2014-09-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多