google-apputilsって何者?
Operations Research関連のオープンソースソフトウェアを探していたら,2010年9月から始まってgooglerが中心となって開発しているようなor-toolsというプロジェクトを見つけました.or-toolsはswigを使ったPythonのライブラリになっていてサンプルコードも多数含まれているのですが,サンプルコード一部(例えばknapsack.py)の中に下のような見慣れないコードがありました.
from google.apputils import app # 省略 def main(unused_argv): # 省略 if __name__ == '__main__': app.run()
google-apputils-pythonとはGoogle内製のアプリでも使用されてる便利ツールのようです.残念ながらまだドキュメントが充実していないようなのですが,どんなことができるのか簡単に見ていきましょう.まずは次のようなシンプルなPythonファイルを用意します.
#!/usr/bin/env python '''How to use this module''' from google.apputils import app def main(argv): if len(argv) < 2: raise app.UsageError('usage error') print argv if __name__ == '__main__': app.run()
sample.pyとして実行します.
$ sample.py --help How to use this module flags: google.apputils.app: -?,--[no]help: show this help --[no]helpshort: show usage only for this module --[no]helpxml: like --help, but generates XML output --[no]run_with_pdb: Set to true for PDB debug mode (default: 'false') --[no]run_with_profiling: Set to true for profiling the script. Execution will be slower, and the output format might change over time. (default: 'false') --[no]show_build_data: show build data and exit gflags: --flagfile: Insert flag definitions from the given file into the command line. (default: '') --undefok: comma-separated list of flag names that it is okay to specify on the command line even if the program does not define a flag with that name. IMPORTANT: flags in this list that have arguments MUST use the --flag=value format. (default: '') $ sample.py --helpshort How to use this module $ sample.py How to use this module usage error $ sample.py arg ['./sample.py', 'arg'] $ sample.py --run_with_profiling arg ['./sample.py', 'arg'] 4 function calls in 0.002 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 :0(len) 1 0.002 0.002 0.002 0.002 :0(setprofile) 1 0.000 0.000 0.002 0.002 profile:0(<function main at 0x1010679b0>) 0 0.000 0.000 profile:0(profiler) 1 0.000 0.000 0.000 0.000 sample.py:6(main) $ sample.py --run_with_pdb arg > /Users/likr/python/test/sample.py(7)main() -> if len(argv) < 2: (Pdb)
このようにapputilsを使えば,モジュールのdocstringをusageとして表示させたり,コマンドライン引数を渡すだけでプロファイラやデバッガの起動ができるようになります.apputilsでハンドルされるフラグはmainには渡ってきません.また,以下のようにフラグを自分で追加することもできます.
#!/usr/bin/env python '''How to use this module''' from google.apputils import app import gflags class MyFlag(gflags.BooleanFlag): def __init__(self): gflags.BooleanFlag.__init__(self, 'myflag', 0, 'my flag', short_name='m', allow_override=1) def Parse(self, arg): if arg: print 'myflag is used' def main(argv): if len(argv) < 2: raise app.UsageError('usage error') print argv if __name__ == '__main__': gflags.DEFINE_flag(MyFlag()) app.run()
$ sample.py -? How to use this module flags: ./sample.py: -m,--[no]myflag: my flag google.apputils.app: -?,--[no]help: show this help --[no]helpshort: show usage only for this module --[no]helpxml: like --help, but generates XML output --[no]run_with_pdb: Set to true for PDB debug mode (default: 'false') --[no]run_with_profiling: Set to true for profiling the script. Execution will be slower, and the output format might change over time. (default: 'false') --[no]show_build_data: show build data and exit gflags: --flagfile: Insert flag definitions from the given file into the command line. (default: '') --undefok: comma-separated list of flag names that it is okay to specify on the command line even if the program does not define a flag with that name. IMPORTANT: flags in this list that have arguments MUST use the --flag=value format. (default: '') $ sample.py --myflag aaa myflag is used ['./sample.py', 'aaa'] $ sample.py -m aaa myflag is used ['./sample.py', 'aaa']
gflags.BooleanFlag.__init__の第2引数が--xxxのxxxに,shortname引数が-yのyに対応します.
普通のmainの書き方と大きく変わらない書き方で,コマンドライン引数によるプロファイラの起動ができるのは便利そうだと思いました.google-apputilsはPyPIにも登録されているのでeasy_installやpipでもインストールできます.google-apputilsには,今回紹介した以外にもアプリケーションのサブコマンド,デバッグ,プロファイリング,日付などを便利に扱う機能があるようです.他の機能の紹介は機会があれば後日.
Python Hack-a-thonで発表してきました + 発表の補足
Python Hack-a-thon 2011.02で「PyCUDAの紹介」という内容で発表をしました.発表資料はSlideShareにアップしています.
発表時間が短くなったこともあり,説明が雑だった部分も多かったと思うので,喋り足りなかった部分をここで改めて補足します.
発表動機
ここ数年CPU単一コアのクロック数が伸び悩み,「タダ飯が食える時代は終わった」とまで言われる中,GPGPUはマルチスレッド・マルチプロセスプログラミングと同じぐらい重要な技術になっていくかもしれません.しかしながらGPUコンピューティングに関する知識が十分に広まっているとはまだ言えない現状だと思います.マルチコア環境における並行コンピューティングと同様で,GPGPUも「いいプロセッサを使ってるんだから速くなって当たり前」というわけではなく,GPUのアーキテクチャに適したアルゴリズムを考えることが重要です.自分自身がここ1年でGPGPUに触れてそういったことを強く感じたので,今回はより多くの人にGPUコンピューティングを広められたらと思い発表の機会をいただきました.
PyCUDAの利点
PyCUDAではとてもPythonらしい高度な抽象化が実現されているので,煩雑なメモリ操作などを簡略化し,全体として簡潔なコードでGPUコンピューティングを行うことができます.そしてPyCUDAのSourceModule用に書いたカーネル関数はそのままC言語の環境に持っていくことが可能です.GPUコンピューティングで性能を出すために重要なのはもちろん,GPU上で実行されるカーネル関数のチューニングです.CPUで行っていたある処理をGPU上で処理させてどの程度高速化できるかは,試してみないとわからないという部分があります.そこで,まずPyCUDAを使ってカーネル関数のチューニングを行うという使い方が考えられます.PyCUDAで十分な性能が出ないのなら,CやFortranで書き換えても十分な性能は得られないかと思います.まずはPyCUDAでGPUコンピューティングを体験してみるのはいかがでしょうか.
また,複数GPUを使った処理や複数マシンを使った処理をするときにもPythonを使えば比較的簡単に書けるかもしれません.
GPUArray
GPUArrayを使ったプログラミングをコメントを入れつつ少し解説します.
>>> import pycuda.autoinit # デバイスの初期化などをしてくれる >>> from pycuda import gpuarray >>> import numpy >>> a = numpy.random.rand(1000000).astype(numpy.float32) # ホスト側データ >>> gpu_a = gpuarray.to_gpu(a) # ホストからデバイスへ転送 >>> n = numpy.random.rand(100, 100).astype(numpy.float32) >>> gpu_b = gpuarray.to_gpu(b) # 多次元でもOK >>> gpu_a *= 2 # numpyのarrayのように演算するとGPU上で並列実行される >>> gpu_c = gpu_a + 2 # gpuarrayを直接生成できる >>> from pycuda import cumath # GPU上で並列実行される数学ライブラリ >>> gpu_c = cumath.sin(gpu_c) >>> c = gpu_c.get() # デバイスからホストへ転送 >>> print type(c) # numpyのarrayとして返ってくる <type 'numpy.ndarray'>
どうしてもデバイスホスト間のデータ転送はボトルネックになるので,シンプルな処理だとデータが十分に大きくなければnumpyの方が速くなってしまいます.データ転送は最小限に抑えましょう.
Amazon EC2 Cluster InstanceをAMI化
Cluster InstanceはHPC向けにサポートされた機能がある一方で通常のInstanceにはない制約もあります.Cluster InstanceはVirtualizationがHVMでなければならないようで,ec2-bundle-volを使ったAMI化の方法ではparavirtualとなるため,たとえそれをCluster Instance上で作ったとしても新しいCluster Instanceに戻すことができません.作成して環境構築したInstanceをしっかりstopしておけばその間は課金がされないのですが,新しくInstanceを作る毎に環境構築するのも時間の無駄なのでAMI化を行っておきたいところです.ここではHVMで使えるAMIの作り方を解説していきます.
と意気込んでいたのですが,意外と簡単でした.AWS Management Consoleから下図のようにCreate Image(EBS AMI)を選択します.
出てきたダイアログにイメージの名前と説明を入力して「Create This Image」としてやります.
そうすると2枚目の図のようにAMIが作成されます.イメージ作成処理には数分かかる場合があります.
というわけでCluster GPU InstanceにPyCUDA環境を構築とCluster GPU InstanceにPyOpenCL環境とかも構築で構築した環境をAMI化して公開しました.Community AMIsを「ami-f29f6c9b」か「501488653145/PythonGPU」で検索すれば引っかかると思います.使用時にはInstance TypeでCluster GPUを選択してください.
Cluster GPU InstanceにPyOpenCL環境とかも構築
Cluster GPU InstanceにPyCUDA環境を構築の続きで,PyOpenCLとGPU Computing SDK code samplesもインストールしましょう.まずはpipでPyOpenCLをインストールします.
CPLUS_INCLUDE_PATH=/usr/local/cuda/include PATH=/opt/local/bin:$PATH pip install pyopencl
コマンド一つでした.サンプルプログラムなどを実行して動作確認しましょう.
次にGPU Computing SDK code samplesです.現時点でバージョン3.1のToolkitがプリインストールされているのですが,これより新しい3.2のcode samplesなどを落としてきてもmakeが通らないのでご注意ください.
# wget http://developer.download.nvidia.com/compute/cuda/3_1/sdk/gpucomputingsdk_3.1_linux.run # sh gpucomputingsdk_3.1_linux.run
ここまで私とまったく同じやり方であればインストール時の質問はそのままEnterで大丈夫です.コンパイルにいくつかのライブラリが必要なのでyumでインストールします.
# yum install libGLU-devel libXi-devel libXmu-devel freeglut-devel
あとはmakeでエラーが起きなければ大丈夫です.
# cd NVIDIA_GPU_COMPUTING_SDK/C # make
deviceQueryを実行してみましょう.Cluster GPU Instanceに搭載されているTesla M2050の情報が2枚分見えるはずです.
# bin/linux/release/deviceQuery bin/linux/release/deviceQuery Starting... CUDA Device Query (Runtime API) version (CUDART static linking) There are 2 devices supporting CUDA Device 0: "Tesla M2050" CUDA Driver Version: 3.20 CUDA Runtime Version: 3.10 CUDA Capability Major revision number: 2 CUDA Capability Minor revision number: 0 Total amount of global memory: 2817982464 bytes Number of multiprocessors: 14 Number of cores: 448 Total amount of constant memory: 65536 bytes Total amount of shared memory per block: 49152 bytes Total number of registers available per block: 32768 Warp size: 32 Maximum number of threads per block: 1024 Maximum sizes of each dimension of a block: 1024 x 1024 x 64 Maximum sizes of each dimension of a grid: 65535 x 65535 x 1 Maximum memory pitch: 2147483647 bytes Texture alignment: 512 bytes Clock rate: 1.15 GHz Concurrent copy and execution: Yes Run time limit on kernels: No Integrated: No Support host page-locked memory mapping: Yes Compute mode: Default (multiple host threads can use this device simultaneously) Concurrent kernel execution: Yes Device has ECC support enabled: Yes Device 1: "Tesla M2050" CUDA Driver Version: 3.20 CUDA Runtime Version: 3.10 CUDA Capability Major revision number: 2 CUDA Capability Minor revision number: 0 Total amount of global memory: 2817982464 bytes Number of multiprocessors: 14 Number of cores: 448 Total amount of constant memory: 65536 bytes Total amount of shared memory per block: 49152 bytes Total number of registers available per block: 32768 Warp size: 32 Maximum number of threads per block: 1024 Maximum sizes of each dimension of a block: 1024 x 1024 x 64 Maximum sizes of each dimension of a grid: 65535 x 65535 x 1 Maximum memory pitch: 2147483647 bytes Texture alignment: 512 bytes Clock rate: 1.15 GHz Concurrent copy and execution: Yes Run time limit on kernels: No Integrated: No Support host page-locked memory mapping: Yes Compute mode: Default (multiple host threads can use this device simultaneously) Concurrent kernel execution: Yes Device has ECC support enabled: Yes deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 3.20, CUDA Runtime Version = 3.10, NumDevs = 2, Device = Tesla M2050, Device = Tesla M2050 PASSED Press <Enter> to Quit... -----------------------------------------------------------
続いてOpenCLです.こちらもmakeして異常がなければoclDeviceQueryを実行してみましょう.
# cd ../OpenCL # make # bin/linux/release/oclDeviceQuery
Cluster GPU InstanceにPyCUDA環境を構築
Cluster GPU InstanceはOSがCent OS 5.5なのでPythonのバージョンが2.4です.このままではいろいろと不便なのでまずは最新のPython(2.7.1)をソースコードからインストールしましょう.URLやバージョンは執筆時点でのものですので,適当に読み替えてください.インストール先は/opt/localとします.
# wget http://www.python.org/ftp/python/2.7.1/Python-2.7.1.tar.bz2 # tar jxvf Python-2.7.1.tar.bz2 # cd Python-2.7.1 # ./configure --prefix=/opt/local # make
makeすると,ライブラリが足りなくていくつかのモジュールがビルドされていないことがわかります.
Python build finished, but the necessary bits to build these modules were not found: _bsddb _curses _curses_panel _sqlite3 _ssl _tkinter bsddb185 bz2 dbm dl gdbm imageop readline sunaudiodev zlib
yumでインストールしていきましょう.
# yum install sqlite-devel readline-devel zlib-devel ncurses-devel tk-devel gdbm-devel openssl-devel bzip2-devel
再度makeします.
Python build finished, but the necessary bits to build these modules were not found: _bsddb bsddb185 dl imageop sunaudiodev
まだいくつかのモジュールがビルドされていませんが,必要なライブラリがわかりませんでした.ご存知の方は教えていただけると助かります.
# make # make install
Python本体のビルドは完了なはずです.PATHの設定をしておきます.
# echo 'export PATH=$PATH:/usr/local/cuda/bin:/opt/local/bin' >> /etc/profile # echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/local/lib64' >> /etc/profile # source /etc/profile
Pythonの動作確認.pythonだけだとデフォルトのpython2.4を見に行ってしまうので,python2.7と打ちます.
# python2.7 Python 2.7.1 (r271:86832, Feb 12 2011, 23:42:46) [GCC 4.1.2 20080704 (Red Hat 4.1.2-48)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>
pipを使えばPyCUDAを簡単にインストールできるので,まずpipをインストールしましょう.
# cd # wget http://pypi.python.org/packages/source/s/setuptools/setuptools-0.6c11.tar.gz#md5=7df2a529a074f613b509fb44feefe74e # tar xvzf setuptools-0.6c11 # cd setuptools-0.6c11 # python2.7 setup.py build # python2.7 setup.py install # cd # wget http://pypi.python.org/packages/source/p/pip/pip-0.8.2.tar.gz#md5=df1eca0abe7643d92b5222240bed15f6 # tar xvzf pip-0.8.2.tar.gz # cd pip-0.8.2 # python2.7 setup.py build # python2.7 setup.py install
CentOSはgccのバージョンも古く,PyCUDAのビルド時にエラーが発生します.gccもソースコードからビルドしましょう.
# cd # yum install gmp-devel glibc-devel # wget ftp://ftp.dti.ad.jp/pub/lang/gcc/releases/gcc-4.3.5/gcc-4.3.5.tar.bz2 # tar jxvf gcc-4.3.5.tar.bz2 # cd gcc-4.3.5 # ./configure --prefix=/opt/local --enable-languages=c,c++ # make # make install
いよいよPyCUDAのインストールです.pip実行時には,/opt/local/binにインストールしたgccを使ってもらうようにPATHを変更しています.
# pip install numpy # yum install gcc-c++ # PATH=/opt/local/bin:$PATH pip install pycuda
これでインストールは完了です.pycuda.autoinit,pycuda.driverをimportして,エラーが発生しなければ問題ないでしょう.
# python2.7 Python 2.7.1 (r271:86832, Feb 13 2011, 02:53:05) [GCC 4.1.2 20080704 (Red Hat 4.1.2-48)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import pycuda.autoinit >>> import pycuda.driver >>>
zipしたのをenumerate
以前友達から教えてもらった。
l1 = [2,3,4] l2 = ['a', 'b', 'c'] for i,(n,c) in enumerate(zip(l1, l2)): print i,n,c
回文の判定(シーケンスをコピーして反転する)
文字列をスマートに反転するにはスライスを使うと良いと思う.リストにはreverseメソッドがあるので,インプレースでいいならそっちを使う.リストだが,元のインスタンスとは別に反転したものが必要ならlist(reversed(seq))などとするよりはスライスでseq[::-1]としてやる.検証した環境ではスライスを用いる方が若干パフォーマンスが良かった.
応用例として,回文の判定.
def is_palindrome(s): return s == s[::-1]