倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

マジック・パッケージを試してみる

今回はマジック・パッケージを使ったメタプログラミングを試してみます。

  • 一応、よくやるメタ・プログラミング、復習
  • マジック・パッケージでメタプログラミング
  • パフォーマンスは果たして?

参考 URL

一応、よくやるメタ・プログラミング、復習


Groovy では既存のクラスに新たにメソッドを追加したい場合、MetaClass にクロージャ型のプロパティをセットします。 例えば*1 apache commons-math の複素数を表す Complex クラスに対して、(演算子オーバーロードとして) plus メソッドを追加するには

Complex.metaClass.plus = { Complex arg -> delegate.add(arg) }

という風にします。 Complex オブジェクトにはもともと add メソッドが定義されてます(念のため)。 もう少し具体的には、

Complex.metaClass.plus = { Complex arg -> delegate.add(arg) }

def c1 = new Complex(1, 2)
def c2 = new Complex(3, 4)
println(c1 + c2)

という風に + 演算子(plus メソッド)が実行できるようになります。

マジック・パッケージでメタプログラミング


では、マジック・パッケージを使った方法。 この方法では、決められた規約のパッケージ名・クラス名のメタクラスを作成することで、使用者が事前にコードを実行(metaClass.plus = ... のような)せずにメソッドを使用できるようになります。 まぁ、具体的にやってみましょう。

上記の例での Complex クラスはパッケージ org.apache.commons.math.complex に含まれているので、このクラスにメソッドを追加したい場合は

  • groovy.runtime.metaclass.org.apache.commons.math.complex パッケージ

に ComplexMetaClass クラスを作成します:

package groovy.runtime.metaclass.org.apache.commons.math.complex

class ComplexMetaClass extends DelegatingMetaClass {

    ComplexMetaClass(MetaClass meta) {
        super(meta)
    }

    Object invokeMethod(Object object, String method, Object[] args) {
        if (method == 'plus') {
            return object.add(args[0])
        } else {
            super.invokeMethod object, method, args
        }
    }
}

このクラスをコンパイルしてクラスパス上におくと、

def c1 = new Complex(1, 2)
def c2 = new Complex(3, 4)
println(c1 + c2)

というコードがそのまま実行できます。 ちなみに、この方法はコンパイル前提だそうです。

パフォーマンスは果たして?


マジック・パッケージを使った上記の方法では invokeMethod メソッドを実装しているので少々パフォーマンスが心配。 ってことで簡単に実行時間を測ってみましょう。

  • JavaJDK 7 update1
  • Groovy : 2.0.0-beta-1

で試してます。

通常のメソッド

まずは通常のメソッドを使う場合。 複素数の足し算を1,000,000回実行しています。

import org.apache.commons.math.complex.Complex

perform{ int n ->
    def c1 = new Complex(n, n)
    def c2 = new Complex(n, n)
    c1.add(c2)
}

def perform(Closure c){
    long start = System.currentTimeMillis()
    1_000_000.times(c)
    long end = System.currentTimeMillis()
    println(end - start)
}

この実行時間を1としましょう。

メタクラスを使う

次はよくやるメタプログラミングの方法。

import org.apache.commons.math.complex.Complex

Complex.metaClass.plus = { Complex arg -> delegate.add(arg) }

perform{ int n ->
    def c1 = new Complex(n, n)
    def c2 = new Complex(n, n)
    c1 + c2
}

def perform(Closure c){ ... }

perform メソッドは先ほどと同じ。 実行時間は約1.7でした。 おっ、Groovy 結構速くなってんじゃない!?

マジック・パッケージ

で、最後はマジック・パッケージを使った方法。

import org.apache.commons.math.complex.Complex

perform{ int n ->
    def c1 = new Complex(n, n)
    def c2 = new Complex(n, n)
    c1 + c2
}

def perform(Closure c){ ... }

この方法の実行時間は約4.2。 ん〜、invokeMethod メソッドでコーディングしてる分、パフォーマンスが劣化してます。 さらに哀しいことに、この方法ではもともと Complex クラスに定義されている add メソッドを使っても同様のパフォーマンスの劣化があることです! add メソッドを呼び出しても ComplexMetaClass クラスの invokeMethod メソッドが実行されてるんでしょうね。 ComplexMetaClass をもうちょっと違った実装にすれば解決するのかも知れませんが(例えば DelegatingMetaClass 以外のクラスを拡張するとか)、 これはちょっと辛い。

プログラミングGROOVY

プログラミングGROOVY

*1:どうも例えがマイナーなライブラリを使ったものになるのが申し訳ないですが。