IOPSを調整してOpenCVを早く動かす

記事タイトルとURLをコピーする

ナショナル・ジオグラフィックでナマズの生態を無料で勉強できます。そう、Amazon Prime会員ならね。技術一課でOJT中の鎌田です。ダイオウイカの生態は、あまり人気がないようです。
最近、自宅でOpenCVを動かしています。OpenCVとは、Intelによって開発された、画像認識に関連するライブラリです。python,C++などの言語に対応しています。こちらの公式HPの下部に記載されているソースコードを利用すれば、簡単に写真のどこに人の顔が写っているか、検知することができます。
http://docs.opencv.org/trunk/d7/d8b/tutorial_py_face_detection.html
また、あらかじめ用意されたパターン以外にも、検出器を自作することで様々な物体を検知できるようになります。
さて、計算性能においてコストパフォーマンスの良いc4.largeで、Local Binary Pettern法を用いて検出器を作成していました。目標は、30秒以内に検出器の生成が終わることでした。しかし、私の環境では思うように速く結果が出ませんでした。なぜでしょうか。以下、私が行ったOpenCVの操作を記載します。IOPSについて読みたい方はお手数ですが、読み飛ばしてください。

実行したこと

まず、写真を撮ります。
撮った写真を、AWS上で動かしている、Ubuntuを搭載した暇なインスタンスに入れます。 写真は、以下のコマンドでインスタンスに直接送るか(security groupで事前にssh接続を許可しておく必要があります)、s3へアップロードしたものをAWS CLIでダウンロードします。

scp -i path/key.pem filename ubuntu@PublicIP or EIP
aws s3 cp s3://bucketname/object .

OpenCVのUbuntuへのインストール手順はこちら。
http://docs.opencv.org/3.1.0/d7/d9f/tutorial_linux_install.html
尚、マルチスレッド演算のためには、cmakeの際にoptionに -D WITH_TBB=ON を加えてTBBを有効にする必要があります。
例:

cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D WITH_TBB=ON -D BUILD_PYTHON_SUPPORT=ON

画像データは「ピンポン玉」を学習するため、2400枚の不正解画像、3000枚の正解画像を用意しました。正解画像は、一枚の写真を下記のコードで3000枚複製し、

opencv_createsamples -img ./pos/true.jpg -vec ./vec/image.vec -num 3000 -bgcolor 255 -maxidev 40 -maxxangle 0.8 -maxyangle 0.8 -maxzangle 0.5

不正解画像は下記のレポジトリよりお借りしました。
https://github.com/JoakimSoderberg/haarcascade-negatives
また、UbuntuにvncserverなどのGUIを入れておくと、モニタリングや画像処理結果がすぐに確認できるので、便利です。下記リンクを参考に、ピンポン玉を覚えさせるためのコードを実行します。
http://source.hatenadiary.jp/entry/2016/05/17/113928

IOPS

実行時のプロセスをモニタリングした結果、画像の読み込みに、時間がかかっているようでした。EBSには、IOPSという値が設定されています。IOPSは一秒あたりのEBS(SSD)の読み書き単位であり、1IOPSあたり上限が256KBとなっています。
通常、gp2(汎用SSD) では、この値は容量1GBにつき、3IOPSが付与されます。50GBのgp2タイプを利用している場合、150IOPSのベースIOPSが付与されていることになります。150IOPS、つまり、1秒につき150回EBSへ読み書きができます。さらに、gp2タイプは、ベースIOPSを超えたI/Oが発生した場合は際はクレジットを消費して最大で3000IOPSまでバーストすることができます。このクレジットを使い切ると、ベースIOPSの値でI/Oが行われるようになります。
このIOPSの値を調整することで、処理速度が改善するかどうかを調べるために、実験をしてみました。

実験

