NVIDIA Jetson TX2を買ったのでセットアップ
届きました!
意外と大きいです。一辺20cmくらいでしょうか。 ディスプレイ(HDMI)、キーボード、マウス、電源を接続します。USBポートは一基しかないので、キーボードとマウスを両方接続する場合は USBハブを使うか、付属のMicroUSB-USB変換ケーブルを使います。
電源ボタンを押すとUbuntu 16.04のコンソールが開きます。
まずは以下のコマンドでドライバをインストールします。
(sudo時のパスワードはnvidia
)
$ cd NVIDIA-INSTALLER $ sudo ./installer.sh $ sudo reboot
再起動するとGUIが立ち上がります。 Wi-Fiもしくは有線でネットワークの設定を行っておきます。 (後で使うので、割り振られたIPアドレスを確認しておきます。)
ここから、JetPackのインストールです。
JetPackとは、CUDAやcuDNNを始め、Jetsonを使うのに必要そうなソフトウェアをまとめたものっぽいです。 筆者はMacbook Proを使っているので、VirtualBox上のUbuntu 16.04をホストとしてJetPackをインストールすることにしました。
こちらからUbuntu 16.04のイメージをダウンロードして、 VirtualBoxでVMを作ります。JetPackのインストールに最低10GBのディスクスペースが必要とのことなのですが、 ディスクサイズは余裕を持って30GBに設定しました。
(2018/05/03追記) VirtualBox経由だとセットアップがうまくいかないケースがあります。(JetsonのOSを書き換える場合?) 直接UbuntuをインストールしたPCがあれば、そちらを使うことを推奨します。
VMを起動してこちらからJetPackをダウンロードします。 NVIDIA Developer Programへの登録が必要になります。ダウンロードしたら以下のコマンドで実行。
$ chmod +x JetPack-L4T-3.1-linux-x64.run $ ./JetPack-L4T-3.1-linux-x64.run
途中の"Component Manager"画面でFlash OS Image to Target
のActionをno action
に変更します。
各種パッケージをインストールした後、
"Device Information - Jetson TX2"画面でJetsonのIPアドレスとユーザ名、パスワード(共にnvidia
)を入力します。
新しいウィンドウが開き、Jetson側にいろいろインストールされていき、
Installation of target components finished, close this window to continue.
と表示されたら終わりです。
/home/nvidia/NVIDIA_CUDA-8.0_Samples
にCUDAのサンプルが入っているので、
これがうまく動けば少なくともCUDAはちゃんと入っているはずです。
ちなみに、JetsonではNVIDIAのGPUでおなじみのnvidia-smi
コマンドは対応していないみたいです。
代わりにホームディレクトリにあるtegrastats
でGPUの状態を確認します。
nvidia@tegra-ubuntu:~$ sudo ./tegrastats RAM 1103/7851MB (lfb 1501x4MB) cpu [0%@1728,off,off,0%@1728,0%@1727,0%@1728] EMC 0%@1600 APE 150 GR3D 0%@114 RAM 1103/7851MB (lfb 1501x4MB) cpu [0%@345,off,off,1%@345,0%@346,5%@345] EMC 8%@40 APE 150 GR3D 0%@114 RAM 1103/7851MB (lfb 1501x4MB) cpu [1%@345,off,off,1%@345,0%@345,5%@345] EMC 8%@40 APE 150 GR3D 0%@114 RAM 1103/7851MB (lfb 1501x4MB) cpu [1%@345,off,off,2%@345,0%@345,4%@339] EMC 8%@40 APE 150 GR3D 0%@114
ZeroMQ: 複数のEndpointに接続する
ちゃんとドキュメントを読めという話かもしれないが、ZeroMQ のソケットが 複数のEndpointにbind/connectできることを最近知った。そこで、複数のEndpointに対してのbind/connectを 使ったメッセージングのパターンをいくつか考えてみた。
1. ローカル/リモート共用ソケット
同じプロセス内でのソケット間通信をするなら、inproc
を使ったほうが
ローカルループバックに対するtcp
を使うよりも圧倒的に速い。このパターンでは、
一つのソケットに同一プロセスからの接続用のアドレス(ローカル: inproc
)と、
別マシンからの接続用のアドレス(リモート: tcp
)の二つをbindし、接続するソケットの位置
によってconnectするアドレスを使い分ける。これにより、ローカルのソケット間通信はより高速に行われ、また、
受信する側では、ローカルから来たメッセージとリモートから来たメッセージを区別することなく処理を行うことができる。
2. ブロードキャストクラスタ
各ノードでZMQ_PUB
とZMQ_SUB
を一つずつ用意します。
ZMQ_PUB
はbindしてZMQ_SUB
は各ノードのZMQ_PUB
にconnectすると(逆でもたぶんOK)、
ZMQ_PUB
にsendすることで他の全ノードにメッセージが送信されるようになります。
複数のソケットを用意すれば同じことが実現できますが、一つのソケットで複数のEndpointにconnectした方が、
recv/pollするのが一つのソケットで済むので楽になるかと思います。
このパターンを実装するときはZMQ_SUBSCRIBE
を設定するのを忘れずに。
3. P2Pクラスタ
bindとconnectを併用することもできます。このパターンでは、各ノードにZMQ_ROUTER
を一つ用意し、
一つのアドレスに対してbindするのと同時に、他のノードのZMQ_ROUTER
にconnectします。
このように繋ぐことで、ZMQ_ROUTER
のルーティング機能を使えばクラスタ内のどのノードへも
メッセージを送信することができます。ZMQ_ROUTER
同士を繋ぐときはidentityの設定が厄介ですが、
bindする前にZMQ_IDENTITY
を設定し、connectする際にZMQ_CONNECT_RID
を設定しておけば問題ないでしょう。
以上、複数のEndpointへのbind/connectを用いたメッセージングパターンでした。
通信時のソースアドレスを取得する
※Linuxです。
なんて言えばよいのかわからなかったのですが、ip route get xxx.xxx.xxx.xxx
と打ったときに出てくる
ソース(送信元)アドレスをC/C++内で取得したかった。すぐに思いついた方法は、
popen
を使ってip route get
を実行し、標準出力を取得- 一時的にソケットを作って送信元IPアドレスを取得
です。以下のようなサンプルコードを書きました。
どちらも同じ出力が得られますが、手元の環境で試したところ、
1000回実行するのにかかる時間がpopen
を使った方法では約1秒、
ソケットを使った方法では約0.005秒だった。(コードの改善点はあるかもしれませんが。)
他にもっと良い方法があれば教えてください!
ソケットに結び付けられたアドレスを取得する
※Linuxです。
netstat
したときのLocal AddressとForeign AddressにあたるものをC/C++で取得したかった。
Local Addressはgetsockname()
、Foreign Addressはgetpeername()
を使えば取得できるらしい。
C++でサンプルを書くと以下のような感じ。
gist6c0628fe0ad9790fe7a1fbc5cba9b13c
vagrant@vagrant-ubuntu-trusty-64:~$ ./a.out 192.168.0.2 2551 Local Address: 192.168.0.1:46402 Foreign Address: 192.168.0.2:2551
ちゃんと取得できてますね!(↑のサンプルコードを使うときは対応するアドレスをlistenしてください)
ZeroMQのheartbeatを試してみた
ZeroMQのversion 4.2からheartbeat機能が追加されました。 元々コネクションの切断は検知してzmq_socket_monitor(3)で通知してくれたのですが、 TCPエラーを吐かずにコネクションが死んだり、Out of Memory等でプロセスが止まったりした場合は検知できないので heartbeat機能が追加されたようです。(参考:ZeroMQのRFC)
heartbeatに関するソケットのオプションは以下の3つです。
ZMQ_HEARTBEAT_IVL
ZMQ_HEARTBEAT_TIMEOUT
ZMQ_HEARTBEAT_TTL
ZMQ_HEARTBEAT_IVL
ミリ秒の間隔でpingを送り、ZMQ_HEARTBEAT_TIMEOUT
ミリ秒経っても
pongが返ってこなかったらコネクションが死んだとみなすという感じです。また、pongを返す側では
ZMQ_HEARTBEAT_TTL
ミリ秒経っても次のpingが来なかったらコネクションが死んだとみなします。
ZMQ_HEARTBEAT_IVL
を設定することで初めてheartbeatが動作します。
試しに以下のような設定をしてみます。基本的にどちらか片方(connect側もしくはbind側)のソケットにだけ設定しておけばOKだと思います。
int interval = 100, timeout = 1000, ttl = 1000; zmq_setsockopt(sock, ZMQ_HEARTBEAT_IVL, &interval, sizeof(int)); zmq_setsockopt(sock, ZMQ_HEARTBEAT_TIMEOUT, &timeout, sizeof(int)); zmq_setsockopt(sock, ZMQ_HEARTBEAT_TTL, &ttl, sizeof(int)); zmq_setsockopt(sock, ZMQ_HANDSHAKE_IVL, &timeout, sizeof(int));
ZMQ_HEARTBEAT_TTL
はZMQ_HEARTBEAT_IVL
よりも十分大きい値にしておく必要があります。
そうしないと接続が頻繁に切れてしまいます。ZMQ_HEARTBEAT_TIMEOUT
も短すぎると
偽陽性が増えてしまうので注意です。
また、ZMQ_HANDSHAKE_IVL
を設定しています。ZeroMQではソケットが接続した後に
ソケットの設定情報を交換する(これをhandshakeと呼ぶ)そうなのですが、止まっているソケットに再接続した際に
ZMQ_HEARTBEAT_TIMEOUT
よりもZMQ_HANDSHAKE_IVL
が優先されるため、両方共同じ値にしておきます。
ZMQ_HANDSHAKE_IVL
のデフォルト値は30秒なので、これを設定しないと
ZMQ_HEARTBEAT_TIMEOUT
をどれだけ短くしても再接続時に30秒待ってしまいます。
実際にこんな感じのコードを書いて動かします。
bind/connectが完了した時点で、connect側のプロセスにSIGTSTPを送るとbind側のzmq_socket_monitor(3)
から
ZMQ_EVENT_DISCONNECTED
が通知されます。逆に、bind側のプロセスにSIGTSTPを送ると、connect側の
zmq_socket_monitor(3)
からZMQ_EVENT_DISCONNECTED
が通知された後に、再接続を試みます。
bind側のlistenが残っているからか、再接続は成功しますが、handshakeが失敗して再びZMQ_EVENT_DISCONNECTED
が
通知されるというような挙動になります。
また、heartbeatを有効にしても前回の調査同様、unbind/disconnect時には 相手側のソケットには何も通知されません。disconnectしても再接続しようとするし、unbind/disconnectとは何なのだろう…。 とりあえずソケットを切断するときは必ずcloseした方が良さそうです。
※ZeroMQ v4.2.1を使用
ZeroMQのzmq_socket_monitor(3)の挙動を調べた
ZeroMQのzmq_socket_monitor(3)
の挙動を調べてみました。
bindするソケットとconnectするソケットを用意し、以下の順に処理して
zmq_socket_monitor(3)
でどのようなイベントが通知されるか確認しました。
- create (bind/connect socket)
- bind
- connect
- disconnect
- close (connect socket)
- create (connect socket)
- connect
- unbind
- close (bind socket)
上記の処理を1秒間隔で行い、イベントの出力を確認します。 使用したソースコードはこちらに載せました。
bind側の出力
vagrant@vagrant-ubuntu-trusty-64:~$ ./a.out bind tcp://0.0.0.0:2551 [0.000] [main] Create... [1.000] [main] Bind... [1.010] [event] number: 8, value: 12, address: tcp://0.0.0.0:2551 [2.011] [event] number: 32, value: 13, address: tcp://0.0.0.0:2551 [4.014] [event] number: 512, value: 13, address: tcp://0.0.0.0:2551 [6.019] [event] number: 32, value: 13, address: tcp://0.0.0.0:2551 [7.001] [main] Unbind... [7.009] [event] number: 128, value: 12, address: tcp://0.0.0.0:2551 [8.003] [main] Close... [8.004] [event] number: 1024, value: 0, address: [9.004] [main] Finish...
connect側の出力
vagrant@vagrant-ubuntu-trusty-64:~$ ./a.out connect tcp://192.168.0.1:2551 [0.000] [main] Create... [2.000] [main] Connect... [2.003] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551 [2.004] [event] number: 1, value: 12, address: tcp://192.168.0.1:2551 [3.002] [main] Disconnect... [4.005] [main] Close... [4.006] [event] number: 1024, value: 0, address: [5.012] [main] Create... [6.014] [main] Connect... [6.015] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551 [6.016] [event] number: 1, value: 11, address: tcp://192.168.0.1:2551 [8.013] [event] number: 512, value: 11, address: tcp://192.168.0.1:2551 [8.014] [event] number: 4, value: 183, address: tcp://192.168.0.1:2551 [8.196] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551 [8.197] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551 [8.197] [event] number: 4, value: 125, address: tcp://192.168.0.1:2551 [8.316] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551 [8.317] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551 [8.317] [event] number: 4, value: 141, address: tcp://192.168.0.1:2551 [8.466] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551 [8.466] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551 [8.467] [event] number: 4, value: 186, address: tcp://192.168.0.1:2551 [8.652] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551 [8.653] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551 [8.654] [event] number: 4, value: 197, address: tcp://192.168.0.1:2551 [8.848] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551 [8.849] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551 [8.849] [event] number: 4, value: 110, address: tcp://192.168.0.1:2551 [8.957] [event] number: 2, value: 115, address: tcp://192.168.0.1:2551 [8.958] [event] number: 128, value: 11, address: tcp://192.168.0.1:2551 [8.959] [event] number: 4, value: 138, address: tcp://192.168.0.1:2551 [9.015] [main] Close... [9.015] [main] Finish...
一番左側の列は経過時間を表しています。 numberはZeroMQで定義されているイベント番号で、以下のようになっています。
/* Socket transport events (tcp and ipc only) */ #define ZMQ_EVENT_CONNECTED 1 #define ZMQ_EVENT_CONNECT_DELAYED 2 #define ZMQ_EVENT_CONNECT_RETRIED 4 #define ZMQ_EVENT_LISTENING 8 #define ZMQ_EVENT_BIND_FAILED 16 #define ZMQ_EVENT_ACCEPTED 32 #define ZMQ_EVENT_ACCEPT_FAILED 64 #define ZMQ_EVENT_CLOSED 128 #define ZMQ_EVENT_CLOSE_FAILED 256 #define ZMQ_EVENT_DISCONNECTED 512 #define ZMQ_EVENT_MONITOR_STOPPED 1024
この結果からわかることは、
- bindした直後にbind側で
ZMQ_EVENT_LISTENING
が通知される - connectした直後にbind側で
ZMQ_EVENT_ACCEPTED
、connect側でZMQ_EVENT_CONNECT_DELAYED
、ZMQ_EVENT_CONNECTED
が通知される - disconnectしてもbind側、connect側共に何も通知されない
- unbindするとbind側には
ZMQ_EVENT_CLOSED
が通知されるが、connect側には何も通知されない - connect側をcloseするとbind側に
ZMQ_EVENT_DISCONNECTED
が通知される - bind側をcloseするとconnect側に
ZMQ_EVENT_DISCONNECTED
が通知され、connect側は再接続を試みる - connect側が再接続を試みている間は、
ZMQ_EVENT_CLOSED
、ZMQ_EVENT_CONNECT_RETRIED
、ZMQ_EVENT_CONNECT_DELAYED
が通知され続ける - ソケットをcloseすると、
ZMQ_EVENT_MONITOR_STOPPED
が通知される
ちなみに、プロセスを強制的に落とした場合はcloseしたときと同じ挙動になり、
ZMQ_EVENT_DISCONNECTED
が通知されます。
今回は通知するイベントをZMQ_EVENT_ALL
にしましたが、
実際に使うときには適切なイベントを選択して通知した方が良いかと思います。
※ZeroMQ v4.2.1を使用