フリーソフトウェアとそれを取巻く世界の状況 (2007)

ツイッターで「オープンソース」についてオープンソースに自由という概念を付け加えるべきである、みたいなことをいっているのを読んだ。1998年にフリーソフトウェアという言葉を忌避するため、会議で「オープンソース」という言葉を作った人たちがいる。自由を抜いたオープンソースという言葉に、自由を加えよという人が出てくるぐらい、22年という時間は長いということなのだろう。10年ひと昔というが「ふたむかし」であるし、1年を7年に数えるドックイヤーでは「154年」である。確かにもう遠いむかしだ。

これもむかしの話であるが、2007年に大阪市立大学大学院創造都市研究科ワークショップにおいて「フリーソフトウェアとそれを取巻く世界の状況」というタイトルで講演したことがあり、その中でフリーソフトウェアという立場からいかにオープンソースという言葉がいかに堕落しているかを示した。その時のスライドの公開版を作ったのでpdf化して載せておく。当時使っていたフォントと誤字脱字は修正してある。

Pythonのnumpyライブラリは思っていたより高速だった

画像を処理しようとR-G-Bの各々の値をいじくろうとして、古典的にimage[i][j]を書き換えていたらメッチャ遅かった。こういう時こそ行列でまるごと計算できるnumpyの登場だよな、と思って処理したらメッチャ速かった。どの程度速くなるかは使っているハードウェア依存だと思うけれど、とりあえず、どれくらい速いか試してみる価値はある。ちなみに下のテストコードをpython3.7.3 + Intel Core i7-6500U CPU @ 2.50GHzの組み合わせで動かすと800から1000倍程度速かった。

#!/bin/env python
import numpy as np
import time
hw=(720,1280)
ma = np.full_like(np.zeros(hw, np.int16), 64, dtype=np.int16)
mb = np.full_like(np.zeros(hw, np.int16), 128, dtype=np.int16)
t0 = time.time()
mc = ma + mb
t1 = time.time()
t2 = t1 - t0
print("ma + mb: "+ str(int(t2*100000)/100) + "ms")
t0 = time.time()
for i in range(hw[0]):
    for j in range(hw[1]):
        mc[i][j] = ma[i][j] + mb[i][j]
t1 = time.time()
t3 = t1 - t0
print("ma[i][j] + mb[i][j]: "+ str(int(t3*100000)/100) + "ms")
print("np is " + str(int(t3/t2)) + " times faster than loop calc.")

Raspberry Pi 3 B+ & Raspberry Pi OS & OpenCV 4.2.0

システムに最適化してOpenCV 4.2.0の環境を構築する

Raspberry Pi 3 B+に Raspberry Pi OS (旧名 Raspbian ) を載せて OpenCV 4.2.0 をNEONとVFPV3を有効にしてコンパイルした時のcmakeのオプションと、ちょっとしたノウハウを書いておく。全体のコンパイルのやり方はあちこちに書いてあるので、そっちを参照して欲しい。

拡張命令

Raspberry Pi 3 にはSoC(CPUチップ)にARMv7が使われていて、拡張命令・拡張機能が用意されており、それを使えばCPUのみで処理するよりも速くなる。

  • モデル名 ARMv7 Processor rev 4 (v7l)
  • 拡張命令・拡張機能 half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32

OpenCV 4.2.0では cmakeの引数でFAST_MATHと NEONと VFPV3 をONにすることで、これらの拡張機能が使えるようになる。

  • VFP (Vector Floating Point) はARMアーキテクチャのコプロセッサ拡張で、VPFv3では半精度、単精度、倍精度の浮動小数点演算が計算できる。
  • NEONとはSIMD and Floating-point命令の名称で、こちらも処理の高速化に寄与する。

cmakeの引数

cmake -D CMAKE_BUILD_TYPE=RELEASE \
 -D CMAKE_INSTALL_PREFIX=/usr/local \
 -D INSTALL_C_EXAMPLES=OFF \
 -D INSTALL_PYTHON_EXAMPLES=ON \
 -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules \
 -D BUILD_EXAMPLES=OFF \
 -D BUILD_TEST=OFF \
 -D ENABLE_FAST_MATH=ON \
 -D ENABLE_NEON=ON \
 -D ENAVLE_VFPV3=ON ..

結果

CPUのみのものと、拡張命令を使ったものを2つ作って比較したところ、顔検出、両目検出などにおいては、拡張命令を使ったものは平均で半分程度の処理時間で終わった。

コンパイル時についてのTips

makeを使ってのコンパイルでは、最初の方は軽い処理からは始まり、最後の方はライブラリ全体を最適化するような重い処理がいくつも発生する。この時、仮想記憶容量がデフォルトのままでは足りなくなるので、スワップサイズを1024KBにしておく必要がある。

makeの後半にあるpythonのcv2ライブラリを作る段階では大量にメモリを消費する。この時、実メモリを1GBしかもたないRaspberry Pi 3では、当然ながら、ページイン・ページアウトによるスラッシングが発生し、ロードアベレージが極端に上昇する。少しでもメモリに余裕が欲しいため、ディスプレイを接続せずにブートしGUIに使うメモリ量を節約した。作業はssh経由でログインして行った。

microSDカードやUSBメモリ上でコンパイルせずに、nfsを使いファイルサーバー(M.2 SSD)に接続し、その上にソースコードを展開し、コンパイルした。ファイルサーバーとなったGNU/Linux上でネットワークのトラフィックを確認していたが30MB/sec程度のスループットが確保出来ていた。

makeは-jオプションを使ってコンパイルを並列化することが可能であり、4コアを持つarmv7lなので、4コアをフルに使うことが有効といわれている。しかし、現実にはコンパイル以外のシステムも同時に動いているので、すべてのコアをコンパイルに充てると、トータルでのシステムのスループットが悪くなってしまう。そのため3コアのみをコンパイルに使用し、1コアをシステムに割り当てた。

並列化の最大の問題点はRaspberry Pi 3の実メモリは1GBと余裕がなく、それを複数のコンパイル・プロセスが奪い合うことである。小さいサイズのモジュールをコンパイルしている時は良いが、後半になり大きなモジュールをコンパイルする際は、頻繁にページイン・ページアウトを繰り返すようになる。また、コンパイル時に大量のメモリが必要なケースはランダムに発生するわけではなく、後半に連続して発生する。スラッシングは極端にスループットを落とす。そこで、定期的にスワップ領域の利用状況をみて、長期間に渡りスワップ領域が逼迫している時は、makeを一旦killし、シングルタスクのmakeに戻して処理し、スワップ領域が使われなくなったらば、また並列処理オプションをつけたmakeに戻すといったことをした。比較をしていないので、どの程度コンパイル時間が短くなったかはわからないが、理屈としてはページイン・ページアウトのスラッシングを避けているので、トータルで効率がよくなっているはずである。