【问题标题】:Binary search slower than hardcoded if branches from generated code?如果生成代码的分支,二进制搜索比硬编码慢?
【发布时间】:2012-07-05 12:00:09
【问题描述】:

This guy 声称二进制搜索(在 C 编译器中)比从生成的代码中硬编码的 if 分支慢。 (请原谅 Clojure 代码和古怪的标题 - 这声称这个人所做的通常与编译器有关)。

他写

我偶尔会在黑暗的角落看到这种代码。当一个男人知道 他的处理器如何工作,知道他的 C 编译器如何工作,知道他的数据 结构,真的,真的需要他的循环速度很快,然后他会 偶尔写这种东西。

这是真正的程序员编写的代码。

这是二分搜索示例(请原谅 Clojure)

Start: (1 2 3 4 6 8 9 10 11 12)
Finish: ((((1) (2)) ((3) ((4) (6)))) (((8) (9)) ((10) ((11) (12)))))

然后,他将二进制搜索替换为生成的代码,如果基于硬编码值进行分支:

(defn lookup-fn-handwritten [x]
  (if (< x 6) 
    (if (< x 3)                         ; x is < 6
      (if (< x 2)                       ; x is < 3
        (if ( < x 1)                    ; x is < 2
          0                             ; < 1
          1)                            ; 1 <= x < 2
        3)                              ; 2 <= x < 3
      (if (< x 4)                       ; 3 <= x < 6
        4                               ; 3 <= x < 4
        2))                             ; 4 <= x < 6
    (if (< x 10)                        ; 6 <= x < 10
      (if (< x 9)                       ; 6 <= x < 9
        (if (< x 8) 
          2                             ; 6 <= x < 8
          3)                            ; 8 <= x < 9
        3)                              ; 9 <= x < 10
      (if (< x 11)                      ; 10 < x
        (if (< x 12)                    ; 11 <= x
          1                             ; 11 <= x < 12
          0)
        0))))                           ; 12 <= x

http://www.learningclojure.com/2010/09/clojure-faster-than-machine-code.html

我的问题是 - 如果从生成的代码和硬编码值中进行分支硬编码是否比二进制搜索更有效? (在任何语言中 - 但作者声称这在 C 中有效 - 然后似乎只在 JVM 上演示它)。

(请再次原谅链接帖子的古怪标题 - 这只是疯狂。)

【问题讨论】:

  • 他的标题没有错,可以说一个好的算法总有一天会毁掉一个坏算法的手工优化汇编版本。
  • 这并不是什么特别的说法……
  • 对于这么小的集合,数组查找或 switch 语句会更快,它的O(1)

标签: java c clojure jvm


【解决方案1】:

好吧,if-cascade 可能与二分查找做同样的事情,这意味着它执行相同的比较,但没有相关的“二分查找管理”。这是一个展开的循环,编译器展开循环确实是有原因的。所以是的,它会更快。

但它真的会更快吗?现在有一个叫做“缓存”的问题。无论您是展开循环还是其他任何内容,您的代码都会变得更大,因此运行代码所需的更多内存访问可能会抵消这种好处。

此外,您永远不知道编译器可能会使用什么样的指令来优化代码,而当您手动展开循环时它可能不会使用这些指令。或者反过来,谁知道呢。在具有“内置”二进制搜索的语言中更是如此,因此编译器知道它正在处理什么。

因此,仅计算诸如“我有所有的比较而没有其他东西”之类的操作可能还不够;还有其他因素会影响执行时间。如果您在一个 CPU 上进行分析以发现“我的展开版本更快”,另一个 CPU 可能仍然不同意。

优化是一个词,不确定我是否可以在这里拼出来:-)

【讨论】:

  • 除了开场白“当一个人知道他的处理器是如何工作的......”之外,你基本上是对的 - 这基本上改变了问题的重点,因为你会知道 CPU,编译器、缓存大小、相对总线延迟、带宽等。我曾与非常聪明的专业开发人员一起工作,他们可以通过真正理解上述所有内容来获得显着的性能 - 在我看来,这就是问题的本质。
【解决方案2】:

“硬编码”版本也是一种二分搜索,只是比大多数二分搜索实现更高效。我根本不认为这种说法是非同寻常的。但是,如果您避免了大多数典型的低效率实现,您可能能够以相当快的速度进行一般的二分搜索。

【讨论】:

    【解决方案3】:

    这种比较在现代 JVM 上可能非常困难。
    因为 HotSpot 编译器在运行时会进行许多动态优化,如果它检测到一个类运行了很多次,它将广泛启动 内联函数调用,这会产生嵌套的 if 表达式树。

    在进行此类比较时,重要的是要在该类上“预热”JVM 几百万次以完成此类内联。我不认为差异会非常明显。

    【讨论】:

      【解决方案4】:

      级联 if 实际上是一个特定的二进制搜索,因此您可以通过没有实现通用二进制搜索的代码来节省,实际上它是一个 unwound loop,所以它总是会更快。

      在一般情况下(即您不是针对特定配置的情况),您可能会发现,一旦循环对于缓存来说太大,CPU 缓存会损害展开循环的性能。

      优化的第一条规则是衡量、发现问题并进行相应的优化,原始链接就是一个很好的例子,适合特定的配置。

      所以是的,无论编译器、解释器或 CPU 是什么,一个好的算法总是会击败一个坏的算法。这就是原始链接中的重点。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-03-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-22
        • 1970-01-01
        相关资源
        最近更新 更多