パフォーマンス向上のためのヒント

スクリプトのプリコンパイル

モジュールのスクリプトをプリコンパイルしておくと、コンパイルのためのオーバーヘッドをなくなるため、モジュールの初期化が若干速くなります。

% cd /usr/local/pnuts/modules
% pnutsc -o pnuts-modules-C.jar pnuts-modules.jar
ただし、次の点に注意してください。

文字列の連結

+演算子による文字列の連結はあまり速くありません。

a = ""
for (i : 1..10000){
  a += "."
}

まず、連結したい文字列を順にprint()println()等の関数で表示する関数を定義し、 pnuts.textモジュールのtextGrab()関数に渡します。

function f1(){
  for (i : 1..10000){
    print(".")
  }
}

f2 = textGrab(f1)

f2()

ループ

次のようなfor 文のループは、カウンタ変数に対する演算のオーバーヘッドのため、遅くなります。

for(i = 0; i < 1000000; i++){
}

ベンチマーク等、ループのオーバヘッドが気になるケースでは、次のようにするとよいでしょう。

for (i : 0..999999){
   ...
}

Java Beansのプロパティへのアクセス

getXXXX()/setXXXX()のようなメソッド呼び出しは、XXXXプロパティへのアクセスに置き換えることで速くなります。

now = Date()
longValue = now.time

Generatorスタイルとコールバック・スタイル

大きなループでメソッド呼び出しがある場合は、Generatorスタイルかコールバック方式を検討してください。

たとえば、テキストファイルの各行を順に読むコードは、readLines()メソッドを使ってGeneratorスタイルで記述することができ、メソッド呼び出しのオーバーヘッドを軽減することができます。

もう一つの例は、Mapのエントリーを一つずつ取り出して、key-valueペアを表示するものですが、これは、mapFunction関数を使って、コールバック形式で記述することができます。

Pnutsによるループ(遅い) Generatorスタイル(速い) コールバック・スタイル(速い)
while ((line = reader.readLine()) != null){
    println(line)
}
for (line : readLines(reader)){
  println(line)
}
readLines(reader, println)
m = map()
m.a = 1
m.b = 2
m.c = 3

for (pair : m.entrySet()){
   println(pair.getKey(), "=", pair.getValue())
}
n/a
m = map()
m.a = 1
m.b = 2
m.c = 3

mapFunction(function (k, v) println(k, "=", v), m)

適切な Implementation の選択

pnuts.lang.Implementation インタフェースは、スクリプトインタプリタの実装のインタフェースを定義しています。 次のクラスがその実装クラスとしてPnutsの配布パッケージに含まれています。

pnuts.lang.PnutsImpl
ASTインタプリタ
pnuts.compiler.CompilerPnutsImpl
On-the-fly コンパイラ
pnuts.ext.CachedPnutsImpl
Mixed モード。 コンパイル(パーズ)されたスクリプトをキャッシュして再利用する。

On-the-fly コンパイラは、通常はASTインタプリタよりも速く実行されます。 対話的な利用はコンパイルのオーバヘッドが気にならないのでOn-the-flyコンパイラが向いています。

Pnuts が、スクリプトエンジンとしてアプリケーションに埋め込まれ、同じスクリプトが何度も実行される場合、Mixed モードが適しています。 スクリプトをコンパイルするのに、そのスクリプトの大きさに応じて数ミリ秒から数百ミリ秒かかるので、もし、一部のスクリプトが何度も実行されるようならコンパイルされたコードをキャッシュすると効率が向上します。

もし、アプリケーションが決められたスクリプトしか実行しない場合、ASTインタプリタとプリコンパイルされたスクリプトを組み合わせるのが効果的です。コンパイルのためのオーバヘッドがないですし、必要であればコンパイラパッケージを取り去ることもできます。

メソッド呼び出しやインスタンス生成がボトルネックになる場合

makeProxy() の利用を検討してください。

Javaで関数やload可能なクラスを実装する

スクリプト実行に伴うオーバーヘッド(Reflection APIやメソッド検索等が原因となる)をなくす必要がある場合は、Javaで関数、あるいはload可能なクラスを実装すればよい。 "Javaによる関数の定義" and "load可能なクラス"を参照。