Raspberry Pi でプログラミング?
Raspberry Pi は現在(2013/02/02)のところ、XのドライバーがGPUでアクセラレーションされていないため、ウィンドウの消去、移動、スクロールなどでCPUに多くの負荷がかかります。その結果、GUIで使用すると遅く感じます。 一方、コンソールに直接OpenGL ES2の出力を表示させると、同じRaspberry Pi とは思えないほどのスピードで3Dアニメーションができます。
3Dのアニメーションを行うゲームなどのアプリケーションは、CやC++でOpenGL ES2のプログラムを作成すれば最も良いパフォーマンスを出せます。 しかしプログラミングの学習用として C や C++ を題材にするのは無理があります。たとえば文字列を扱うだけでメモリアドレスやポインタの概念が必要になってきて大変です。 Raspberry Pi のターゲットユーザである子供が皆、CやC++のプログラミングに興味を持ってくれるとも思えません。
Raspberry Pi には、どんな言語が適当なんでしょうか? 公式のSDカードイメージの Raspbian には最初から多くの言語が、すぐ使用できるように入っています。 私自身は、最近、Python、C++、LuaJITを使ってOpenGL ES2を試していましたが、いまのところ Raspberry Pi では速度とプログラミングの難易度のバランスで LuaJIT が最も適していると思っています。
今回はまず、OpenGL ES2のプログラムで必要となる 4x4行列の乗算をいろいろな言語で自己中心的にベンチマークをしてみました。 あくまで対象を3Dのリアルタイムアニメーションのプログラミング入門としています。 今回使った言語は、公式のSDカードイメージの Raspbian(2012-12-16) にすべて最初から含まれています。
結果
まずは結果を示します。LuaJIT は Python の100倍のスピードです。Quaternionと行列の相互変換というような細かい計算を多く行うアプリケーションには非常に有利な処理系です。LuaJIT は ffiモジュールを使うことで、Cのライブラリを自由に呼び出すことができるため、他のアプリケーションに言語機能を提供するための組み込み用言語のLuaとは違って、高速な汎用言語となっています。CPU が遅めの Raspberry Pi にとっては非常に強力なツールになります。
言語 | 備考 | 実行時間(秒) |
---|---|---|
Python 2.7.3 | リスト | 169.6 |
NumPy | 48.6 | |
Python 3.2.3 | リスト | 177.4 |
NumPy | 58.9 | |
Lua 5.1.5 | - | 21.9 |
LuaJIT 2.0.0 | テーブル(JIT) | 2.42 |
テーブル(JIT OFF) | 6.65 | |
ffi double (JIT) | 1.37 | |
ffi double (JIT OFF) | 57.3 | |
gcc 4.6.3 | -O0 | 2.26 |
-O2 | 0.66 | |
アセンブリ | ベクトル演算 | 0.63 |
Python
もともとは、教育用に Python を使うために Raspberry Pi の開発が始められたようです。「ラズペリーパイ」の「パイ」は「パイソン」の「パイ」のつもりだったそうです。Python にはctypes という C用の共有ライブラリ内の関数を動的にリンクして呼び出す事のできる強力な機能があります。したがって、Raspberry Pi 固有のlibbcm_host.so などに含まれる関数も ctypes をインポートして、「bcm = ct.CDLL('libbcm_host.so')」のように指定するだけで使えるようになります。
Raspberry Pi には以下のように、Python 2.7.3 と Python 3.2.3 の両方が入っています。
$ python Python 2.7.3 (default, Jan 13 2013, 11:20:46) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>
$ python3 Python 3.2.3 (default, Jul 6 2012, 13:39:51) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>>
Python のリストを使用
4x4の正方行列を1次元配列で表して計算に使うことにします。Python のデータ型のうち、リストは C の配列 とほぼ同じものです。4x4行列の乗算を100万回実行して時間を計っています。
コード
#!/usr/bin/python # -*- coding: utf-8 -*- #------------------------------- # mulmatrix.py # 2012/02/02 - Jun Mizutani #------------------------------- import time def mulMatrix(mr, ma, mb): a0 =ma[ 0]; a1 =ma[ 1]; a2 =ma[ 2]; a3 =ma[ 3] a4 =ma[ 4]; a5 =ma[ 5]; a6 =ma[ 6]; a7 =ma[ 7] a8 =ma[ 8]; a9 =ma[ 9]; a10=ma[10]; a11=ma[11] a12=ma[12]; a13=ma[13]; a14=ma[14]; a15=ma[15] b0 =mb[ 0]; b1 =mb[ 1]; b2 =mb[ 2]; b3 =mb[ 3] b4 =mb[ 4]; b5 =mb[ 5]; b6 =mb[ 6]; b7 =mb[ 7] b8 =mb[ 8]; b9 =mb[ 9]; b10=mb[10]; b11=mb[11] b12=mb[12]; b13=mb[13]; b14=mb[14]; b15=mb[15] mr[ 0] = a0 * b0 + a4 * b1 + a8 * b2 + a12 * b3 mr[ 1] = a1 * b0 + a5 * b1 + a9 * b2 + a13 * b3 mr[ 2] = a2 * b0 + a6 * b1 + a10 * b2 + a14 * b3 mr[ 3] = a3 * b0 + a7 * b1 + a11 * b2 + a15 * b3 mr[ 4] = a0 * b4 + a4 * b5 + a8 * b6 + a12 * b7 mr[ 5] = a1 * b4 + a5 * b5 + a9 * b6 + a13 * b7 mr[ 6] = a2 * b4 + a6 * b5 + a10 * b6 + a14 * b7 mr[ 7] = a3 * b4 + a7 * b5 + a11 * b6 + a15 * b7 mr[ 8] = a0 * b8 + a4 * b9 + a8 * b10+ a12 * b11 mr[ 9] = a1 * b8 + a5 * b9 + a9 * b10+ a13 * b11 mr[10] = a2 * b8 + a6 * b9 + a10 * b10+ a14 * b11 mr[11] = a3 * b8 + a7 * b9 + a11 * b10+ a15 * b11 mr[12] = a0 * b12+ a4 * b13+ a8 * b14+ a12 * b15 mr[13] = a1 * b12+ a5 * b13+ a9 * b14+ a13 * b15 mr[14] = a2 * b12+ a6 * b13+ a10 * b14+ a14 * b15 mr[15] = a3 * b12+ a7 * b13+ a11 * b14+ a15 * b15 a = [ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14., 15.] b = [ 1., 2., 3., 4., 1., 2., 3., 4., 1., 2., 3., 4., 1., 2., 3., 4. ] c = [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.] start = time.time() for i in range(1000000): mulMatrix(c, b, a) print("%f sec\n" % (time.time() - start)) print(c)
実行結果 (Python 2.7.3)
$ time python mulmatrix.py 169.341166 sec [6.0, 12.0, 18.0, 24.0, 22.0, 44.0, 66.0, 88.0, 38.0, 76.0, 114.0, 152.0, 54.0, 108.0, 162.0, 216.0] real 2m49.667s user 2m48.620s sys 0m0.110s
実行結果 (Python 3.2.3)
$ time python3 mulmatrix.py 176.241442 sec [6.0, 12.0, 18.0, 24.0, 22.0, 44.0, 66.0, 88.0, 38.0, 76.0, 114.0, 152.0, 54.0, 108.0, 162.0, 216.0] real 2m57.384s user 2m57.050s sys 0m0.110s
他の言語と比べて遅いですが、「100万回の4x4行列の乗算に3分は本当に遅いのか?」という問題です。3次元のアプリケーションでは、物体が運動して、視点が移動する場合には少なくとも2回の乗算が必要になります。「100万 ÷ 180秒 ÷ 秒間60回 ÷ 乗算2回 = 46」なので、行列演算だけで独立した物体が46個が限界になります。 色々な最適化や、シェーダの使い方で工夫する余地はありますが、あと10倍ぐらいの速度が欲しい感じです。
NumPy を使用
Python用の高速数値計算モジュールである NumPy を使ってみました。
コード
#!/usr/bin/python # -*- coding: utf-8 -*- #------------------------------- # mulNumPy.py # 2012/02/02 - Jun Mizutani #------------------------------- import time import numpy as np a = np.array( [[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [ 12., 13., 14., 15.]]) b = np.array( [[ 1., 2., 3., 4.], [ 1., 2., 3., 4.], [ 1., 2., 3., 4.], [ 1., 2., 3., 4.]]) start = time.time() for i in range(1000000): c = np.dot(a, b) print("%f sec\n" % (time.time() - start)) print(c)
実行結果 (Python 2.7.3)
ench $ time python mulNumPy.py 46.930748 sec [[ 6. 12. 18. 24.] [ 22. 44. 66. 88.] [ 38. 76. 114. 152.] [ 54. 108. 162. 216.]] real 0m48.631s user 0m48.270s sys 0m0.230s
実行結果 (Python 3.2.3)
$ time python3 mulNumPy.py 55.821260 sec [[ 6. 12. 18. 24.] [ 22. 44. 66. 88.] [ 38. 76. 114. 152.] [ 54. 108. 162. 216.]] real 0m58.917s user 0m58.650s sys 0m0.170s
Pythonのリストを使った場合より4倍ほど速くなりますが、NumPyのロードに2秒ほどかかっている感じがします。
Lua
Lua は主にアプリケーションやゲームに組み込んで使われる (ゲームコーディング・コンプリート ) ことの多い、高速でコンパクトなスクリプト言語です。 最近のプログラミング言語の最大公約数というか、 MECE(ミーシー : mutually exclusive and collectively exhaustive) というか、機能的に必要とされる最小限を取り出したような言語です。Luaは言語仕様が小さく、組み込みの関数や標準ライブラリが多くないため、習得が容易な言語です。
コード
ここでは、Lua のテーブルというデータ型を使って行列を表します。テーブルは連想配列と普通の配列のどちらにも使えます。Lua単独では秒単位の時間計測しかできないので、コマンドの実行時間を計測する bash の組み込みコマンド time を使いました。
#!/usr/bin/lua -- ----------------------------- -- mulmatrix.lua -- 2012/02/02 - Jun Mizutani -- ----------------------------- function mulArrayMatrix(mr, ma, mb) local a0 =ma[ 1]; local a1 =ma[ 2]; local a2 =ma[ 3]; local a3 =ma[ 4] local a4 =ma[ 5]; local a5 =ma[ 6]; local a6 =ma[ 7]; local a7 =ma[ 8] local a8 =ma[ 9]; local a9 =ma[10]; local a10=ma[11]; local a11=ma[12] local a12=ma[13]; local a13=ma[14]; local a14=ma[15]; local a15=ma[16] local b0 =mb[ 1]; local b1 =mb[ 2]; local b2 =mb[ 3]; local b3 =mb[ 4] local b4 =mb[ 5]; local b5 =mb[ 6]; local b6 =mb[ 7]; local b7 =mb[ 8] local b8 =mb[ 9]; local b9 =mb[10]; local b10=mb[11]; local b11=mb[12] local b12=mb[13]; local b13=mb[14]; local b14=mb[15]; local b15=mb[16] mr[ 1] = a0 * b0 + a4 * b1 + a8 * b2 + a12 * b3 mr[ 2] = a1 * b0 + a5 * b1 + a9 * b2 + a13 * b3 mr[ 3] = a2 * b0 + a6 * b1 + a10 * b2 + a14 * b3 mr[ 4] = a3 * b0 + a7 * b1 + a11 * b2 + a15 * b3 mr[ 5] = a0 * b4 + a4 * b5 + a8 * b6 + a12 * b7 mr[ 6] = a1 * b4 + a5 * b5 + a9 * b6 + a13 * b7 mr[ 7] = a2 * b4 + a6 * b5 + a10 * b6 + a14 * b7 mr[ 8] = a3 * b4 + a7 * b5 + a11 * b6 + a15 * b7 mr[ 9] = a0 * b8 + a4 * b9 + a8 * b10+ a12 * b11 mr[10] = a1 * b8 + a5 * b9 + a9 * b10+ a13 * b11 mr[11] = a2 * b8 + a6 * b9 + a10 * b10+ a14 * b11 mr[12] = a3 * b8 + a7 * b9 + a11 * b10+ a15 * b11 mr[13] = a0 * b12+ a4 * b13+ a8 * b14+ a12 * b15 mr[14] = a1 * b12+ a5 * b13+ a9 * b14+ a13 * b15 mr[15] = a2 * b12+ a6 * b13+ a10 * b14+ a14 * b15 mr[16] = a3 * b12+ a7 * b13+ a11 * b14+ a15 * b15 end function printArrayMatrix4(ma) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[1], ma[5], ma[9], ma[13])) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[2], ma[6], ma[10], ma[14])) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[3], ma[7], ma[11], ma[15])) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[4], ma[8], ma[12], ma[16])) end local A = { 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4} local B = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} local C = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} for i=1,1000000 do mulArrayMatrix(C, A, B) end printArrayMatrix4(C)
実行
$ time lua mulmatrix.lua 6.00000e+00 2.20000e+01 3.80000e+01 5.40000e+01 1.20000e+01 4.40000e+01 7.60000e+01 1.08000e+02 1.80000e+01 6.60000e+01 1.14000e+02 1.62000e+02 2.40000e+01 8.80000e+01 1.52000e+02 2.16000e+02 real 0m21.940s user 0m21.900s sys 0m0.000s
LuaJIT
LuaJIT は Lua 5.1.5 に対応した JITコンパイラ処理系です。実行時にネイティブコードにコンパイルします。Raspbianのディスクイメージ(2012-12-16-wheezy-raspbian.zip)に入っているのは LuaJIT 2.0.0 直前の LuaJIT 2.0.0-beta11 (2012-10-16) で、初めてRaspberry Piが採用している armhf に対応したバージョンです。 インタプリタの手軽さとコンパイラの高速性を備えた非常に高速でコンパクトな処理系です。バイナリのサイズは365キロバイトでPythonの7分の1です。
LuaJIT には標準で FFI (Foreign Function Interface) モジュール という拡張機能を持っていて、その中にCのパーサを持っているため Cのライブラリの宣言(関数や構造体)をほとんどそのままコード中で使うことができます。実行時には共有ライブラリを /etc/ld.so.conf から検索してきて(たぶん)、LuaJITが実行しているコードにリンクできるようです。結果として Python の ctypes と同じことができます。
Luaのテーブルを使う
Luaのテーブルを1次元配列として使います。Luaのテーブルは添字が1から始まるところに注意が必要です。
コード
基本的に上の Luaで書いたコードと同じですが、ここでは内部でも gettimeofday を使って時間を計測しています。 これには FFI モジュールを使って C のライブラリの関数を直接使ってPython の time.time() と同じことを実現しています。
#!/usr/bin/luajit -- ----------------------------- -- mulmatrix1.lua -- 2012/02/02 - Jun Mizutani -- ----------------------------- ffi = require "ffi" ffi.cdef[[ typedef struct timeval { long tv_sec; long tv_usec; } timeval; int gettimeofday(struct timeval *tv, void *tz); ]] function gettimeofday() local t = ffi.new("timeval") ffi.C.gettimeofday(t, nil) return t.tv_sec, t.tv_usec end function mulArrayMatrix(mr, ma, mb) local a0 =ma[ 1]; local a1 =ma[ 2]; local a2 =ma[ 3]; local a3 =ma[ 4] local a4 =ma[ 5]; local a5 =ma[ 6]; local a6 =ma[ 7]; local a7 =ma[ 8] local a8 =ma[ 9]; local a9 =ma[10]; local a10=ma[11]; local a11=ma[12] local a12=ma[13]; local a13=ma[14]; local a14=ma[15]; local a15=ma[16] local b0 =mb[ 1]; local b1 =mb[ 2]; local b2 =mb[ 3]; local b3 =mb[ 4] local b4 =mb[ 5]; local b5 =mb[ 6]; local b6 =mb[ 7]; local b7 =mb[ 8] local b8 =mb[ 9]; local b9 =mb[10]; local b10=mb[11]; local b11=mb[12] local b12=mb[13]; local b13=mb[14]; local b14=mb[15]; local b15=mb[16] mr[ 1] = a0 * b0 + a4 * b1 + a8 * b2 + a12 * b3 mr[ 2] = a1 * b0 + a5 * b1 + a9 * b2 + a13 * b3 mr[ 3] = a2 * b0 + a6 * b1 + a10 * b2 + a14 * b3 mr[ 4] = a3 * b0 + a7 * b1 + a11 * b2 + a15 * b3 mr[ 5] = a0 * b4 + a4 * b5 + a8 * b6 + a12 * b7 mr[ 6] = a1 * b4 + a5 * b5 + a9 * b6 + a13 * b7 mr[ 7] = a2 * b4 + a6 * b5 + a10 * b6 + a14 * b7 mr[ 8] = a3 * b4 + a7 * b5 + a11 * b6 + a15 * b7 mr[ 9] = a0 * b8 + a4 * b9 + a8 * b10+ a12 * b11 mr[10] = a1 * b8 + a5 * b9 + a9 * b10+ a13 * b11 mr[11] = a2 * b8 + a6 * b9 + a10 * b10+ a14 * b11 mr[12] = a3 * b8 + a7 * b9 + a11 * b10+ a15 * b11 mr[13] = a0 * b12+ a4 * b13+ a8 * b14+ a12 * b15 mr[14] = a1 * b12+ a5 * b13+ a9 * b14+ a13 * b15 mr[15] = a2 * b12+ a6 * b13+ a10 * b14+ a14 * b15 mr[16] = a3 * b12+ a7 * b13+ a11 * b14+ a15 * b15 end function printArrayMatrix4(ma) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[1], ma[5], ma[9], ma[13])) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[2], ma[6], ma[10], ma[14])) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[3], ma[7], ma[11], ma[15])) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[4], ma[8], ma[12], ma[16])) end local A = { 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4} local B = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} local C = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } local start_sec, start_usec = gettimeofday() for i=1,1000000 do mulArrayMatrix(C, A, B) end local end_sec, end_usec = gettimeofday() local sec = end_sec - start_sec local usec = end_usec - start_usec if usec < 0 then usec = usec + 1000000 sec = sec - 1 end print(string.format("luajit + array : %f sec", sec+usec/1000000)) printArrayMatrix4(C)
実行
$ time luajit -joff mulmatrix1.lua luajit + array : 6.622208 sec 6.00000e+00 2.20000e+01 3.80000e+01 5.40000e+01 1.20000e+01 4.40000e+01 7.60000e+01 1.08000e+02 1.80000e+01 6.60000e+01 1.14000e+02 1.62000e+02 2.40000e+01 8.80000e+01 1.52000e+02 2.16000e+02 real 0m6.646s user 0m6.610s sys 0m0.010s $ time luajit mulmatrix1.lua luajit + array : 2.394701 sec 6.00000e+00 2.20000e+01 3.80000e+01 5.40000e+01 1.20000e+01 4.40000e+01 7.60000e+01 1.08000e+02 1.80000e+01 6.60000e+01 1.14000e+02 1.62000e+02 2.40000e+01 8.80000e+01 1.52000e+02 2.16000e+02 real 0m2.418s user 0m2.390s sys 0m0.010s
ffi で C の double型の配列を使う
LuaJIT の ffi はCのデータ構造をかなり自由に使うことができます。このバージョンでは行列を倍精度浮動小数点数の1次元配列として表しています。OpenGL ES2 のシェーダに渡す必要のある単精度浮動小数点数の配列も扱えます。
コード
Cの形式の倍精度浮動小数点数の配列なので、インデックス (添字) は 0 から始まります。
#!/usr/bin/luajit -- ----------------------------- -- mulmatrix2.lua -- 2012/02/02 - Jun Mizutani -- ----------------------------- ffi = require "ffi" ffi.cdef[[ typedef struct timeval { long tv_sec; long tv_usec; } timeval; int gettimeofday(struct timeval *tv, void *tz); ]] function gettimeofday() local t = ffi.new("timeval") ffi.C.gettimeofday(t, nil) return t.tv_sec, t.tv_usec end function mulMatrix(mr, ma, mb) local a0 =ma[ 0]; local a1 =ma[ 1]; local a2 =ma[ 2]; local a3 =ma[ 3] local a4 =ma[ 4]; local a5 =ma[ 5]; local a6 =ma[ 6]; local a7 =ma[ 7] local a8 =ma[ 8]; local a9 =ma[ 9]; local a10=ma[10]; local a11=ma[11] local a12=ma[12]; local a13=ma[13]; local a14=ma[14]; local a15=ma[15] local b0 =mb[ 0]; local b1 =mb[ 1]; local b2 =mb[ 2]; local b3 =mb[ 3] local b4 =mb[ 4]; local b5 =mb[ 5]; local b6 =mb[ 6]; local b7 =mb[ 7] local b8 =mb[ 8]; local b9 =mb[ 9]; local b10=mb[10]; local b11=mb[11] local b12=mb[12]; local b13=mb[13]; local b14=mb[14]; local b15=mb[15] mr[ 0] = a0 * b0 + a4 * b1 + a8 * b2 + a12 * b3 mr[ 1] = a1 * b0 + a5 * b1 + a9 * b2 + a13 * b3 mr[ 2] = a2 * b0 + a6 * b1 + a10 * b2 + a14 * b3 mr[ 3] = a3 * b0 + a7 * b1 + a11 * b2 + a15 * b3 mr[ 4] = a0 * b4 + a4 * b5 + a8 * b6 + a12 * b7 mr[ 5] = a1 * b4 + a5 * b5 + a9 * b6 + a13 * b7 mr[ 6] = a2 * b4 + a6 * b5 + a10 * b6 + a14 * b7 mr[ 7] = a3 * b4 + a7 * b5 + a11 * b6 + a15 * b7 mr[ 8] = a0 * b8 + a4 * b9 + a8 * b10+ a12 * b11 mr[ 9] = a1 * b8 + a5 * b9 + a9 * b10+ a13 * b11 mr[10] = a2 * b8 + a6 * b9 + a10 * b10+ a14 * b11 mr[11] = a3 * b8 + a7 * b9 + a11 * b10+ a15 * b11 mr[12] = a0 * b12+ a4 * b13+ a8 * b14+ a12 * b15 mr[13] = a1 * b12+ a5 * b13+ a9 * b14+ a13 * b15 mr[14] = a2 * b12+ a6 * b13+ a10 * b14+ a14 * b15 mr[15] = a3 * b12+ a7 * b13+ a11 * b14+ a15 * b15 end function printMatrix4(ma) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[0], ma[4], ma[8], ma[12])) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[1], ma[5], ma[9], ma[13])) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[2], ma[6], ma[10], ma[14])) print( string.format(" %10.5e %10.5e %10.5e %10.5e", ma[3], ma[7], ma[11], ma[15])) end local a = ffi.new("double[?]", 16, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4) local b = ffi.new("double[?]", 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) local c = ffi.new("double[?]", 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) local start_sec, start_usec = gettimeofday() for i=1,1000000 do mulMatrix(c, a, b) end local end_sec, end_usec = gettimeofday() local sec = end_sec - start_sec local usec = end_usec - start_usec if usec < 0 then usec = usec + 1000000 sec = sec - 1 end print(string.format("luajit + ffi : %f sec", sec+usec/1000000)) printMatrix4(c)
jit off 実行
$ time luajit -joff mulmatrix2.lua luajit + ffi : 57.263448 sec 6.00000e+00 2.20000e+01 3.80000e+01 5.40000e+01 1.20000e+01 4.40000e+01 7.60000e+01 1.08000e+02 1.80000e+01 6.60000e+01 1.14000e+02 1.62000e+02 2.40000e+01 8.80000e+01 1.52000e+02 2.16000e+02 real 0m57.287s user 0m57.180s sys 0m0.030s
jit on 実行
$ time luajit mulmatrix2.lua luajit + ffi : 1.343749 sec 6.00000e+00 2.20000e+01 3.80000e+01 5.40000e+01 1.20000e+01 4.40000e+01 7.60000e+01 1.08000e+02 1.80000e+01 6.60000e+01 1.14000e+02 1.62000e+02 2.40000e+01 8.80000e+01 1.52000e+02 2.16000e+02 real 0m1.368s user 0m1.340s sys 0m0.010s
C とアセンブリ
Raspberry Pi メモ (05) とほぼ同じコードですが、比較のためにここでもリストを載せておきます。
C の部分
アセンブリで書いたオブジェクトファイルをリンクしている以外は、他の言語と同じ事をしています。
コード
行列が配列ではなく、構造体にしています。
//-------------------------------------------------------- // Multiply 4x4 Matrix // 2013/02/02 Jun Mizutani // as -mcpu=arm1176jzf-s -mfpu=vfp mat4x4.s -o mat4x4asm.o // gcc -Wall -o mat4 mat4.c mat4x4asm.o // gcc -O2 -Wall -o mat4-O2 mat4.c mat4x4asm.o //-------------------------------------------------------- #include <stdio.h> #include <sys/time.h> typedef struct Matrix4 { double m0,m1,m2,m3, m4,m5,m6,m7, m8,m9,m10,m11, m12,m13,m14,m15; } Matrix4; Matrix4 a = { 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4 }; Matrix4 b = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; Matrix4 c = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; extern void MulMatrix4x4asm(Matrix4 *result, Matrix4 *a, Matrix4 *b); void MulMatrix4(Matrix4 *a, Matrix4 *b, Matrix4 *c) { c->m0 = a->m0 * b->m0 + a->m4 * b->m1 + a->m8 * b->m2 + a->m12 * b->m3; c->m1 = a->m1 * b->m0 + a->m5 * b->m1 + a->m9 * b->m2 + a->m13 * b->m3; c->m2 = a->m2 * b->m0 + a->m6 * b->m1 + a->m10* b->m2 + a->m14 * b->m3; c->m3 = a->m3 * b->m0 + a->m7 * b->m1 + a->m11* b->m2 + a->m15 * b->m3; c->m4 = a->m0 * b->m4 + a->m4 * b->m5 + a->m8 * b->m6 + a->m12 * b->m7; c->m5 = a->m1 * b->m4 + a->m5 * b->m5 + a->m9 * b->m6 + a->m13 * b->m7; c->m6 = a->m2 * b->m4 + a->m6 * b->m5 + a->m10* b->m6 + a->m14 * b->m7; c->m7 = a->m3 * b->m4 + a->m7 * b->m5 + a->m11* b->m6 + a->m15 * b->m7; c->m8 = a->m0 * b->m8 + a->m4 * b->m9 + a->m8 * b->m10+ a->m12 * b->m11; c->m9 = a->m1 * b->m8 + a->m5 * b->m9 + a->m9 * b->m10+ a->m13 * b->m11; c->m10= a->m2 * b->m8 + a->m6 * b->m9 + a->m10* b->m10+ a->m14 * b->m11; c->m11= a->m3 * b->m8 + a->m7 * b->m9 + a->m11* b->m10+ a->m15 * b->m11; c->m12= a->m0 * b->m12+ a->m4 * b->m13+ a->m8 * b->m14+ a->m12 * b->m15; c->m13= a->m1 * b->m12+ a->m5 * b->m13+ a->m9 * b->m14+ a->m13 * b->m15; c->m14= a->m2 * b->m12+ a->m6 * b->m13+ a->m10* b->m14+ a->m14 * b->m15; c->m15= a->m3 * b->m12+ a->m7 * b->m13+ a->m11* b->m14+ a->m15 * b->m15; } void PrintMatrix4(Matrix4 *a) { printf("%10.5e %10.5e %10.5e %10.5e \n", a->m0, a->m4, a->m8, a->m12); printf("%10.5e %10.5e %10.5e %10.5e \n", a->m1, a->m5, a->m9, a->m13); printf("%10.5e %10.5e %10.5e %10.5e \n", a->m2, a->m6, a->m10, a->m14); printf("%10.5e %10.5e %10.5e %10.5e \n\n", a->m3, a->m7, a->m11, a->m15); } int main(int argc, char* argv[]) { struct timeval start, now; struct timezone tzone; double elapsedTime = 0.0; int i; printf("start C version\n"); gettimeofday(&start, &tzone); for(i=0; i<1000000; i++) MulMatrix4(&a, &b, &c); gettimeofday(&now, &tzone); elapsedTime = (double)(now.tv_sec - start.tv_sec) + (double)(now.tv_usec - start.tv_usec)/1000000.0; printf("%4.6f sec\n", elapsedTime); PrintMatrix4(&c); printf("start ASM version\n"); gettimeofday(&start, &tzone); for(i=0; i<10000000; i++) MulMatrix4x4asm(&c, &a, &b); gettimeofday(&now, &tzone); elapsedTime = (double)(now.tv_sec - start.tv_sec) + (double)(now.tv_usec - start.tv_usec)/1000000.0; printf("%4.6f sec\n", elapsedTime); PrintMatrix4(&c); return 0; }
アセンブリ部分
VFPv2のベクトルモードを使って行列の乗算を行なっています。詳細は Raspberry Pi メモ (05) を参照して下さい。
コード
@---------------------------------------------------------- @ mat4x4.s 2012/08/19 Jun Mizutani @ as -mcpu=arm1176jzf-s -mfpu=vfp mat4x4.s -o mat4x4asm.o @ gcc -O2 -Wall -o mat4 mat4.c mat4x4asm.o @--------------------------------------------------------- .text .global MulMatrix4x4asm @ MulMatrix4x4asm(Matrix4 *result, Matrix4 *a, Matrix4 *b) @ [result] = [a] x [b] @ [ r0 ] = [r1] x [r2] .align 3 MulMatrix4x4asm: stmfd sp!, {r0-r3, lr} vpush {d0-d15} bl vector4_mode @ LEN=4: vector mode mov r3, r0 fldmiad r2!, {d0-d3} @ vec_e mov r0, r1 @ vec_a fldmiad r0!, {d8-d11} @ load a[0_3] fmuld d4, d8, d0 @ w[i] = a[i] * e0 fldmiad r0!, {d12-d15} @ load b[0-3] fmacd d4, d12, d1 @ w[i] = w[i] + b[i] * e1 fldmiad r0!, {d8-d11} @ load c[0-3] fmacd d4, d8, d2 @ w[i] = w[i] + c[i] * e2 fldmiad r0, {d12-d15} @ load d[0-3] fmacd d4, d12, d3 @ w[i] = w[i] + d[i] * e3 fstmiad r3!, {d4-d7} @ store w[i] fldmiad r2!, {d0-d3} @ vec_f mov r0, r1 @ vec_a fldmiad r0!, {d8-d11} fmuld d4, d8, d0 @ x[i] = a[i] * f0 fldmiad r0!, {d12-d15} fmacd d4, d12, d1 @ x[i] = x[i] + b[i] * f1 fldmiad r0!, {d8-d11} fmacd d4, d8, d2 @ x[i] = x[i] + c[i] * f2 fldmiad r0, {d12-d15} fmacd d4, d12, d3 @ x[i] = x[i] + d[i] * f3 fstmiad r3!, {d4-d7} @ store x[i] fldmiad r2!, {d0-d3} @ vec_g mov r0, r1 @ vec_a fldmiad r0!, {d8-d11} fmuld d4, d8, d0 @ y[i] = a[i] * g0 fldmiad r0!, {d12-d15} fmacd d4, d12, d1 @ y[i] = y[i] + b[i] * g1 fldmiad r0!, {d8-d11} fmacd d4, d8, d2 @ y[i] = y[i] + c[i] * g2 fldmiad r0, {d12-d15} fmacd d4, d12, d3 @ y[i] = y[i] + d[i] * g3 fstmiad r3!, {d4-d7} @ store y[i] fldmiad r2, {d0-d3} @ vec_h mov r0, r1 @ vec_a fldmiad r0!, {d8-d11} fmuld d4, d8, d0 @ z[i] = a[i] * h0 fldmiad r0!, {d12-d15} fmacd d4, d12, d1 @ z[i] = z[i] + b[i] * h1 fldmiad r0!, {d8-d11} fmacd d4, d8, d2 @ z[i] = z[i] + c[i] * h2 fldmiad r0, {d12-d15} fmacd d4, d12, d3 @ z[i] = z[i] + d[i] * h3 fstmiad r3, {d4-d7} bl scalar_mode @ LEN=1: scalar mode vpop {d0-d15} ldmfd sp!, {r0-r3, pc} @------------------------------------------------------------------------- @ set FPSTR STRIDE[21:20] and LEN[18:16] @------------------------------------------------------------------------- scalar_mode: stmfd sp!, {r2, lr} fmrx r2, FPSCR @ FPSCR --> r10 bic r2, r2, #0x00370000 @ clear (STRIDE=1 and LEN=1) fmxr FPSCR, r2 @ r10 --> FPSCR ldmfd sp!, {r2, pc}
ビルド
#!/bin/sh as -mcpu=arm1176jzf-s -mfpu=vfp mat4x4.s -o mat4x4asm.o gcc -O0 -Wall -o mulmat mat4.c mat4x4asm.o gcc -O2 -Wall -o mulmat-O2 mat4.c mat4x4asm.o
実行
jun@raspi512 ~/MatrixBench $ time ./build real 0m2.792s user 0m2.360s sys 0m0.240s jun@raspi512 ~/MatrixBench $ ./mulmat start C version 2.261465 sec 6.00000e+00 2.20000e+01 3.80000e+01 5.40000e+01 1.20000e+01 4.40000e+01 7.60000e+01 1.08000e+02 1.80000e+01 6.60000e+01 1.14000e+02 1.62000e+02 2.40000e+01 8.80000e+01 1.52000e+02 2.16000e+02 start ASM version 0.677662 sec 6.00000e+00 2.20000e+01 3.80000e+01 5.40000e+01 1.20000e+01 4.40000e+01 7.60000e+01 1.08000e+02 1.80000e+01 6.60000e+01 1.14000e+02 1.62000e+02 2.40000e+01 8.80000e+01 1.52000e+02 2.16000e+02 jun@raspi512 ~/MatrixBench $ ./mulmat-O2 start C version 0.662011 sec 6.00000e+00 2.20000e+01 3.80000e+01 5.40000e+01 1.20000e+01 4.40000e+01 7.60000e+01 1.08000e+02 1.80000e+01 6.60000e+01 1.14000e+02 1.62000e+02 2.40000e+01 8.80000e+01 1.52000e+02 2.16000e+02 start ASM version 0.628814 sec 6.00000e+00 2.20000e+01 3.80000e+01 5.40000e+01 1.20000e+01 4.40000e+01 7.60000e+01 1.08000e+02 1.80000e+01 6.60000e+01 1.14000e+02 1.62000e+02 2.40000e+01 8.80000e+01 1.52000e+02 2.16000e+02
Cもアセンブラも同じように最速です。Pythonと比べると300倍近い差になります。かなり頑張って書いたアセンブリのコードとLuaJITが2倍程度しか速くないのはショックです。逆に LuaJIT の速度を信じて安心して使ってもいいとも言えます。
ダウンロード
ここに掲載したコード(MatrixBench.tar.gz 9KB)を置いておきます。
$ tar ztvvf MatrixBench.tar.gz drwxr-xr-x jun/jun 0 2013-02-02 16:58 MatrixBench/ -rw-r--r-- jun/jun 917 2013-02-02 16:57 MatrixBench/mat4x4asm.o -rwxr-xr-x jun/jun 156 2013-02-02 14:48 MatrixBench/build -rw-r--r-- jun/jun 3674 2013-02-02 06:06 MatrixBench/mat4x4.s -rwxr-xr-x jun/jun 2975 2013-02-02 12:36 MatrixBench/mulmatrix2.lua -rwxr--r-- jun/jun 1817 2013-02-02 15:02 MatrixBench/mulmatrix.py -rwxr-xr-x jun/jun 7357 2013-02-02 16:57 MatrixBench/mulmat-O2 -rwxr--r-- jun/jun 3149 2013-02-02 13:09 MatrixBench/mat4.c -rwxr-xr-x jun/jun 2269 2013-02-02 12:23 MatrixBench/mulmatrix.lua -rwxr--r-- jun/jun 561 2013-02-02 14:43 MatrixBench/mulNumPy.py -rwxr-xr-x jun/jun 8749 2013-02-02 16:57 MatrixBench/mulmat -rwxr-xr-x jun/jun 2853 2013-02-02 12:35 MatrixBench/mulmatrix1.lua