【python】restAPI実行でconcurrent.futuresを使ってみた

## はじめに
こんにちは。ジールの@________________-_です。

ネットワークアクセス処理にはマルチスレッド処理が有効という記事や本をよく見かけますが、
restAPIを利用してデータ送信する機会がありましたので、実験としてどの位速度が変わるのか計測してみました。
以下3つのパターンで比較しました。

- マルチプロセス処理：`concurrent.futures.ProcessPoolExecutor`を利用。
- マルチスレッド処理：`concurrent.futures.ThreadPoolExecutor`を利用。
- 単純なループ処理：`for`ループを利用。

## 環境

- マシン：MacBook Pro 13inch 2020
- CPU：2GHz Quad-Core intel Corei5
- Mem：16GB

## 処理時間の計測
以下のコードを実行して、100回と1000回のリクエストで比較してみました。

```py
import concurrent.futures
import time

import requests


def post_req():
    url = 'https://jsonplaceholder.typicode.com/posts'
    data = {'msg1': 'test_data1', 'msg2': 'test_data2', 'msg3': 'test_data3'}

    response = requests.post(url, json=data)

    return response.status_code


def process_pool(exec_cnt=1):
    start = time.perf_counter()

    with concurrent.futures.ProcessPoolExecutor() as executor:
        response_process = [executor.submit(post_req) for _ in range(exec_cnt)]

    success_cnt = 0
    error_cnt = 0
    for res in response_process:
        if res.result() == 201:
            success_cnt += 1
        else:
            error_cnt += 1

    print(f'process_success_cnt: {success_cnt}')
    print(f'process_error_cnt: {error_cnt}')
    print(f'process_duration: {time.perf_counter() - start}')


def thread_pool(exec_cnt=1):
    start = time.perf_counter()

    with concurrent.futures.ThreadPoolExecutor() as executor:
        response_process = [executor.submit(post_req) for _ in range(exec_cnt)]

    success_cnt = 0
    error_cnt = 0
    for res in response_process:
        if res.result() == 201:
            success_cnt += 1
        else:
            error_cnt += 1

    print(f'thread_success_cnt: {success_cnt}')
    print(f'thread_error_cnt: {error_cnt}')
    print(f'thread_duration: {time.perf_counter() - start}')


def normal_loop(exec_cnt=1):
    start = time.perf_counter()
    success_cnt = 0
    error_cnt = 0

    for _ in range(exec_cnt):
        res = post_req()
        if res == 201:
            success_cnt += 1
        else:
            error_cnt += 1

    print(f'normal_loop_success_cnt: {success_cnt}')
    print(f'normal_loop_error_cnt: {error_cnt}')
    print(f'normal_loop_duration: {time.perf_counter() - start}')


if __name__ == '__main__':
    exec_cnt = 100
    #exec_cnt = 1000
    print(f'exec_cnt: {exec_cnt}')

    print('-----start process-----')
    process_pool(exec_cnt)

    print('-----start thread-----')
    thread_pool(exec_cnt)

    print('-----start normal-----')
    normal_loop(exec_cnt)
```

## 結果
何回か実施しましたが、結果はほぼほぼ以下の通りでした。
どちらの場合もマルチスレッド処理が一番早いです。
マルチスレッドと通常のループ処理を比較すると10倍以上の差が出てます。

**リクエスト100回の時**
```
exec_cnt: 100
-----start process-----
process_success_cnt: 100
process_error_cnt: 0
process_duration: 6.557022245
-----start thread-----
thread_success_cnt: 100
thread_error_cnt: 0
thread_duration: 4.388986459999999
-----start normal-----
normal_loop_success_cnt: 100
normal_loop_error_cnt: 0
normal_loop_duration: 49.342961767000006
```

**リクエスト1000回の時**
```
exec_cnt: 1000
-----start process-----
process_success_cnt: 1000
process_error_cnt: 0
process_duration: 60.508084134
-----start thread-----
thread_success_cnt: 1000
thread_error_cnt: 0
thread_duration: 34.706618609
-----start normal-----
normal_loop_success_cnt: 1000
normal_loop_error_cnt: 0
normal_loop_duration: 480.41693989199996
```

## おわりに
マルチスレッドと通常のループを比べるとこんなにも差が出るんですね！！！
10倍以上も差が出たのは驚きました。
マルチスレッドとマルチプロセスだと、マルチプロセスの方がオーバーヘッドが大きい分遅くなってます。
今回のような処理の場合は、マルチスレッドの利用はとても有効そうです！

## 参考
**プロセスとスレッドの説明**
詳細に説明されていてとても分かりやすいです。

https://qiita.com/simonritchie/items/1ce3914eb5444d2157ac

**concurrent.futuresの使い方**

https://docs.python.org/ja/3/library/concurrent.futures.html

***
株式会社ジールでは、「ITリテラシーがない」「初期費用がかけられない」「親切・丁寧な支援がほしい」「ノーコード・ローコードがよい」「運用・保守の手間をかけられない」などのお客様の声を受けて、オールインワン型データ活用プラットフォーム「ZEUSCloud」を月額利用料にてご提供しております。
ご興味がある方は是非下記のリンクをご覧ください：
 
https://www.zdh.co.jp/products-services/cloud-data/zeuscloud/?utm_source=qiita&utm_medium=referral&utm_campaign=qiita_zeuscloud_content-area