読者です 読者をやめる 読者になる 読者になる

twistedでkqreactorを使う

Python

 twistedのKQueueベースのreactor(twisted.internet.kqreactor)を試してみました。PyKQueueはメンテナンスされていないらしく、僕の環境(OS X 10.4.8, Python2.5, twisted2.5)では、少し手間取ったので、メモを残しておきます。


 twisted.internet.kqreactorには、PyKQueueが必要です。PyKQueueのサイトから最新版(1.3)をダウンロードしてきます。

$ curl -O ftp://ftp.freebsd.org/pub/FreeBSD/ports/local-distfiles/dwhite/PyKQueue-1.3.tar.gz
$ tar xzf PyKQueue-1.3
$ cd PyKQueue-1.3


 「最新版」といっても、Python1.Xの時代からメンテナンスされていないらしく、メモリ関連のコードに致命的なバグがあります。また、twistedのドキュメントにもpatchが記載されているので、それも合わせて適用する必要があります。
 まとめたものを、本エントリの最後に記載しました。PyKQueueのコードは非常に古めかしく、かつ、「書きかけ感」があるので、もっと手を入れたいところではありますが、twistedでkqreactorを使うことが当面の目的なので、最小限にとどめてあります。


 また、setup.pyも用意されていないので、自分で用意します。(代わりにMakefileが容易されていますが、これはPython拡張の配布の流儀に沿いません。)

# -*- coding: utf-8 -*-
# setup.py
from distutils.core import setup, Extension

setup(
    name="kqsyscall",
    version="1.3",
    ext_modules=[
        Extension("kqsyscall", sources=["kqsyscallmodule.c"])
    ]
)


 パッチを適用後、ビルド、インストールして、PyKQueueのセットアップは完了です。

$ patch < twisted.patch
patching file kqsyscallmodule.c
$ sudo python setup.py install


 twistdコマンドで、kqreactorを選択する場合は、-rオプションを使います。

$ twistd -ny spam.py -r kqueue


 プログラム内で、reactorを起動するときには、あらかじめtwisted.internet.kqreactorを読み込んで、kqreactorをインストールする必要があります。

from twisted.internet import kqreactor
kqreactor.install()


 肝心のパフォーマンスですが、標準のselectreactorとほとんど変わらないか、selectreactorよりも少し悪いような気がします。この原因が、PyKQueueやtwistedのコードにあるのか、あるいは他の原因があるのかは、もう少し調査してみないと分かりません。


 以下は、PyKQueue1.3のkqsyscallmodule.cに対するパッチです。twisted/internet/kqreactor.pyのコメント部分を参考にしています。

--- kqsyscallmodule.c	2001-01-29 11:59:50.000000000 +0900
+++ kqsyscallmodule.c.new	2007-02-19 21:25:30.000000000 +0900
@@ -58,7 +58,6 @@
 static KQEventObject *
 newKQEventObject (PyObject *arg)
 {
-  // return PyObject_New (KQEventObject, &KQEvent_Type);
   return PyObject_NEW (KQEventObject, &KQEvent_Type);
 }
 
@@ -67,8 +66,7 @@
 static void
 KQEvent_dealloc(KQEventObject *self)
 {
-  // PyObject_Del(self);
-  PyMem_DEL (self);
+  PyObject_Del(self);
 }
 
 // --------------------------------------------------------------------------------
@@ -137,7 +135,7 @@
 }
 
 statichere PyTypeObject KQEvent_Type = {
-  PyObject_HEAD_INIT(NULL)
+  PyObject_HEAD_INIT(&PyType_Type)
   0,                             // ob_size
   "KQEvent",                     // tp_name
   sizeof(KQEventObject),         // tp_basicsize
@@ -208,7 +206,8 @@
   } else {
     int kqfd = kqueue();
     if (kqfd < 0) {
-      PyMem_DEL (self);
+      Py_DECREF(self);
+      //PyMem_DEL (self);
       PyErr_SetFromErrno (PyExc_OSError);
       return NULL;
     } else {
@@ -291,14 +290,15 @@
 
   /* Build timespec for timeout */
   totimespec.tv_sec = timeout / 1000;
-  totimespec.tv_nsec = (timeout % 1000) * 100000;
+  totimespec.tv_nsec = (timeout % 1000) * 1000000;
 
   // printf("timespec: sec=%d nsec=%d\n", totimespec.tv_sec, totimespec.tv_nsec);
 
   /* Make the call */
-
+  Py_BEGIN_ALLOW_THREADS
   gotNumEvents = kevent (self->fd, changelist, haveNumEvents, triggered, wantNumEvents, &totimespec);
-
+  Py_END_ALLOW_THREADS
+  
   /* Don't need the input event list anymore, so get rid of it */
   free (changelist);
 
@@ -361,7 +361,7 @@
 statichere PyTypeObject KQueue_Type = {
 	/* The ob_type field must be initialized in the module init function
 	 * to be portable to Windows without using C++. */
-	PyObject_HEAD_INIT(NULL)
+	PyObject_HEAD_INIT(&PyType_Type)
 	0,			/*ob_size*/
 	"KQueue",			/*tp_name*/
 	sizeof(KQueueObject),	/*tp_basicsize*/