組み込みDL向けのプロセス間通信

この記事は、[LeapMind Advent Calendar 2019](https://adventar.org/calendars/3954)の20日目の記事です。
## 要約

C言語でプロセス間通信にとしてsocketとsignalを使い、初期化を事前に行うことで、Deep Learningのモデルの結果を得るまでの短縮する方法について書きます。


## 自己紹介
私は普段は**PythonメインのDeep Learning エンジニア**です。今回は**C言語**を使います。組み込みDLの開発を行うLeapMindではC/C++を使うエンジニアは多いですが、私自身はC/C++を使った開発はほとんどないです。**習熟度は高くないので**、間違いなどありましたら、ご指摘いただきたいです！


※ 私自身はC/C++を使うことはほとんどないですが、[LeapMind](https://leapmind.io/)では、組み込み・エッジコンピューティングの開発も行っています。

## 目的
組み込みDL向けのプロセス間通信というタイトルにしましたが、ただのプロセス間通信するだけです！
本当はC/C++でmnistを動かそうと思いましたが、分量が多くなるのでinference部分はダミーにしています（後述）。

さて、製造業などの組み込み機器だと**数十msec**などの短い時間で、処理を行いたいことがあります。
小さい入力であれば、DLのモデルであっても数十msecで実行できることがありますが、
単純にTensorFlowで作られたDLのモデルの読み込み処理を行うと、起動までで**数秒単位**でかかってしまうことがあります。

そういう場合には、事前のサービスとして立ち上げておき、リクエストを送るようにすることで、
待ち時間をなくすことができます。

実際には、DLでよく使われるPythonにも当然socketモジュールは存在しますし、よりリッチなことができるライブラリーが
多数存在しますが、組み込みに近い環境では、Pythonをイントールすることはできない or したくないということがあります。
そのため、**C/C++などでの開発を求められる**ことがあります。

※ 今回はマシン内での通信のみを想定しています。外部と通信する場合には、セキュリティーのこともしっかり考える必要があります。

## 環境
今回使う環境は、以下の環境にしました。

* Ubuntu==16.04
* gcc==5.4.0

※ 別のディストリビューションやMacでもそのまま動くはずですし、winsock2を使えばWindowsでも同じことができます。

## 手段

プロセス間通信を行う方法は複数あります。
今回は、socket通信を使ったプロセス間通信にします。
また、おまけで、signalを使った簡易な方法を書きたいと思います。

## 処理の流れ
下図のようなclient、serverの通信処理になります。
inferenceでは3x3の行列を入力し、列ごとに和をとるに計算します。
3x3の行列（9個のfloat列）をclientより送信し、3個のfloat列を通信するようにします。

![Untitled Diagram.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276312/3a606d70-3b8d-bee1-a432-efd04230ce2a.png)

## ソースコード
socketについて書かれた書籍は、あまりないので、ネットの技術ブログ等を読んで概要を掴んで、**正確な情報は、**[**Linux Programmer's Manual（翻訳）**](https://linuxjm.osdn.jp/html/LDP_man-pages/man7/socket.7.html)などを読んで理解するのが普通なのかなと思います。


参考記事・資料
* [Linux Programmer's Manual（翻訳）](https://linuxjm.osdn.jp/html/LDP_man-pages/man7/socket.7.html)
* [Geekなページ](http://www.geekpage.jp/programming/linux-network/)
* [ソケットプログラミング](https://www.katto.comm.waseda.ac.jp/~katto/Class/10/GazoTokuron/code/socket.html)
* [コネクション型通信：サーバプログラムの作成](http://research.nii.ac.jp/~ichiro/syspro98/server.html)

```C:server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#define PORT 52579
#define SIZE 3


void inference(float* input, float* output)
{
    for (size_t i=0; i<SIZE; ++i)
    {
        for (size_t j=0; j<SIZE; ++j)
        {
            output[i] += input[SIZE*i + j];
        }
    }
}


int main(void)
{
    // create socket
    int sock;
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        perror("socket");
        return 1;
    }

    // struct about the connection destination
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    // bind
    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) != 0)
    {
        perror("bind");
        return 1;
    }

    // listen
    int backlog = 1;
    if (listen(sock, backlog) != 0)
    {
        perror("listen");
        return 1;
    } else
    {
        printf("listen\n");
    }

    while(1)
    {
        int status;

        // accept socket
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int connected_socks = accept(sock, (struct sockaddr *)&client, &len);

        if (connected_socks == -1)
        {
            perror("accept");
        } else
        {
            printf("accepted\n");
        }

        // recieve
        float input[SIZE*SIZE] = {};
        status = recv(connected_socks, input, sizeof(input), 0);

        if (status == -1)
        {
            perror("recv");
        } else
        {
            printf("received\n");
        }

        // print recieved data
        printf("[ ");
        for (size_t i=0; i<SIZE; ++i)
        {
            for (size_t j=0; j<SIZE; ++j)
            {
                printf("%f ", input[SIZE*i + j]);
            }
        }
        printf("]\n");

        // inference
        float output[SIZE] = {};
        inference(input, output);

        // send
        status = send(connected_socks, output, sizeof(output), 0);
        
        if (status == -1)
        {
            perror("send");
        } else 
        {
            printf("send\n");
        }

        close(connected_socks);
    }

    close(sock);
    return 0;
}
```

```C:client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 52579
#define SIZE 3


int main(void)
{
    int status;

    // create socket
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    // struct about the connection destination
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(PORT);
    server.sin_addr.s_addr = inet_addr("127.0.0.1");

    // connect server
    status = connect(sock, (struct sockaddr *)&server, sizeof(server));
    if (status == -1)
    {
        perror("connect");
    } else {
        printf("connected\n");
    }

    // input
    float input[SIZE*SIZE] = {
        0.f, 1.f, 2.f,
        3.f, 4.f, 5.f,
        6.f, 7.f, 8.f
    };

    // send
    status = send(sock, input, sizeof(input), 0);
    if (status == -1)
    {
        perror("send");
    } else {
        printf("send\n");
    }

    // recieve
    float output[SIZE] = {};
    status = recv(sock, output, sizeof(output), 0);
    if (status == -1)
    {
        perror("recv");
    } else {
        printf("received\n");
    }

    // print received data
    printf("[ ");
    for(size_t i=0; i<SIZE; ++i)
    {
        printf("%f ", output[i]);
    }
    printf("]\n");

    // close socket
    close(sock);

    return 0;
}
```


## 実行方法
#### コンパイル
```shell

gcc -o server server.c
gcc -o client client.c
```

### 実行
```shell

$ ./server
listen
accepted
received
[ 0.000000 1.000000 2.000000 3.000000 4.000000 5.000000 6.000000 7.000000 8.000000 ]
send
```

```shell
./client
connected
send
received
[ 3.000000 12.000000 21.000000 ]
```

### C/C++によるDL 
TensorFlowにはC/C++ APIがあります。少し癖がありますが、ドキュメントを読めば比較的簡単に使うことができるはずです（コンパイルするのがちょっと大変だけど...）。こちらを紹介だけでもかなりの分量になります。記事や参考となるソースコードは多数存在するので、それらを参考にいただければと思います。

* [Tensorflow公式（ソースからのビルド）](https://www.tensorflow.org/install/source?hl=ja)
* [mnnistのC++のサンプル(JackyTung/tensorgraph)](https://github.com/JackyTung/tensorgraph)
* [saito's memo: Pythonで学習したネットワークをC++で実行する](https://memo.saitodev.com/home/tensorflow/use_graph_in_cxx/)

※ Tensorflowでは初回の実行でメモリー確保等が行われ初回の動作が遅いので初回だけ空実行することで、それ以降は待ち時間なしで実行できるようになります。

## おまけ（signal）
プログラムを終了させたいときには、`ctrl+C`でプログラムを終了すると思いますが、それはsignalというOSの機能つかっています。
`ctrl+Z`でプログラムを停止することもできます。

プログラムを起動してモデル読み込みなど初期化が終わった時点で、停止シグナルを自身に送ることで、停止します。
外部プログラムorコマンドからsignalを送ることで、プログラムを再開して待ち時間なしで実行することができます。
非常にprimitiveな方法ですが、デモ用等で非常に簡易で良いときにはことで良いこともあります。

通信によって直接データを渡すことができないですが、たとえば、カメラから直接読み込み実行することや、ファイル読み込みで簡易に済ますことが考えられます。

```C:run_by_signal.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>


int main(void)
{
    printf("Initialize...\n");
    // run some initialization here
    sleep(1);

    pid_t c_pid = getpid();
    printf("Send SIGCONT to PID: %d to run\n", c_pid);

    while(1)
    {
        raise(SIGTSTP);
        printf("run \n");
    }

    return 0;
}
```

```C:send_signal.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>


int main(int argc, char *argv[]) {
    printf("send continue signal to PID:%s\n", argv[1]);
    kill(atoi(argv[1]), SIGCONT);
}
```

### 実行方法

#### コンパイル

```shell

gcc -o run_by_signal run_by_signal.c
gcc -o send_signal send_signal.c
```

#### 実行
`run_by_signal`を実行すると、初期化を行いすぐに停止する。
その後`send_signal`を実行することで、`run_by_signal`が再開され、処理が実行され（ここでは`run`を出力するだけ）、またすぐに停止する。

`send_signal`を実行するときには、`run_by_signal`のプロセスIDを指定する。

```
$ ./run_by_signal
Initialize...
Send SIGCONT to PID: 17088 to run

[1]+  Stopped                 ./run_by_signal
$ ./send_signal 17088
send continue signal to PID:17088
run

[1]+  Stopped                 ./run_by_signal
```

`kill`コマンドを使って、`send_signal`と同じこともできます。


こちらも最終的にsignalも正確な情報を[Linux Programmer's Manual（翻訳）](https://linuxjm.osdn.jp/html/LDP_man-pages/man7/signal.7.html)などを読んで理解するのが普通なのかなと思います。

## まとめ・感想
「Pythonばっかりなので、たまにはC言語を使いたいな。」と思い書いてみました。
プロセス間通信にとしてsocketとsignalを使い、初期化を事前に行うことで、Deep Learningのモデルの結果を得るまでの短縮する方法について書きました。

TensorflowをC++ APIを使ったことはありますが、CのAPIは無いので試してみたいです。
またPytorchにもC++ APIがあるようなので、こちらも近いうちに試してみたいとおもいます。
Pythonのインターフェースと類似しているようなので、慣れている人から便利と聞きました。

## 参考文献一覧
* [Linux Programmer's Manual（翻訳）](https://linuxjm.osdn.jp/html/LDP_man-pages/man7/socket.7.html)
* [Geekなページ](http://www.geekpage.jp/programming/linux-network/)
* [ソケットプログラミング](https://www.katto.comm.waseda.ac.jp/~katto/Class/10/GazoTokuron/code/socket.html)
* [コネクション型通信：サーバプログラムの作成](http://research.nii.ac.jp/~ichiro/syspro98/server.html)
* [Tensorflow公式（ソースからのビルド）](https://www.tensorflow.org/install/source?hl=ja)
* [mnnistのC++のサンプル(JackyTung/tensorgraph)](https://github.com/JackyTung/tensorgraph)
* [saito's memo: Pythonで学習したネットワークをC++で実行する](https://memo.saitodev.com/home/tensorflow/use_graph_in_cxx/)