前提・実現したいこと
Pythonで機械学習をテストするコードを書いています。
データが十数GBあり、それを予めメモリにロードすることで高速化していますが、(コードを書き換えるなどした)異なるプログラムを同時実行するにはメモリ容量に限界があります。
そこでメモリにロードするプログラムと、そこにアクセスして機械学習を実行するプログラムに分離したいと考えていますが、この機能を実現する方法・コードサンプルはあるでしょうか。
ネットで調べても、同一のプログラムからマルチプロセスに分岐して共有メモリを実現する方法しか見つけられませんでした……。
よろしくお願いします。
環境:Python 3.6.9、Ubuntu 18.04.3 LTS
現状は、一つの仮想空間内にロード用プログラムとデータと学習プログラムがあり、それがメモリ容量の限界とのことですね。現在のCPUでは仮想空間のアドレス可能領域は256テラバイトのはずですので仮想空間が足りないことではなく、物理メモリが足りないことが問題なのではないですか。
ここで、二つのプロセス(仮想空間)を作ってメモリを共有するなどしても、物理メモリの不足は変わらないように思います。
> マルチプロセスに分岐して共有メモリを実現する方法
それでは駄目なのですか?メモリを共有するということは並列処理ということですよね?
ppaul さん
現状は、ロード用と学習用がまとまったプログラムを動かしています。
物理メモリは96GBあるのですが、共用サーバなので無闇やたらと使うことができない状況です。
仮想メモリを使って複数動作させることはできますが、メモリの確保に失敗してプログラムが強制終了することがあります(OOM Killerに殺されます)。
meg_ さん
一度にすべての処理を開始するわけではなく、何度もプログラムを変えて試行する必要がある関係で、よくあるマルチプロセスの方法をそのまま使うことができません。
「mmapを使った共有メモリ」が最初に頭に浮かびますが、そういう視点というよりは、他プロセスからアクセス可能な共有メモリがあり、他プロセスは任意の時点で起動し、その共有メモリにアクセスしたいとのようなことでしょうか。
「他プロセスからアクセス可能な共有メモリがあり、他プロセスは任意の時点で起動し、その共有メモリにアクセスしたい」というのは、その通りです。
mmapはすみませんが使ったことがないので明確には答えられませんが、少し調べてわからなかった点があるので、以下のことをご存知でしたら教えてほしいです。
・mmapをしてファイルを論理メモリにマッピングすると、主記憶にはデータがコピーされるのか。
・(コピーされる場合)複数プログラムからmmapをした場合は複数のコピーが作成されるのか。
・(コピーされない場合)補助記憶にアクセスするので遅延が発生しますよね?
> ・mmapをしてファイルを論理メモリにマッピングすると、主記憶にはデータがコピーされるのか。
主記憶、つまりRAMであるPCのメインメモリを指しているとして、そうです。ファイルの内容がメモリに展開され、mmapオブジェクトを介してアクセスするかたちになります。
> ・(コピーされる場合)複数プログラムからmmapをした場合は複数のコピーが作成されるのか。
mmapオブジェクトの生成方法によります。MAP_PRIVATEやMAP_SHAREDの指定で、プログラムごとのものか、複数プログラムで共有するひとつのものかが決まります。https://docs.python.org/ja/3/library/mmap.html
> ・(コピーされない場合)補助記憶にアクセスするので遅延が発生しますよね?
前述のMAP_PRIVATEならプログラムそれぞれのmmmapオブジェクトになるので、結果的にロードの為の遅延は発生することになりますね。本コメント欄で提案したmmap(メモリマップファイル、メモリマップトファイル)はファイルアクセスに際してメモリ上に展開し、メモリ上の操作でファイルに最終的に結果をコミットすることができるというもので、そのメカニズムの中で共有メモリを実現でき、複数プロセス間で共有できるというだけです。そのメモリを示すmmapオブジェクトに対してはファイル操作のようなメソッド操作が伴うので、C言語のようにローレベルで構造体のイメージで操作できれば簡単ですが、pythonプログラムの中でシームレスに扱い続けるのはちょっと難しいかもしれません。更に、数十GBもの巨大データだとメインメモリから追い出されるだろうから、アクセスする位置によっては仮想記憶によりスワップアウトされる機会も増えそうです。しかし、巨大なデータに伴うスワップアウトによる遅延は避け得ないことであるはずです。
尚、私自身はそのような巨大なサイズの共有メモリは扱ったことはありません。OSの制限でできないかもしれません。
最初に考えなければならないのは、「予めメモリにロードすることで高速化」というのが成り立つかどうかです。
すべてのデータに最初から最後までアクセスするようなプログラムは、High-Performance LINPACKのようなベンチマークプログラム以外には滅多にありません。必要もなくメモリを圧迫するのは、スラッシングを引き起こして却って性能を落とすこともあります。
プログラムがデータ待ちでエラプスが大きい場合で、すべてのデータを一度に使わないのであれば、別スレッドを作って、データの読み込みと計算を重ね合わせることで性能が良くなることが良くあります。
読み込み1 読み込み2 読み込み3 読み込み4
計算1 計算2 計算3 計算4
というようなパイプライン処理をするのです。
pythonではやったことはありませんが、threadingモジュールを使うメモリ共有型並列処理で、lockとかsemaphoreを使って同期をとりながらやれば、それほど難しくないと思います。
threadingを使った場合は、読み込みの結果はpythonのオブジェクトになるので、他の手段を使った場合のような困難さはありません。
本当にすべてのデータを最初から最後までアクセスするようなプログラムを実行したいのであれば、システム管理者にお願いして使用メモリなどの制限や優先順位をあげてもらうのが筋です。
データをもたせた Python プロセス立ち上げといて、そこに Python ソースコード流し込んで exec させるという邪悪な方法を思いつきました
dodox86 さん
ありがとうございます。
mmapでMAP_SHAREDにしてどこまでできるか試してみます。
使用しているサーバをよく確認してみたらスワップ領域は小さめに設定されていたので、スワッピングの遅延は考えなくても良さそうです。
(機械学習に最適化するための設定だと思われますが、そのせいでメモリの確保に失敗していたようです。)
ppaul さん
案をいただきありがとうございます。
並列でのパイプライン処理は確かにうまく使えば便利そうですね。
ただ、学習時のシャッフルがオンになっているので、次のデータを予測できないという問題があります。
A_kirisaki さん
作法としてはよろしくないですが、まあ、できなくはないのでしょうね。
デバッグも大変になるのでやりたくないですけど(笑)
> データをもたせた Python プロセス立ち上げといて、そこに Python ソースコード流し込んで exec させる
A_kirisakiさんご提示の案、実用性は置いておくとしても、通常と逆の発想で技術的に面白いです。
A_kirisakiのアイデアの意味が良くわかりません。
pythonのexec関数のことなのだとすると、メモリ問題の解決策にはならないですね。
pythonのexecは、シェルのsourceコマンドのようなもので、文字列として受け取ったものをインタープリタが解析して実行する機能ですので、fork-execのexecとは無関係です。
それとも、pythonの中でexec系のシステムコールを発行しようという話なのでしょうか。
mmapを試してみましたが、mmapを呼び出してもデータは物理メモリにロードされないことがわかりました。
回答は受付中のままにしますが、これから年末ということもあって忙しくなりそうなので、この問題の解決は後回しにさせていただきます。
ppaul さん
execのシステムコールを発行しない=同一のプロセスだからこそ、マルチスレッディングとexecを使うことで、共有メモリにアクセスしつつコードを動的に読み込んで実行できそうな気がするのですが、違うのですかね?
> mmapを呼び出してもデータは物理メモリにロードされない
どのように検証されたか分かりませんが、実際の読み出し処理(プログラムによるアクセス)が開始しないと、そのアクセスしたい領域部分は物理メモリにロードされない可能性はあるかも、です。
> ppaul さん
Python の exec 関数は実行文脈としてオブジェクトを渡せる仕様なのでそこに巨大なデータと必要な関数群を備えたオブジェクトを渡して、サーバーとしてラップしプロセスとして立ち上げてコード送りつけたろ、って発想です。巨大なデータが読み込みのみならあわよくばマルチプロセス化も狙えるってスンポーです
インデックス(添字)を乱数で指定して読み込みを繰り返すプログラムを実行しましたが、メモリはほぼ消費されませんでした。
物理メモリへの実際のロードがセグメント単位で行われるとして、そのセグメントのサイズが小さかったために消費が少なかったという可能性は考えられますが、いまいちよく分かっていません。
こうなったら実際に導入して速度が出るかを確かめるしかないですが、そこに手を回す余裕は今はない、という状況ですので……。
また今度試してみます。
メモリへのロード回数がダメなら一回ですませばすればいいじゃないその 2、ということで tmpfs で RAM ディスク化するのを思いつきました。バッファ介さずファイル(RAM ディスク)から直接読み込むように設定すればお手軽にオンメモリ化できるのでは
あなたの回答
tips
プレビュー