python减少实现会产生什么开销?

vae*_*r-k 0 python clojure python-3.x clojurescript

我有一些python脚本花费的时间超出了我的预期,所以我开始调查并发现了python性能的一些惊喜.大多数情况下,它似乎围绕着reduce,但我不明白为什么.

为了实验,我写了以下两个模块:

py.py

from functools import reduce

def mysum(n):
    return reduce(lambda acc, x: acc + x, range(n + 1))

n = int(1e8)
print(mysum(n))
Run Code Online (Sandbox Code Playgroud)

clj.clj

(defn mysum [n]
  (reduce + (range (inc n))))

(println (mysum 1e8))
Run Code Online (Sandbox Code Playgroud)

我比较了他们的表现time:

?  ~ time python py.py
5000000050000000
python py.py  21.90s user 0.41s system 95% cpu 23.344 total
?  ~ time lumo clj.clj
5000000050000000
lumo clj.clj  2.44s user 0.13s system 102% cpu 2.519 total
Run Code Online (Sandbox Code Playgroud)

看起来python的执行速度比clojure实现慢10倍.但这与我的预期相反.

即使使用JVM运行clj文件,这会导致很大的启动成本,python也会超过一英里:

?  ~ time clj clj.clj
5000000050000000
clj clj.clj  6.01s user 0.72s system 153% cpu 4.394 total
Run Code Online (Sandbox Code Playgroud)

为什么python在这里减速这么慢?难道我做错了什么?

Ala*_*son 6

您的python代码正在被解释,而Clojure代码正由HotSpot编译器在JVM上编译.这是JVM的一大优势,也是Python和Ruby分别在JVM上拥有端口Jython和JRuby的原因.

原生Java中的简单总和甚至更快:以下是一些快速比较:

class Calc {
  public static long cumsum( long limit ) {
    long result = 0;
    for (long i=0; i<limit; i++) {
      result += i;
    }
    return result;
  }

(let [limit 1e8]
  (newline) (println :result-clj)
  (crit/quick-bench (reduce + (range limit)))

(newline) (println :result-java-cumsum)
(crit/quick-bench (Calc/cumsum limit)))

:result-clj             Execution time mean : 1777.600 ms
:result-java-cumsum     Execution time mean :   26.920399 ms
Run Code Online (Sandbox Code Playgroud)

是的,这是66倍的加速.尝试将计数减少到1e6.

:result-clj             Execution time mean : 17572.885 µs
:result-java-cumsum     Execution time mean :   257.092 µs
Run Code Online (Sandbox Code Playgroud)

另一个技巧是Hotspot编译器通常可以识别1..N中的和,并用代数公式代替

sum(1..N) => N*(N+1)/2
Run Code Online (Sandbox Code Playgroud)

根本没有循环!