为什么Clojure的Hello World程序与Java和Python相比如此缓慢?

11

更新

根据许多人的建议,看起来这是因为clojure代码首先被编译,然后再执行。AOT编译应该有所帮助。鉴于我发现实际的Clojure AOT编译过程有点棘手(例如classpath问题、目录问题等等),我在这里写了一个小的逐步过程,以供有兴趣的人参考。


大家好,

我正在阅读《Programming Clojure》并比较我用于一些简单代码的一些语言。我注意到在每种情况下,clojure实现都是最慢的。例如,

Python - hello.py

def hello_world(name):
  print "Hello, %s" % name

hello_world("world")

结果如下,

$ time python hello.py
Hello, world

real    0m0.027s
user    0m0.013s
sys 0m0.014s

Java - hello.java

import java.io.*;

public class hello {
  public static void hello_world(String name) {
    System.out.println("Hello, " + name);
  }

  public static void main(String[] args) {
    hello_world("world");
  }
}

以及结果,

$ time java hello
Hello, world

real    0m0.324s
user    0m0.296s
sys 0m0.065s

最后,

Clojure - hellofun.clj

(defn hello-world [username]
  (println (format "Hello, %s" username)))

(hello-world "world")

以及结果,

$ time clj hellofun.clj 
Hello, world

real    0m1.418s
user    0m1.649s
sys 0m0.154s

这是整整1.4秒的时间!有没有人知道造成这个问题的原因?Clojure真的那么慢吗,还是需要使用JVM技巧等来加快执行速度?

更重要的是,这种巨大的性能差异在某些时候不会成为一个问题吗?(我的意思是,假设我在生产系统中使用Clojure - 我得到的利益似乎完全被我在这里看到的性能问题所抵消了)。


这里使用的机器是一台2007年的Macbook Pro,运行Snow Leopard系统,2.16Ghz Intel C2D和2G DDR2 SDRAM。

顺便说一句,我使用的clj脚本是从这里获取的,并且看起来像这样:

#!/bin/bash
JAVA=/System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home/bin/java
CLJ_DIR=/opt/jars
CLOJURE=$CLJ_DIR/clojure.jar
CONTRIB=$CLJ_DIR/clojure-contrib.jar
JLINE=$CLJ_DIR/jline-0.9.94.jar
CP=$PWD:$CLOJURE:$JLINE:$CONTRIB

# Add extra jars as specified by `.clojure` file
if [ -f .clojure ]
then
  CP=$CP:`cat .clojure`
fi

if [ -z "$1" ]; then
  $JAVA -server -cp $CP \
      jline.ConsoleRunner clojure.lang.Repl
else
  scriptname=$1
  $JAVA -server -cp $CP clojure.main $scriptname -- $*
fi

2
每次运行Clojure示例时,它都会被编译。如果您想要测量运行性能,则需要进行AOT编译。 - fogus
1
有一个非常好的理由,不是每个人都有勇气对代码进行基准测试。这并不容易。你不能只是点击运行并观看秒表。 - Rayne
1
@fogus - 谢谢,我正在查阅AOT编译。@Rayne - 我并没有以字面意义上的“基准测试”代码。我想要一个解释为什么它比其他语言运行缓慢的原因,并且即时编译与AOT编译的解释帮助我理解了这一点! - viksit
你能否更新一下,使用AOT编译后的速度如何? - rogerdpack
4个回答

27

在这里你并没有测量太多,除了Clojure启动时间之外。你现在运行程序的方式同时也计算了编译时间。如果你想要看到更快的加载速度,你需要提前编译你的代码。

作为一个用过Python编程的人,我发现Clojure通常比Python快得多,而且你通常可以将Clojure程序的速度提高到纯Java的2倍至4倍。


