先日、gRPCでC#とPythonを連携する方法について書いたが、Pythonで受信したProtobufのデータを処理すると実行速度に問題があることがわかった。
速度が必要なケースでは、Protobufの使用はあきらめた方がよさそうだ。
今回は、シンプルにSocket通信でデータを渡してPythonで処理する方法について記述する。
Socketでデータを送受信する場合、データの中身はただのバイナリになるため、構造化されたデータを送受信する場合は、データのシリアライズとデシリアライズの処理を記述する必要がある。
データが固定長であれば、処理しやすいが、可変長の場合は、送受信のデータにサイズを含めるなどの処理が必要になる。
C#側(クライアント)
サンプルコード:
SocketSample/Program.cs at master · TadaoYamaoka/SocketSample · GitHub
以下のようにしてSocketでサーバに接続する。
TcpClient tcp = new TcpClient("127.0.0.1", 50007); NetworkStream ns = tcp.GetStream();
送信するデータは、BitConverter.GetBytesでbyte配列に変換して送信する。
通信回数を抑えるため、MemoryStreamに格納してまとめて送信する。
var ms = new MemoryStream(); int size = 4 + 4 + 4 * a.b.Length; ms.Write(BitConverter.GetBytes(size), 0, 4); // ... ns.Write(ms.ToArray(), 0, 4 + size);
応答の受信は、Readで行う。byte配列で受け取るため、BitConverter.ToInt32などで数値など変換する。
byte[] int_bytes = new byte[4]; ns.Read(int_bytes, 0, 4); size = BitConverter.ToInt32(int_bytes, 0);
最後に接続をクローズする。
ns.Close(); tcp.Close();
Python側(サーバー)
サンプルコード:
SocketSample/SocketServer.py at master · TadaoYamaoka/SocketSample · GitHub
以下のように接続待ち→接続を行う。
s.bind(('127.0.0.1', 50007)) s.listen(1) conn, addr = s.accept()
受信は、recvを使用するとバイト列が返る。recv_intoを使うとバッファに直接書き込む。バッファにはnumpyのndarrayが使用できる。
size = int.from_bytes(conn.recv(4, socket.MSG_WAITALL), sys.byteorder) # ... data = np.empty(size, dtype=np.uint8) conn.recv_into(data, size, socket.MSG_WAITALL)
応答の送信は、sendallで行う。numpyのndarrayは直接引数に渡せる。
res = np.array([1, 2, 3]) conn.sendall(len(res).to_bytes(4, sys.byteorder)) conn.sendall(res)
デシリアライズとデータ加工をCythonで行う
バイト列をデシリアライズして、機械学習で使用するためにデータ加工を行うような場合は、実行速度をできるだけ高速化したい。
そのような場合、Cythonを使うことでデシリアライズとデータ加工をC++で記述できる。
サンプルコード:
SocketSample/Decoder.pyx at master · TadaoYamaoka/SocketSample · GitHub
SocketSample/Decoder_impl.cpp at master · TadaoYamaoka/SocketSample · GitHub
データの受け渡しをnumpyのndarrayにすることで、余分なメモリコピーを抑えることができる。