画像は先ほどのものを利用します。アプリケーションがCPUの問題で遅延しないように、三台のc4.2xlargeインスタンスをローンチします。gp2ではクレジットの残数が確認できず、現在のIOPSの値を把握できないため、それぞれにprovisioned IOPS 150 60GB、provisioned IOPS 100 60GB、provisioned IOPS 3000 60GBのEBSをアタッチしています。同じAMIから作成し、IOPS以外の条件は全て同じになるようにしています。以下のコマンドをそれぞれのインスタンスで実行し、Local Binary Pattern法で学習をさせ、画像検出器(カスケード)を生成します。

date;opencv_traincascade -data ./cascade/trained_data/ -vec ./vec/image.vec -bg nglist.txt -numPos 2400 -numNeg 2400 -numStages 10 -numThreads 4 -featureType LBP -minHitRate 0.999 -maxFalseAlarmRate 0.5;date

カスケード生成コマンドの先頭と末尾にdateをかませることによって、実行開始時間と実行終了時間を取得します。実行完了までにかかった時間を対照してみました。実行するたびに、生成されたカスケードファイルは消去しています。二回目以降の実行結果がどれも15秒になるのは、画像データがキャッシュされることが原因です。(echo 1 > /proc/sys/vm/drop_cachesを実行し、キャッシュを解放した後に再度実行すると、無事(?)初回と同じくらいカスケード生成に時間がかかります)

実験結果

iops 100
一回目:実行完了まで 44秒
Sun Oct 2 06:00:29 UTC 2016
Sun Oct 2 06:01:13 UTC 2016

二回目:実行完了まで 15秒
Sun Oct 2 06:05:44 UTC 2016
Sun Oct 2 06:05:59 UTC 2016

iops 150
一回目:実行完了まで 33秒
Sun Oct 2 06:00:27 UTC 2016
Sun Oct 2 06:01:00 UTC 2016
二回目:実行完了まで 15秒
Sun Oct 2 06:05:47 UTC 2016
Sun Oct 2 06:06:02 UTC 2016

iops 3000
一回目:実行完了まで 17秒
Sun Oct 2 06:00:25 UTC 2016
Sun Oct 2 06:00:42 UTC 2016
二回目:実行完了まで 15秒
Sun Oct 2 06:05:45 UTC 2016
Sun Oct 2 06:06:10 UTC 2016

考察

カスケードの生成は、大量の画像データの読み書きが行われるため、IOPSの値が大きく実行時間に影響するようです。それぞれのEBSのメトリクスを確認したところ、最大で1分間あたり10万回程度のEBSヘの読み書き動作が行われていました。IOPSの値が増え、一秒あたりにアクセス可能な回数が増えるほど読み書き動作の回数は膨れ上がっています。今回のケースは、高度な計算が要求されていないので、ボトルネックとなりうるのはEBSの読み書き速度でした。なので、IOPSを上げるほど、処理が早くなりました。

まとめ

今回、IOPSの値がデータの読み書きが多いアプリケーションの動作に大きく影響していることがわかりました。チューニングの際には、CloudWatchの詳細メトリクスを有効にし、アプリケーションのピーク時のワークロードをチェックすると、より適切な値が設定できます。
Provisioned IOPSの場合は、VolumeConsumedReadWriteOps という値をみると、期間内の読み書き操作の合計数を確認できます。
gp2の場合は、VolumeReadOpsの値とVolumeWriteOpsの値を足し合わせることで期間内の読み書き操作の合計数を確認できます。

尚、その他に考慮する点として、EBSとインスタンスの間の帯域が最適されているか否か、もチェックするべきポイントです。今回利用したc系のインスタンスは作成時に自動で帯域最適化オプションにチェックが入っていましたが、そうではない場合はローンチの際にチェックした方が良い結果が得られます。

Amazon prime素晴らしい

改めてG.I.ジョーみましたが、面白いですね。個人的にはマッド・マックスくらい面白いです。
次回ブログを書くまでに、ジェイソン・ステイサムが出てる映画はコンプリートするという、誓いを立てようと思います。