【问题标题】:Delphi Performance: Case Versus If德尔福性能:案例与如果
【发布时间】:2011-02-02 13:46:49
【问题描述】:

我想这可能与之前的 SO 问题有些重叠,但我找不到关于这个主题的 Delphi 特定问题。

假设您想检查一个无符号的 32 位整数变量“MyAction”是否等于任何常量 ACTION1、ACTION2、...、ACTIONn,其中 n 是 - 比如说 1000。我想,除了更优雅,

case MyAction of
  ACTION1: {code};
  ACTION2: {code};
  ...
  ACTIONn: {code};
end;

快很多
if MyAction = ACTION1 then
  // code
else if MyAction = ACTION2 then
  // code
...
else if MyAction = ACTIONn then
  // code;

如果正确的动作 ACTIONi 的 i 值很高,我猜 if 变体需要 O(n) 时间来完成(即找到正确的动作),而 case 变体花费的时间要少得多(O(1 )?)。

  1. 我是否更正了切换速度更快的说法?
  2. 我是否正确,在开关盒中找到正确操作所需的时间实际上与 n 无关? IE。检查一百万个案件真的不需要比检查十个案件更长的时间吗?
  3. 这究竟是如何工作的?

【问题讨论】:

    标签: performance delphi case if-statement


    【解决方案1】:

    编译器会将case语句翻译成以下之一:

    1. 两级表,使用一张表将值映射到索引,并使用索引从跳转表中选择地址
    2. 通过表间接跳转
    3. 连续跳跃
    4. 二分搜索 - 这是递归的,因此二分搜索的叶子可以使用 2、3 或 4 中的任何一个。

    它使用启发式方法,例如案例数量、案例范围、不同备选方案的数量(每个备选方案可能实现一系列不同的值)等。

    case 语句的直觉是它是一个O(1) 操作。

    【讨论】:

    • 我发现 Delphi 可以做到这一点以及所有其他优化并且仍然编译得如此之快,这让我感到很惊讶!
    【解决方案2】:

    先检查现实总是好的......

    Delphi 2010 编译器似乎非常喜欢测试和分支。比如下面这个简单的代码,是不会编译成分支表的。

    var
      c: (aaa, bbb, ccc);
    
    begin
      case c of
        aaa: sleep(0);
        bbb: sleep(0);
        ccc: sleep(0);
      end;
    end.
    

    编译器会生成如下代码:

    Project56.dpr.24: case c of
    0040A1C4 0FB6053C0E4100   movzx eax,[$00410e3c]
    0040A1CB 2C01             sub al,$01
    0040A1CD 7208             jb $0040a1d7
    0040A1CF 740F             jz $0040a1e0
    0040A1D1 FEC8             dec al
    0040A1D3 7414             jz $0040a1e9
    0040A1D5 EB19             jmp $0040a1f0
    Project56.dpr.25: aaa: sleep(0);
    0040A1D7 6A00             push $00
    0040A1D9 E86EDAFFFF       call Sleep
    0040A1DE EB10             jmp $0040a1f0
    Project56.dpr.26: bbb: sleep(0);
    0040A1E0 6A00             push $00
    0040A1E2 E865DAFFFF       call Sleep
    0040A1E7 EB07             jmp $0040a1f0
    Project56.dpr.27: ccc: sleep(0);
    0040A1E9 6A00             push $00
    0040A1EB E85CDAFFFF       call Sleep
    

    更复杂的案例将被编译成一个测试和跳跃系列。比如……

    var
      c: (aaa, bbb, ccc, eee, fff, ggg, hhh);
    
    begin
      case c of
        aaa: sleep(0);
        bbb: sleep(0);
        ccc: sleep(0);
        hhh: sleep(0);
      end;
    end.
    

    ...被编译成...

    Project56.dpr.24: case c of
    0040A1C4 0FB6053C0E4100   movzx eax,[$00410e3c]
    0040A1CB 2C01             sub al,$01
    0040A1CD 720C             jb $0040a1db
    0040A1CF 7413             jz $0040a1e4
    0040A1D1 FEC8             dec al
    0040A1D3 7418             jz $0040a1ed
    0040A1D5 2C04             sub al,$04
    0040A1D7 741D             jz $0040a1f6
    0040A1D9 EB22             jmp $0040a1fd
    Project56.dpr.25: aaa: sleep(0);
    0040A1DB 6A00             push $00
    0040A1DD E86ADAFFFF       call Sleep
    0040A1E2 EB19             jmp $0040a1fd
    Project56.dpr.26: bbb: sleep(0);
    0040A1E4 6A00             push $00
    0040A1E6 E861DAFFFF       call Sleep
    0040A1EB EB10             jmp $0040a1fd
    Project56.dpr.27: ccc: sleep(0);
    0040A1ED 6A00             push $00
    0040A1EF E858DAFFFF       call Sleep
    0040A1F4 EB07             jmp $0040a1fd
    Project56.dpr.28: hhh: sleep(0);
    0040A1F6 6A00             push $00
    0040A1F8 E84FDAFFFF       call Sleep
    

    此类代码最可能的原因是跳转表不能很好地与 L1 缓存一起使用,因此如果没有大量的案例标签存在,测试和跳转版本可能会更快。

    该推理的“证明”是以下程序确实被翻译成跳转表。

    var
      b: byte;
    
    begin
      case b of
        0: sleep(0);
        1: sleep(0);
        2: sleep(0);
        3: sleep(0);
        4: sleep(0);
        5: sleep(0);
        6: sleep(0);
        7: sleep(0);
        8: sleep(0);
        9: sleep(0);
       10: sleep(0);
       11: sleep(0);
       12: sleep(0);
       13: sleep(0);
       14: sleep(0);
       15: sleep(0);
       16: sleep(0);
       17: sleep(0);
       18: sleep(0);
       19: sleep(0);
       20: sleep(0);
      end;
    end.
    
    Project56.dpr.12: case b of
    0040A178 0FB6C0           movzx eax,al
    0040A17B 83F814           cmp eax,$14
    0040A17E 0F8728010000     jnbe $0040a2ac
    0040A184 FF24858BA14000   jmp dword ptr [eax*4+$40a18b]
    ...
    Project56.dpr.14: 1: sleep(0);
    0040A1EB 6A00             push $00
    0040A1ED E85ADAFFFF       call Sleep
    0040A1F2 E9B5000000       jmp $0040a2ac
    Project56.dpr.15: 2: sleep(0);
    0040A1F7 6A00             push $00
    0040A1F9 E84EDAFFFF       call Sleep
    0040A1FE E9A9000000       jmp $0040a2ac
    Project56.dpr.16: 3: sleep(0);
    0040A203 6A00             push $00
    0040A205 E842DAFFFF       call Sleep
    0040A20A E99D000000       jmp $0040a2ac
    ...
    

    巴里可以给我们一个明确的答案。我只是在测试和闲逛。

    【讨论】:

      【解决方案3】:

      请注意,如果 MyAction 的值是加权的,则使用级联 if..else 可以获得良好的性能,您将最可能的情况放在顶部附近。我并不是说在处理整数时它会在性能方面与 case/switch 语句竞争。但是,如果一个案例不合适(例如,假设您有字符串),请将您的高百分比测试放在顶部。

      【讨论】:

        【解决方案4】:
        1. 是的,开关是 O(1),而级联 if 是 O(n)
        2. 是的,见 (1)
        3. 带有branch table

        【讨论】:

        • @Chris - O(n/2) is O(n) - 通常使用 n 的最高增长率函数,省略常数因子。
        猜你喜欢
        • 2014-07-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-19
        • 2012-11-22
        • 1970-01-01
        相关资源
        最近更新 更多