Keep on moving

あんまりまとまってないことを書きますよ

PyPyでHQ9+を実装してみた

PyPy で Grass を実装してみた - プログラマのネタ帳を読んで、
すごく楽しそうだったので、HQ9+を実装してみました。
こういう役に立ちそうで立たない実装を作るのは楽しかったです。

ソース

HQ9plus.py

import os, sys
# So that you can still run this module under standard CPython, I add this
# import guard that creates a dummy class instead.
try:
    from pypy.rlib.jit import JitDriver
except ImportError:
    class JitDriver(object):
        def __init__(self,**kw): pass
        def jit_merge_point(self,**kw): pass
        def can_enter_jit(self,**kw): pass

jitdriver = JitDriver(greens=['program'], reds=[])

class HQ9Plus(object):
    def __init__(self, src):

        self._src = src
        self._count = 0
        jitdriver.jit_merge_point(program=self._src)

    def run(self):
        _output = ""
        for str in self._src:
            if str == "H":
                _output += self._hello()
            elif str == "Q":
                _output +=self._print_source()
            elif str == "9":
                _output+=self._print_99_bottles_of_beer()
            elif str == "+":
                self._increment()
            else:
                _output+="some error occured"
        rtn = _output
        return rtn
    
    def _hello(self):
        return "Hello, world\n"
    
    def _print_source(self):
        return self._src + "\n"

    def _print_99_bottles_of_beer(self):

        _output = ""
        _list = range(1,100)
        _list.reverse()
        for i in _list:
            if i == 0:
                before = "no more bottles"
                after = "99 bottles"
            elif i == 1 :
                before = "1 bottle"
                after = "no more bottles"
            else:
                before = "%d bottles" % (i)
                after = "%d bottles" % (i - 1)

            if i == 0:
                action = "Go to the store and buy some more"
            else :
                action = "Take one down and pass it around"

            _output +="%s of beer on the wall. %s of beer.\n" % (before, before)
            _output +="%s, %s of beer on the wall.\n" % (action, after)
            _output +="\n"
        
        rtn = ""
        
        return rtn
            
    def _increment(self):
        self._count += 1

def parse(program):
    parsed = []

    for char in program:
        if char in ('H', 'Q', '9', '+'):
            parsed.append(char)
    
    return "".join(parsed)

def run(fp):
    program_contents = ""
    while True:
        read = os.read(fp, 4096)
        if len(read) == 0:
            break
        program_contents += read
    os.close(fp)
    program = parse(program_contents)
    print HQ9Plus(program).run()

    #program, bm = parse(program_contents)
    #mainloop(program, bm)

def entry_point(argv):
    try:
        filename = argv[1]
    except IndexError:
        print "You must supply a filename"
        return 1
    
    run(os.open(filename, os.O_RDONLY, 0777))
    return 0

def target(*args):
    return entry_point, None
    
def jitpolicy(driver):
    from pypy.jit.codewriter.policy import JitPolicy
    return JitPolicy()

if __name__ == "__main__":
    entry_point(sys.argv)

test.hq9p

HHQ+HQ++HQ9+

実行の方法

translateの方法
  1. https://bitbucket.org/pypy/pypy/get/release-1.6.tar.bz2をダウンロードして展開する
  2. python pypy-pypy-release-1.6/pypy/translator/goal/translate.py --opt=jit hq9plus.py
  3. hq9plus-c というファイルが作成される
実行
  • pure Pythonでの実行結果
$ time python hq9plus.py test.hq9p > /dev/null
real    0m0.027s
user    0m0.020s
sys     0m0.000s
  • translate.pyで作成されたファイルでの実行結果
$ time ./hq9plus-c test.hq9p > /dev/null
real    0m0.004s
user    0m0.000s
sys     0m0.000s

気づき点

  • translateは結構時間がかかります。jitオプションをつけると更に時間がかかります。
  • RPythonは型推論でエラーを出してくれるので便利。Pythonでは実行できても、潜在的なエラーを教えてくれる

まとめ

こんな簡単な実装なんですが、translateしてやることで何倍も早く実行することが可能です。
これもしかするとPythonで簡単なDSLを書いてスピードアップすることもできそうですね。
JITすげー。