web-dev-qa-db-ja.com

PythonとRuby)が遅いのに、LISP実装は速いのはなぜですか?

関数呼び出しやループのような単純なもの、そしてカウンターをインクリメントするループだけでもfarより多くの時間がPython =およびRubyチキンスキーム、ラケット、またはSBCLよりも。

これはなぜですか?スローネスは動的言語に支払う代償であると人々がよく言うのをよく耳にしますが、Lispは非常に動的であり、途方もなく遅いわけではありません(通常、Cの5倍未満の速度です; Ruby and = Pythonは2桁になる場合があります。さらに、LISPスタイルは再帰を使用しますが、常に末尾再帰ではありませんが、スタックはヒープ内の継続のリンクリストなどであり、命令型のPythonやRubyよりもLISPの速度を遅くする必要があります。

ラケットとSBCLはJITされていますが、チキンスキームは静的にコンパイルされるか、最適化されていないインタープリターを使用します。どちらも動的言語にあまり適しておらず、低速です。まだChicken Schemeの単純なcsiインタープリターを使用していても(バイトコードのコンパイルも実行しません!)、PythonおよびRubyをはるかに超える速度が得られます。

なぜPythonとRuby)は、同様に動的なLispに比べて途方もなく遅いのですか?それは、オブジェクト指向であり、巨大なvtableと型階層が必要なためですか?

例:階乗関数。 Python:

def factorial(n):
    if n == 0:
        return 1
    else:
    return n*factorial(n-1)

for x in xrange(10000000):
    i = factorial(10)

ラケット:

#lang racket

(define (factorial n)
  (cond
   [(zero? n) 1]
   [else (* n (factorial (sub1 n)))]))

(define q 0)

(for ([i 10000000])
  (set! q (factorial 10)))

タイミング結果:

ithisa@miyasa /scratch> time racket factorial.rkt
racket factorial.rkt  1.00s user 0.03s system 99% cpu 1.032 total
ithisa@miyasa /scratch> time python factorial.py
python factorial.py  13.66s user 0.01s system 100% cpu 13.653 total
28
ithisa

コンパイルされたLISPシステムは、通常、RubyまたはPythonよりもかなり高速です。

たとえば、RubyとSBCLの比較を参照してください。

http://benchmarksgame.alioth.debian.org/u32/benchmark.php?test=all&lang=yarv&lang2=sbcl&data=u32

またはPythonおよびSBCL:

http://benchmarksgame.alioth.debian.org/u32/benchmark.php?test=all&lang=python3&lang2=sbcl&data=u32

ただし、次の点に注意してください。

  • SBCLはネイティブコードコンパイラを使用します。バイトコードからネイティブコードへのバイトコードマシンまたはJITコンパイラのようなものは使用しません。 SBCLは、実行前に、すべてのコードをソースコードからネイティブコードにコンパイルします。コンパイラはインクリメンタルであり、個々の式をコンパイルできます。したがって、EVAL関数とRead-Eval-Print-Loopからも使用されます。
  • SBCLは、型宣言と型推論を利用する最適化コンパイラを使用します。コンパイラはネイティブコードを生成します。
  • 共通のLISPは、コードを動的または非動的にするさまざまな最適化を可能にします(インライン化、事前バインディング、型チェックなし、宣言された型に特化したコード、末尾呼び出しの最適化など)。これらの高度な機能を利用するコードは、特にコンパイラにこれらのことを伝える必要がある場合、複雑に見える場合があります。
  • これらの最適化を行わなくても、コンパイル済みLISPコードはインタプリタコードよりも高速ですが、最適化されたコンパイル済みコードよりも低速です。
  • Common LISPは、CLOS(Common LISP Object System)を提供します。 CLOSコードは通常、非CLOSよりも低速です-この比較が意味をなす場合。動的関数型言語は、動的オブジェクト指向言語よりも高速になる傾向があります。
  • 言語実装が高度に最適化されたランタイムを使用する場合(bignum算術演算など)、遅い言語の実装は最適化コンパイラよりも高速になる可能性があります。一部の言語には、Cに実装された多くの複雑なプリミティブがあります。それらは高速になる傾向がありますが、残りの言語は非常に遅くなる可能性があります。

また、一部の操作は類似しているように見えても、異なる場合があります。整数変数を反復するforループは、範囲を反復するforループと本当に同じですか?

17
Rainer Joswig

Ruby/Python/etcでのメソッドディスパッチは負荷が高く、Ruby/Python/etcプログラムは主にメソッドを呼び出すことによって計算します。 Ruby内のforループも、eachへのメソッド呼び出しの構文糖衣です。

12
Alex D

私はあなたのラケットのインストールについては知りませんが、フラグなしで実行された場合、私がちょうどapt-get install 'したラケットはJITコンパイルを使用します。 --no-jitで実行すると、Python時間(racket:3s、racket --no-jit:37s、python: 74s)。また、モジュールのスコープでの割り当ては、Pythonでのローカルの割り当てよりも言語設計上の理由から(非常に自由度の高いモジュールシステム))、コードを関数に移動するとPython 60年代。残りのギャップは、偶然の一致、さまざまな最適化の焦点の組み合わせとして説明できます(関数呼び出しはLISPで非常に高速でなければならない、Python人々はあまり気にしない)、品質それぞれの言語設計の基本的な結果ではなく、実装(参照カウントと適切なGC、スタックVMとレジスタVM)など)。

2
user395760