質問: ThreadM1において、明らかにメモリ不足が原因で書き込み処理が停滞してしまっています。 なぜ、ThreadM1は機動的にメモリ確保する動作にならないのか、また、これを解決するためにはどうしたらよいでしょうか? **[訂正]明らかにメモリ不足と判断したのは誤りがあるかもしれません。処理が遅すぎてメモリ確保するタイミングがずれただけかもしれません。パフォーマンスモニタで再確認したところ、メモリが徐々に確保されて行っている様子がうかがえました。** M1をマルチプロセス化することで解決いたしました。 しかしながら、先に記載したスレッド時の動作原因がわかりません。引き続き、後学のため原因がご存知の方はご教示願えないでしょうか。
回答: メインから, データ生成スレッド(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)** ``` [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 ``` **マルチスレッドでの処理 50種 x 1000 件 x 10項目(あるいは 100項目)** (100件あたり, 0.8秒程度) (処理終了時に, データ確認のため Pandasで sql読み取り, 項目を抜粋して表示) ``` [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 ``` ### 結論? * sqlite3 は問題には関係ないように思われること。(記されていない条件があれば別だが) * ある程度制限をかけてのテストプログラムだが, それでもスレッドが極端に遅くなるようなことはない テストに使用したプログラムにはなく, 問題にしているプログラムには存在するような**「何か」**が原因であろうと思われる 考えられる一番の要因は, やはり GIL でしょう > > 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/プロセス あたりを Threadは (大抵は) OSがその機能を持つが, 独自に実装する場合もなくはない。Processとの関係は, (環境によって変わるが) Process内に複数の Threadと捉えてて(普通は)大丈夫 詳しくは https://ja.wikipedia.org/wiki/スレッド_(コンピュータ)) Processが親プロセスから影響を受けることは多々あり, Processから生成した Threadが何らかの情報を引き継ぎ影響を受けることもあるが, その逆はあまりありません(絶対とは言えないが) その Threadが所属する Processからの子プロセスという形になる, のが普通。(起動元 Threadが)所属する Processの影響を(起動される子プロセスが) 受けるのは普通にある (なので, 遅いスレッドから生成されたプロセスが, 遅いとは言えない)