にほんごのれんしゅう

日本語として伝えるための訓練を兼ねたテクログ

shedskinはスピードだけじゃない

shedskinはスピードだけじゃない

shedskinはpythonスクリプトの文章を直接解釈して,C++に対応するコードを出力している.

これは,nodejsやpypyでのJITコンパイリングによる速度向上とは別物である.
JITは専用のフレームワーク上で動くことを強制されるので,スタンドアロンな実行バイナリを出力不能なのである.

このことは何を意味するか

  • shedskinは単独で実行可能なバイナリを出力可能
  • 単独で実行可能なバイナリはMakefileを加工することで,shared o bjectに変換可能である
  • nginx, apacheなどはshared objectで専用のモジュールをかける
  • CGIなんか目じゃないぐらい早い(盛った)

shedskinはPythonでの生産性を兼ね備えながら,従来C++でしか書けなかったネーティブモジュールを作成可能に!!

下記のPythonスクリプト

コードになにか意味を持たせて書いたわけでは,ない. ベンチマーク用のスクリプトとして書いた.

import re, sys, os, math

class A:
  def __init__(self):
    pass
  def echo(self, some):
    pass
    #print some,
  def add(self, rh, lh):
    return rh + lh
  def split(self, line, delim):
    return line.split(delim)
  def parse(self, line, regex):
    return re.search(regex, line).group(1)
#instance make
if __name__ == '__main__':
  for i in range(1,10000000):
    a = A()
    a.echo("")
    b = a.add(10, 10)
    c = a.split('a!a', '!')
    d = a.parse('mogemoge', 'o(.*?)o')

とりあえず速度の比較のデータ

pure Pythonで実行した結果

gkobayas@ubuntu:~/dev/shedskin_experiment/module_experimental2$ time python same.py

real    0m32.796s
user    0m32.534s
sys     0m0.164s

shedskinでC++化してコンパイルして実行した結果

gkobayas@ubuntu:~/dev/shedskin_experiment/module_experimental2$ time ./same

real    0m24.086s
user    0m23.941s
sys     0m0.016s

あれ,,,そうでもない,,,まあ文字列操作が主だからしょうがないか,,,

本題はC++で書かれたコードからこのshedskinのデータを利用することである

必要条件として下記のものが挙げられる.

  • shedskinで自動生成されたIFを持つプロトタイプ宣言を使用側で定義する.
  • shedskinは内部で閉じていて通常のstd::stringなどに情報を流すことができないので,shedskinそのものをハックする必要がある.
  • Makefileを実行バイナリではなくshared objectを吐くようにハックする必要がある.

Makefileを実行バイナリではなくshared objectを吐くようにハックする

Makefileを下記のように-shared -fPC変数を付ける

SHEDSKIN_LIBDIR=/usr/local/lib/python2.7/dist-packages/shedskin/lib
CC=g++
CCFLAGS=-O2 -march=native -Wno-deprecated $(CPPFLAGS) -I. -I${SHEDSKIN_LIBDIR} -fPIC -shared # <-追加します
LFLAGS=-lgc -lpcre $(LDFLAGS) -lutil

CPPFILES=/home/gkobayas/dev/shedskin_experiment/module_experimental2/same.cpp \
  ${SHEDSKIN_LIBDIR}/sys.cpp \
  ${SHEDSKIN_LIBDIR}/stat.cpp \
  ${SHEDSKIN_LIBDIR}/re.cpp \
  ${SHEDSKIN_LIBDIR}/os/path.cpp \
  ${SHEDSKIN_LIBDIR}/os/__init__.cpp \
  ${SHEDSKIN_LIBDIR}/math.cpp \
  ${SHEDSKIN_LIBDIR}/builtin.cpp

HPPFILES=/home/gkobayas/dev/shedskin_experiment/module_experimental2/same.hpp \
  ${SHEDSKIN_LIBDIR}/sys.hpp \
  ${SHEDSKIN_LIBDIR}/stat.hpp \
  ${SHEDSKIN_LIBDIR}/re.hpp \
  ${SHEDSKIN_LIBDIR}/os/path.hpp \
  ${SHEDSKIN_LIBDIR}/os/__init__.hpp \
  ${SHEDSKIN_LIBDIR}/math.hpp \
  ${SHEDSKIN_LIBDIR}/builtin.hpp #<-ちなみにハック対象

shedskinで自動生成されたIFを持つプロトタイプ宣言を使用側で定義する

shedskinによって自動生成されたclassのプロトタイプ宣言を見ると以下のようになっている.
externがついている変数は外部で利用可能なのである
つまりこの時,Aのインスタンスaがshedskinから引き出せる

extern __ss_int __3, __4, b, i;
extern list<str *> *c;
extern str *__name__, *d;
extern A *a; //ここが引き出せる
extern class_ *cl_A;
class A : public pyobj {
public:
    A() {}
    A(int __ss_init) { //たぶんこのインスタンスは呼べないんですよね
        this->__class__ = cl_A;
        __init__();
    }
    void *echo(str *some);
    str *parse(str *line, str *regex);
    __ss_int add(__ss_int rh, __ss_int lh);
    list<str *> *split(str *line, str *delim);
    void *__init__(); //コンストラクタとは別に初期化用のメソッドがある.
};

shedskinで自動生成されたIFを持つプロトタイプ宣言を使用側で定義する

実際に使用する側になる.

  #include <string>
#include <iostream>
#include <gc/gc_allocator.h>
//avoid corrision to prototype, declare only
/* 必要なだけのプロトタイプ宣言
 * header全部取り込むとか,Makefile作るコストと
 * 何のheaderが必要になるのか精査が面倒なので,
 * 必要な関数,変数だけ定義という風にした(externもしていないので動作してるは割と奇跡かもしれない)
 */
namespace __shedskin__{
  class str{
    public:
    str(const char* s);
    char* get_char();
    int __len__();
  };
};
namespace __same__ {
  using namespace __shedskin__;
  class A {
    public:
    void* echo(str* some);
    str* parse(str* line, str* regex);
    int add(int rh, int lh);
  };
  A *a;
};
using namespace std;
using namespace __same__;
using namespace __shedskin__;
int main(){
  cout << "test" << endl;
  str* b = new str("ababa");
  str* c = new str("(b.*?b)");
  str* d = a->parse(b, c);//aの機能を利用
  cout << d->get_char() << endl;//正しく"bab"が出力される
  cout << b->__len__() << endl; // __len__関数も使えるよ
  cout << a->add(10, 15) << endl; // 足し算できます
}

実行するとこんな感じ

gkobayas@ubuntu:~/dev/shedskin_experiment/module_experimental2$ ./a
test
bab
5
25h