75336


# プログラム内容

マルチスレッドおよびマルチプロセスを組み合わせたプログラムとなっており、下図ような構成で動作しています。  
現在、リファクタリング中で、ProcessA-Cの処理は以前はThreadでしたが、高速化のために並行処理させるため、MultiProcess化しています。

[![プログラム構成](https://i.stack.imgur.com/e2q0i.jpg)](https://i.stack.imgur.com/e2q0i.jpg)

## 追記情報

Thread M1をMultiProcessでプロセス化しました。  
これにより、M1での書き出しが高速化しました。

[![スレッドのマルチプロセス化](https://i.stack.imgur.com/ctIWm.png)](https://i.stack.imgur.com/ctIWm.png)

## 処理内容の概要（追記情報）

1.Process A  
外部イベントを受信して、イベントに応じて外部から情報を取得後、M1に情報を引き渡します。  
2.Process B  
M1からの指令により、外部に対して何らかの制御コマンドを発行、返信を処理します。  
3.Process C  
GUIプロセスです。ユーザーアクションを通知し、状態変化を受信して描画します。  
4.M1  
必要な情報を整理し、各プロセスの監視、終了、中断・再開などを処理します。  
必要な情報をDBへ読み書きする処理も担います。  
5.Main  
アプリケーション起動ファイルの読み込み、外部プログラムを起動するサブプロセス、Loggingプロセス、M1を起動・終了を行います。

## DBのレコード情報

詳細は記載しきれませんが、参考として記載いたします。  
1回の書き込みにより更新されるテーブル数は44（厳密には種別により書き込むテーブルが異なります。  
各テーブルのカラム数は最大のもの１つで100、そのほかは10以下がほとんどです。  
今回の環境ではレコードは95。（計測した処理時間は、レコード数が8のときも変化ありませんでした。）  
情報更新された対象のみデータを取得し、更新します。

# 環境

Python3.7  
Windows10  
VScode

# 問題点

Thread M1において、Process Aから取得したデータをSQLiteを介してDBファイルに書き込む処理を実行します。  
この書き込み処理は実績があり、１回のデータ書き込み処理におおよそ0.15~0.2秒ほどかかります。  
しかし、Thread M1では、１回の書き込み処理に6秒もかかってしまいます。インメモリDBで処理させると、8.0-9.4秒にさらに遅くなってしまいます。

# 試したこと

  1. DBファイルとインメモリDB動作  
ThreadM1では、1回の書き込み処理の時間が以下の通りとなりました。  
DBファイル：6秒強  
インメモリ：8.0秒超  
インメモリにしたほうが遅くなります。

  2. MainProcessでの書き込み  
ThreadM1から親スレッドにデータを転送し、書き込みした結果、以下の通りとなりました。  
DBファイル：0.2秒弱  
インメモリ：0.02秒程度  
インメモリにしたほうが格段に速くなりました。

  3. パフォーマンスモニタでのメモリ観察  
上記１のときには、メモリがなだらかに上昇しました。  
上記２のときには、すぐにメモリを確保して一段ジャンプアップしたようになりました。

  4. M1のマルチプロセス化  
M1をマルチプロセス化し、実行しました。メインプロセスで実行する場合と同じ速度になりました。パフォーマンスモニタでもメモリの確保を確認しました。  
DBファイル：0.2秒弱  
インメモリ：0.02秒程度

5.一部機能停止時【追加情報】  
Process B/Cの起動せずに計測しました。  
DBファイル：5-6秒(若干低下した印象です。  
インメモリ：5-6秒程度→しばらくしたら2.5-3.0秒に短縮

6.処理ループのインターバル時間変更  
上記5において、ProcessAやThreadM1の処理ループインターバル時間を10倍長くしても、処理時間は変わりませんでした。

# 解決したい課題

ThreadM1において、明らかにメモリ不足が原因で書き込み処理が停滞してしまっています。  
なぜ、ThreadM1は機動的にメモリ確保する動作にならないのか、また、これを解決するためにはどうしたらよいでしょうか？  
**[訂正]明らかにメモリ不足と判断したのは誤りがあるかもしれません。処理が遅すぎてメモリ確保するタイミングがずれただけかもしれません。パフォーマンスモニタで再確認したところ、メモリが徐々に確保されて行っている様子がうかがえました。**

M1をマルチプロセス化することで解決いたしました。  
しかしながら、先に記載したスレッド時の動作原因がわかりません。引き続き、後学のため原因がご存知の方はご教示願えないでしょうか。


CC BY-SA 4.0
2021-04-09T07:24:19.447
75179
2021-04-17T06:55:01.050
2021-04-16T07:00:02.177
32891
32891
question
python python3 sqlite python-multiprocessing memory
実行スレッドによって、同じSQLite書き込み処理時間が大きく異なるのはなぜでしょうか？


メインから, データ生成スレッド(Process A を想定), データ受け取り＆書き込みスレッド(M1 を想定)  
2つのスレッドを呼び出して, スレッド間のデータ渡し 及び sqlite3 DBへの書き込みを行ってみました  
(メイン側は, スレッドの開始, 及び終了待ち合わせ程度を行う)

  * データ生成スレッド(Process A 代わり) は, table_id + 10 項目 ⇨ 11 項目の数値を一度に送信  
(ただし, table_id == 0 の場合は 100 項目とする, id合わせて 101 項目)

  * table_id は 50 種類 ⇨ 最終的に 50 種類の sqlite3 table へ分類される
  * 件数は 50 種類 x 1,000 件 ⇨ 50,000 件を送信・受信する  
(1つのテーブルあたり 1000件だがランダムなので上下あり)

  * sqlite3 DB はファイルとした。`connect`後に `drop table`を行っている

スレッドの動作を被らせるため以下の処置を行う

  * 実際のレコード数は 100件未満だが, あまりに少ないと有意な情報が得られないため 1000件とした
  * スレッド間通信は `Queue()`を使用。スレッドの動作が偏らないように `maxsize=10` 程度を指定  
(サイズが大きいとデータが収まるため 走りきってしまう)

  * データ生成スレッド(Process A 代わり) に, 1/10 の確率で `1ms` の sleepを行った

### 処理速度について, 要因

  * スレッド間通信/プロセス間通信は, 何を使うかによってそれなりに速度に差が付く。しかしスレッド間通信の方が有利であり, かなりの差が付く。スレッド速度が遅いこととは関係ないと思われ
  * sql の `insert` で, 毎回 `commit` を行うと極端に遅くなる。しかしスレッドかどうかには関係ないと思われ
  * 最後にまとめて `commit`を行い, その間 journalが蓄積される。journalが存在する間は他からアクセスできないが, 他のスレッドやプロセスからアクセスするのでなければ影響ない
  * sqlite3 の動作については (他にも検証の余地があるかもしないが), sqlite3 が遅くなったと言うより, スレッドが遅くなったことにより影響を受けた, と思われ

**比較のための, 単一処理 30,000 件 (queue.put = > queue.get => sqlite3 insert)**

[code]

    [2021-04-17 04:10:11,371] sqlite:INFO: test: create table 30000
    [2021-04-17 04:10:12,068] sqlite:INFO: sqlite commit
    [2021-04-17 04:10:12,073] sqlite:INFO: test: data end 0.7001097202301025sec
    
[/code]

**マルチスレッドでの処理 50種 x 1000 件 x 10項目(あるいは 100項目)**  
(100件あたり, 0.8秒程度)  
(処理終了時に, データ確認のため Pandasで sql読み取り, 項目を抜粋して表示)

[code]

    [2021-04-17 04:10:13,366] sqlite:INFO: create table
    [2021-04-17 04:10:21,437] sqlite:INFO: future result: done
    [2021-04-17 04:10:21,442] sqlite:INFO: data end 8.072937726974487sec
    [2021-04-17 04:10:21,444] sqlite:INFO: pandas read
    item_01 item_10 item_70 item_99
    0   10408   79939   40135   53468
    1   89249   64335   76415   97793
    2   55930   34558   16455   78368
    3   83655   42987   17511   88956
    4   37793   43735   13895   89253
    ... ... ... ... ...
    956 37199   56699   76178   12523
    957 43779   2493    74782   29388
    958 78387   87789   8596    58658
    959 26001   73102   36995   60308
    960 16271   9552    91286   63834
    961 rows × 4 columns
    
    [2021-04-17 04:10:21,494] sqlite:INFO: sqlite commit
    [2021-04-17 04:10:21,498] sqlite:INFO: sqlite close
    [2021-04-17 04:10:21,499] sqlite:INFO: future result: write end
    
[/code]

### 結論？

  * sqlite3 は問題には関係ないように思われること。(記されていない条件があれば別だが)
  * ある程度制限をかけてのテストプログラムだが, それでもスレッドが極端に遅くなるようなことはない  
テストに使用したプログラムにはなく, 問題にしているプログラムには存在するような**「何か」**が原因であろうと思われる

考えられる一番の要因は, やはり GIL でしょう

<https://wiki.python.org/moin/GlobalInterpreterLock>

> GIL, is a mutex that protects access to Python objects, preventing multiple
> threads from executing Python bytecodes at once.

> Luckily, many potentially blocking or long-running operations, such as I/O,
> image processing, and NumPy number crunching, happen outside the GIL.
> Therefore it is only in multithreaded programs that spend a lot of time
> inside the GIL, interpreting CPython bytecode, that the GIL becomes a
> bottleneck.

bytecode interpreter は (Pythonの Processであれば)プロセスごとに起動・動作してるので競合はなく, 同一
interpreter による(同一 Python objects での), 複数ヶ所の bytecode 実行に問題があります

処理を分けるには, 別スレッド, 別プロセス, 非同期処理などが考えられ, 別プロセスにするのがよいでしょう。(実際, そのような結果になってるようですが)  
スレッドとして高速化を図るには

  * どれか一つのスレッドでも, 休まず bytecode 実行し続けると, GILにより 他のスレッドが割りを食うことに  
(なので何かが無駄に動いてそうな？ … M1が遅く, M1以外にスレッド生成していないなら Mainが問題でしょう)

  * その点 sqlite3 モジュール本体は Python製ではないため影響を受けない。I/O も同様  
(影響を受けたのは Pythonとのデータ受け渡しに関する部分と思われ)

* * *

備考？

> Threadから起動しているProcess全体が、ThreadM1に割り当てられた実行時間内での動作を強いられると予測

Processは OSが管理・制御する機能の一つです (プロセスへのリソースの割り当てを制御したりとか)  
詳しくは
[https://ja.wikipedia.org/wiki/プロセス](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%BB%E3%82%B9)
あたりを

Threadは (大抵は) OSがその機能を持つが, 独自に実装する場合もなくはない。Processとの関係は, (環境によって変わるが)
Process内に複数の Threadと捉えてて(普通は)大丈夫  
詳しくは
[https://ja.wikipedia.org/wiki/スレッド_(コンピュータ)](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%AC%E3%83%83%E3%83%89_\(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF\))

Processが親プロセスから影響を受けることは多々あり, Processから生成した Threadが何らかの情報を引き継ぎ影響を受けることもあるが,
その逆はあまりありません(絶対とは言えないが)  
その Threadが所属する Processからの子プロセスという形になる, のが普通。(起動元 Threadが)所属する
Processの影響を(起動される子プロセスが) 受けるのは普通にある  
(なので, 遅いスレッドから生成されたプロセスが, 遅いとは言えない)


CC BY-SA 4.0
2021-04-17T06:44:32.083
75336
2021-04-17T06:55:01.050
2021-04-17T06:55:01.050
43025
43025
75179
answer