你认为Clojure和PyPy相比怎么样?最近我惊讶地发现,我做的一些图论计算在PyPy中比Clojure快得多(而Clojure比CPython快得多)。两种情况下的代码大多是函数式的,使用数组(Python中的列表,在Clojure中是向量),集合和惰性序列(Python中的生成器)。 - Omar Antolín-Camarena
2
如果您将可变列表与向量进行比较,则会看到性能差异。在Clojure中,当性能真正重要时,我会使用Java数组或轻量级对其进行抽象处理。话虽如此,PyPy的性能似乎非常好。但是,现在我熟悉/熟悉如何从Clojure中获得良好的性能之后,我认为在PyPy与JVM竞争之前还有一些路要走。 - dnolen

2
补充dnolen的回答,当进行Python与Clojure的时间对比时,您可能需要将您的“基本工作单元”打包成一个函数,然后在REPL中使用time宏(在Clojure中)或timeit.timeit函数(在Python中;或者更好地使用IPython的计时工具)来计时。结果应该大致可比。(请注意,Clojure代码需要运行几次才能达到完全性能。)
此外,Clojure还有一些基准测试套件,例如Criterium,您可能想考虑使用其中之一。

感谢提供的信息。我已经使用Python Timeit进行了Python性能基准测试和分析。这个问题更多的是关于JVM设置是否存在问题导致性能下降。它被AOT编译也从很大程度上解释了这一点。 - viksit
@viksit: 是的,JVM启动是另一个重要因素。(显然,如果你的进程有大量工作要做,这就不再是一个重要因素,但这并不适用于“Hello World”示例,或者大多数微基准测试的单次运行。) - Michał Marczyk
@downvoter:出于好奇,您不同意这个答案的哪一部分?(如果是像“Clojure的时间不完全像Python的timeit”这样的内容,那么请注意答案中的“大致可比”的部分(以及预热的备注),并问问自己是否这可能足以进行我们在同一球场的比较... 如果是其他原因,我真的很想知道。) - Michał Marczyk

2

还要注意,在您的clj脚本中使用“-server”选项将使用“服务器JVM”,该选项针对长时间运行的进程进行了优化,但启动时间较慢。

您的Java示例未包括此选项,因此可能正在使用“客户端JVM”,该选项针对更快的启动时间进行了优化。

尝试运行java -jar clojure.jar -i hellofun.clj以进行更公正的比较。


1
通常情况下,与本地或解释语言相比,JVM的启动时间已经有点慢。此外,Clojure在启动时会增加相当大的开销,因为它需要编译和加载大量代码。即使使用AOT,Clojure在运行之前仍然需要设置很多东西。
总之,在短期进程中不要依赖Clojure。大多数情况下,甚至不要依赖Java。像Node.js、Python、Lua等本地或解释语言会更好。
对于中长期进程,Clojure的平均速度将比几乎所有其他动态语言都快,超过Python和Ruby。如果需要,Clojure可以轻松达到与Java几乎相同的速度,并且其Java互操作性非常容易,只需将几个函数更改为纯Java即可在大多数情况下获得与Java相同的速度。
现在,如果您真的想要一些快速的Clojure,我建议您看看lumo。它是一个自包含的ClojureScript REPL,运行在引导ClojureScript上,因此没有JVM可见。
time python -c "print(\"Hello World\")"
Hello World

real    0m0.266s
user    0m0.015s
sys     0m0.202s

time lumo -e "\"Hello World\""
"Hello World"

real    0m0.438s
user    0m0.000s
sys     0m0.203s

如您所见,Lumo的启动速度与Cpy3k相当接近。
另一种选择是Hy,虽然不再是Clojure,但仍然是一种受Clojure启发的Lisp,在Python上运行。
time hy -c "(print \"Hello World\")"
Hello World

real    0m0.902s
user    0m0.000s
sys     0m0.171s

它的启动时间比Cpy3k和Lumo都稍慢,但它提供了Clojure语法和宏,使你可以使用所有Python功能。

谢谢!我一直在关注Lumo,也认真考虑过hy作为一个项目的选择。你有没有用过hy来做生产级别的东西?另外,6年后的新回复,时间过得真快啊。 - viksit
@viksit 我实际上还没有在生产环境中使用过Hy,只是在自己的时间里玩弄了一下。 - Didier A.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接