Pool.map()を使用してマルチプロセッシング中にメモリの問題を解決するにはどうすればよいですか?
以下にプログラムを作成しました:
- 巨大なテキストファイルを_
pandas dataframe
_として読み取ります - 次に、特定の列値を使用してデータを分割し、データフレームのリストとして保存する
groupby
。 - 次に、データを
multiprocess Pool.map()
にパイプして、各データフレームを並列処理します。
すべてが正常で、プログラムは私の小さなテストデータセットでうまく機能します。しかし、大きなデータ(約14 GB)をパイプすると、メモリ消費が指数関数的に増加し、コンピューターがフリーズするか、(HPCクラスターで)停止します。
データ/変数が役に立たなくなったらすぐにメモリをクリアするコードを追加しました。また、プールが完成したらすぐに閉鎖します。それでも14 GBの入力では、2 * 14 GBのメモリ負荷しか期待していませんでしたが、多くのことが進行しているようです。また、_chunkSize and maxTaskPerChild, etc
_を使用して微調整を試みましたが、テストファイルと大きなファイルの両方で最適化に違いは見られません。
multiprocessing
を起動するとき、このコード位置でこのコードの改善が必要だと思います。
p = Pool(3) # number of pool to run at once; default at 1 result = p.map(matrix_to_vcf, list(gen_matrix_df_list.values()))
が、コード全体を投稿しています。
テスト例:最大250 mbのテストファイル( "genome_matrix_final-chr1234-1mb.txt")を作成し、プログラムを実行しました。システムモニターを確認すると、メモリ消費が約6 GB増加していることがわかります。 250 MBのファイルといくつかの出力によって、なぜそんなに多くのメモリ空間が使用されるのか、私はそれほど明確ではありません。実際の問題を確認するのに役立つ場合は、ドロップボックスを介してそのファイルを共有しました。 https://www.dropbox.com/sh/coihujii38t5prd/AABDXv8ACGIYczeMtzKBo0eea?dl=
誰かが、どのように問題を取り除くことができますか?
My python script:
_#!/home/bin/python3
import pandas as pd
import collections
from multiprocessing import Pool
import io
import time
import resource
print()
print('Checking required modules')
print()
''' change this input file name and/or path as need be '''
genome_matrix_file = "genome_matrix_final-chr1n2-2mb.txt" # test file 01
genome_matrix_file = "genome_matrix_final-chr1234-1mb.txt" # test file 02
#genome_matrix_file = "genome_matrix_final.txt" # large file
def main():
with open("genome_matrix_header.txt") as header:
header = header.read().rstrip('\n').split('\t')
print()
time01 = time.time()
print('starting time: ', time01)
'''load the genome matrix file onto pandas as dataframe.
This makes is more easy for multiprocessing'''
gen_matrix_df = pd.read_csv(genome_matrix_file, sep='\t', names=header)
# now, group the dataframe by chromosome/contig - so it can be multiprocessed
gen_matrix_df = gen_matrix_df.groupby('CHROM')
# store the splitted dataframes as list of key, values(pandas dataframe) pairs
# this list of dataframe will be used while multiprocessing
gen_matrix_df_list = collections.OrderedDict()
for chr_, data in gen_matrix_df:
gen_matrix_df_list[chr_] = data
# clear memory
del gen_matrix_df
'''Now, pipe each dataframe from the list using map.Pool() '''
p = Pool(3) # number of pool to run at once; default at 1
result = p.map(matrix_to_vcf, list(gen_matrix_df_list.values()))
del gen_matrix_df_list # clear memory
p.close()
p.join()
# concat the results from pool.map() and write it to a file
result_merged = pd.concat(result)
del result # clear memory
pd.DataFrame.to_csv(result_merged, "matrix_to_haplotype-chr1n2.txt", sep='\t', header=True, index=False)
print()
print('completed all process in "%s" sec. ' % (time.time() - time01))
print('Global maximum memory usage: %.2f (mb)' % current_mem_usage())
print()
'''function to convert the dataframe from genome matrix to desired output '''
def matrix_to_vcf(matrix_df):
print()
time02 = time.time()
# index position of the samples in genome matrix file
sample_idx = [{'10a': 33, '10b': 18}, {'13a': 3, '13b': 19},
{'14a': 20, '14b': 4}, {'16a': 5, '16b': 21},
{'17a': 6, '17b': 22}, {'23a': 7, '23b': 23},
{'24a': 8, '24b': 24}, {'25a': 25, '25b': 9},
{'26a': 10, '26b': 26}, {'34a': 11, '34b': 27},
{'35a': 12, '35b': 28}, {'37a': 13, '37b': 29},
{'38a': 14, '38b': 30}, {'3a': 31, '3b': 15},
{'8a': 32, '8b': 17}]
# sample index stored as ordered dictionary
sample_idx_ord_list = []
for ids in sample_idx:
ids = collections.OrderedDict(sorted(ids.items()))
sample_idx_ord_list.append(ids)
# for haplotype file
header = ['contig', 'pos', 'ref', 'alt']
# adding some suffixes "PI" to available sample names
for item in sample_idx_ord_list:
ks_update = ''
for ks in item.keys():
ks_update += ks
header.append(ks_update+'_PI')
header.append(ks_update+'_PG_al')
#final variable store the haplotype data
# write the header lines first
haplotype_output = '\t'.join(header) + '\n'
# to store the value of parsed the line and update the "PI", "PG" value for each sample
updated_line = ''
# read the piped in data back to text like file
matrix_df = pd.DataFrame.to_csv(matrix_df, sep='\t', index=False)
matrix_df = matrix_df.rstrip('\n').split('\n')
for line in matrix_df:
if line.startswith('CHROM'):
continue
line_split = line.split('\t')
chr_ = line_split[0]
ref = line_split[2]
alt = list(set(line_split[3:]))
# remove the alleles "N" missing and "ref" from the alt-alleles
alt_up = list(filter(lambda x: x!='N' and x!=ref, alt))
# if no alt alleles are found, just continue
# - i.e : don't write that line in output file
if len(alt_up) == 0:
continue
#print('\nMining data for chromosome/contig "%s" ' %(chr_ ))
#so, we have data for CHR, POS, REF, ALT so far
# now, we mine phased genotype for each sample pair (as "PG_al", and also add "PI" tag)
sample_data_for_vcf = []
for ids in sample_idx_ord_list:
sample_data = []
for key, val in ids.items():
sample_value = line_split[val]
sample_data.append(sample_value)
# now, update the phased state for each sample
# also replacing the missing allele i.e "N" and "-" with ref-allele
sample_data = ('|'.join(sample_data)).replace('N', ref).replace('-', ref)
sample_data_for_vcf.append(str(chr_))
sample_data_for_vcf.append(sample_data)
# add data for all the samples in that line, append it with former columns (chrom, pos ..) ..
# and .. write it to final haplotype file
sample_data_for_vcf = '\t'.join(sample_data_for_vcf)
updated_line = '\t'.join(line_split[0:3]) + '\t' + ','.join(alt_up) + \
'\t' + sample_data_for_vcf + '\n'
haplotype_output += updated_line
del matrix_df # clear memory
print('completed haplotype preparation for chromosome/contig "%s" '
'in "%s" sec. ' %(chr_, time.time()-time02))
print('\tWorker maximum memory usage: %.2f (mb)' %(current_mem_usage()))
# return the data back to the pool
return pd.read_csv(io.StringIO(haplotype_output), sep='\t')
''' to monitor memory '''
def current_mem_usage():
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024.
if __name__ == '__main__':
main()
_
バウンティハンターの更新:
Pool.map()
を使用してマルチプロセッシングを達成しましたが、コードにより大きなメモリ負荷が発生しています(入力テストファイル〜300 mb、メモリ負荷は約6 GB)。最大で3 * 300 mbのメモリ負荷しか期待していませんでした。
- 誰かが説明できますか、このような小さなファイルとそのような小さな長さの計算のためにそのような巨大なメモリ要件を引き起こしているのは何ですか?.
- また、私は答えを取り、それを使用して私の大きなプログラムのマルチプロセスを改善しようとしています。したがって、メソッドの追加、計算部分(CPUバウンドプロセス)の構造をあまり変更しないモジュールは問題ありません。
- コードで遊ぶために、テスト目的で2つのテストファイルを含めました。
- 添付コードは完全なコードであるため、コピーアンドペーストした場合に意図したとおりに動作するはずです。変更は、マルチプロセッシングステップの最適化を改善するためにのみ使用してください。
前提条件
Python(以下ではPython 3.6.5の64ビットビルドを使用)では、すべてがオブジェクトです。これにはオーバーヘッドがあり、
getsizeof
を使用すると、オブジェクトのサイズをバイト単位で正確に確認できます。>>> import sys >>> sys.getsizeof(42) 28 >>> sys.getsizeof('T') 50
- Forkシステムコールを使用して(* nixのデフォルト、
multiprocessing.get_start_method()
を参照)子プロセスを作成すると、親の物理メモリはコピーされず、 copy-on-write テクニックが使用されます。 - Fork子プロセスは、親プロセスの完全なRSS(常駐セットサイズ)を引き続き報告します。このため、 [〜#〜] pss [〜#〜] (比例セットサイズ)は、フォークアプリケーションのメモリ使用量を推定するためのより適切なメトリックです。このページの例を次に示します。
- プロセスAには50 KiBの非共有メモリがあります
- プロセスBには300 KiBの非共有メモリがあります
- プロセスAとプロセスBの両方に、同じ共有メモリ領域の100 KiBがあります
PSSはプロセスの非共有メモリと他のプロセスと共有されるメモリの割合の合計として定義されるため、これら2つのプロセスのPSSは次のようになります。
- プロセスAのPSS = 50 KiB +(100 KiB/2)= 100 KiB
- プロセスBのPSS = 300 KiB +(100 KiB/2)= 350 KiB
データフレーム
DataFrame
だけを見てはいけません。 memory_profiler
が役立ちます。
justpd.py
#!/usr/bin/env python3
import pandas as pd
from memory_profiler import profile
@profile
def main():
with open('genome_matrix_header.txt') as header:
header = header.read().rstrip('\n').split('\t')
gen_matrix_df = pd.read_csv(
'genome_matrix_final-chr1234-1mb.txt', sep='\t', names=header)
gen_matrix_df.info()
gen_matrix_df.info(memory_usage='deep')
if __name__ == '__main__':
main()
プロファイラを使用してみましょう。
mprof run justpd.py
mprof plot
プロットを見ることができます:
および行ごとのトレース:
Line # Mem usage Increment Line Contents
================================================
6 54.3 MiB 54.3 MiB @profile
7 def main():
8 54.3 MiB 0.0 MiB with open('genome_matrix_header.txt') as header:
9 54.3 MiB 0.0 MiB header = header.read().rstrip('\n').split('\t')
10
11 2072.0 MiB 2017.7 MiB gen_matrix_df = pd.read_csv('genome_matrix_final-chr1234-1mb.txt', sep='\t', names=header)
12
13 2072.0 MiB 0.0 MiB gen_matrix_df.info()
14 2072.0 MiB 0.0 MiB gen_matrix_df.info(memory_usage='deep')
データフレームは、構築中に〜3 GiBにピークがあり、最大2 GiBになることがわかります。さらに興味深いのは、 info
の出力です。
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4000000 entries, 0 to 3999999
Data columns (total 34 columns):
...
dtypes: int64(2), object(32)
memory usage: 1.0+ GB
ただし、info(memory_usage='deep')
(「深い」とは、object
dtype
sを調べることによりデータを深くイントロスペクションすることです。以下を参照してください):
memory usage: 7.9 GB
え?プロセスの外側を見ると、memory_profiler
の数字が正しいことを確認できます。 sys.getsizeof
もフレームに同じ値を表示します(おそらくカスタム__sizeof__
が原因です)。また、それを使用して割り当てられたgc.get_objects()
を推定する他のツールも表示されます。 pympler
。
# added after read_csv
from pympler import tracker
tr = tracker.SummaryTracker()
tr.print_diff()
与える:
types | # objects | total size
================================================== | =========== | ============
<class 'pandas.core.series.Series | 34 | 7.93 GB
<class 'list | 7839 | 732.38 KB
<class 'str | 7741 | 550.10 KB
<class 'int | 1810 | 49.66 KB
<class 'dict | 38 | 7.43 KB
<class 'pandas.core.internals.SingleBlockManager | 34 | 3.98 KB
<class 'numpy.ndarray | 34 | 3.19 KB
では、これらの7.93 GiBはどこから来たのでしょうか?これを説明してみましょう。 4M行と34列があり、134Mの値が得られます。それらはint64
またはobject
(64ビットポインターです。詳細な説明については sing pandas with large data のいずれかです。したがって、データフレームの値に対してのみ134 * 10 ** 6 * 8 / 2 ** 20
〜1022 MiBがあります。残りの〜6.93 GiBはどうですか?
ストリングインターン
動作を理解するには、Pythonが文字列インターンを行うことを知る必要があります。 Python 2.の文字列インターンについて、2つの良い記事( one 、 two )があります。Python 3および PEP 39 in Python 3.3 C構造が変更されましたが、考え方は同じです。基本的に、識別子のように見える短い文字列はすべて内部辞書のPythonによってキャッシュされ、参照は同じPythonオブジェクトを指します。つまり、シングルトンのように振る舞うと言えます。上記の記事では、メモリプロファイルの重要性とパフォーマンスの向上について説明しています。 interned
の PyASCIIObject
フィールドを使用して、文字列がインターンされているかどうかを確認できます。
import ctypes
class PyASCIIObject(ctypes.Structure):
_fields_ = [
('ob_refcnt', ctypes.c_size_t),
('ob_type', ctypes.py_object),
('length', ctypes.c_ssize_t),
('hash', ctypes.c_int64),
('state', ctypes.c_int32),
('wstr', ctypes.c_wchar_p)
]
次に:
>>> a = 'name'
>>> b = '!@#$'
>>> a_struct = PyASCIIObject.from_address(id(a))
>>> a_struct.state & 0b11
1
>>> b_struct = PyASCIIObject.from_address(id(b))
>>> b_struct.state & 0b11
0
2つの文字列を使用して、ID比較を行うこともできます(CPythonの場合のメモリ比較で対処します)。
>>> a = 'foo'
>>> b = 'foo'
>>> a is b
True
>> gen_matrix_df.REF[0] is gen_matrix_df.REF[6]
True
そのため、object
dtype
に関して、データフレームは最大20個の文字列(アミノ酸ごとに1つ)を割り当てます。ただし、Pandasは列挙に categorical types を推奨していることに注意してください。
パンダの記憶
したがって、次のように7.93 GiBの単純な推定値を説明できます。
>>> rows = 4 * 10 ** 6
>>> int_cols = 2
>>> str_cols = 32
>>> int_size = 8
>>> str_size = 58
>>> ptr_size = 8
>>> (int_cols * int_size + str_cols * (str_size + ptr_size)) * rows / 2 ** 30
7.927417755126953
str_size
は58バイトであり、1文字のリテラルで上で見たように50バイトではないことに注意してください。 PEP 393がコンパクト文字列と非コンパクト文字列を定義しているためです。 sys.getsizeof(gen_matrix_df.REF[0])
で確認できます。
gen_matrix_df.info()
によって報告されるように、実際のメモリ消費量は〜1 GiBである必要があります。これは2倍です。 PandasまたはNumPyによって行われたメモリ(事前)割り当てと関係があると想定できます。次の実験は、理由がないわけではないことを示しています(複数の実行で保存画像が表示されます)。
Line # Mem usage Increment Line Contents
================================================
8 53.1 MiB 53.1 MiB @profile
9 def main():
10 53.1 MiB 0.0 MiB with open("genome_matrix_header.txt") as header:
11 53.1 MiB 0.0 MiB header = header.read().rstrip('\n').split('\t')
12
13 2070.9 MiB 2017.8 MiB gen_matrix_df = pd.read_csv('genome_matrix_final-chr1234-1mb.txt', sep='\t', names=header)
14 2071.2 MiB 0.4 MiB gen_matrix_df = gen_matrix_df.drop(columns=[gen_matrix_df.keys()[0]])
15 2071.2 MiB 0.0 MiB gen_matrix_df = gen_matrix_df.drop(columns=[gen_matrix_df.keys()[0]])
16 2040.7 MiB -30.5 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
...
23 1827.1 MiB -30.5 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
24 1094.7 MiB -732.4 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
25 1765.9 MiB 671.3 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
26 1094.7 MiB -671.3 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
27 1704.8 MiB 610.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
28 1094.7 MiB -610.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
29 1643.9 MiB 549.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
30 1094.7 MiB -549.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
31 1582.8 MiB 488.1 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
32 1094.7 MiB -488.1 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
33 1521.9 MiB 427.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
34 1094.7 MiB -427.2 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
35 1460.8 MiB 366.1 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
36 1094.7 MiB -366.1 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
37 1094.7 MiB 0.0 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
...
47 1094.7 MiB 0.0 MiB gen_matrix_df = gen_matrix_df.drop(columns=[random.choice(gen_matrix_df.keys())])
このセクションは、Pandasの原作者による 設計の問題と将来のPandas2についての最新記事 からの引用で終了します。
パンダの経験則:データセットのサイズの5〜10倍のRAM
プロセスツリー
最後にプールに行き、コピーオンライトを利用できるかどうかを確認しましょう。 smemstat
(Ubuntuリポジトリから入手可能)を使用してプロセスグループのメモリ共有を推定し、 glances
を使用してシステム全体の空きメモリを書き留めます。 。どちらもJSONを記述できます。
Pool(2)
を使用して元のスクリプトを実行します。 3つのターミナルウィンドウが必要です。
smemstat -l -m -p "python3.6 script.py" -o smemstat.json 1
glances -t 1 --export-json glances.json
mprof run -M script.py
次に、mprof plot
は以下を生成します。
合計チャート(mprof run --nopython --include-children ./script.py
)は次のようになります。
上記の2つのグラフはRSSを示していることに注意してください。仮説は、コピーオンライトのため、実際のメモリ使用量を反映していないということです。これで、smemstat
とglances
からの2つのJSONファイルができました。 JSONファイルをCSVに変換する次のスクリプトを作成します。
#!/usr/bin/env python3
import csv
import sys
import json
def smemstat():
with open('smemstat.json') as f:
smem = json.load(f)
rows = []
fieldnames = set()
for s in smem['smemstat']['periodic-samples']:
row = {}
for ps in s['smem-per-process']:
if 'script.py' in ps['command']:
for k in ('uss', 'pss', 'rss'):
row['{}-{}'.format(ps['pid'], k)] = ps[k] // 2 ** 20
# smemstat produces empty samples, backfill from previous
if rows:
for k, v in rows[-1].items():
row.setdefault(k, v)
rows.append(row)
fieldnames.update(row.keys())
with open('smemstat.csv', 'w') as out:
dw = csv.DictWriter(out, fieldnames=sorted(fieldnames))
dw.writeheader()
list(map(dw.writerow, rows))
def glances():
rows = []
fieldnames = ['available', 'used', 'cached', 'mem_careful', 'percent',
'free', 'mem_critical', 'inactive', 'shared', 'history_size',
'mem_warning', 'total', 'active', 'buffers']
with open('glances.csv', 'w') as out:
dw = csv.DictWriter(out, fieldnames=fieldnames)
dw.writeheader()
with open('glances.json') as f:
for l in f:
d = json.loads(l)
dw.writerow(d['mem'])
if __name__ == '__main__':
globals()[sys.argv[1]]()
まず、free
メモリを見てみましょう。
最初と最小の違いは〜4.15 GiBです。そして、PSSの数値は次のようになります。
そして合計:
したがって、コピーオンライトのため、実際のメモリ消費量は約4.15 GiBであることがわかります。ただし、Pool.map
経由でワーカープロセスに送信するために、データをシリアル化しています。ここでもコピーオンライトを活用できますか?
共有データ
コピーオンライトを使用するには、list(gen_matrix_df_list.values())
をグローバルにアクセスできるようにする必要があります。これにより、fork後のワーカーはそれを読み取ることができます。
main
のdel gen_matrix_df
の後のコードを次のように変更しましょう。... global global_gen_matrix_df_values global_gen_matrix_df_values = list(gen_matrix_df_list.values()) del gen_matrix_df_list p = Pool(2) result = p.map(matrix_to_vcf, range(len(global_gen_matrix_df_values))) ...
- 後で行く
del gen_matrix_df_list
を削除します。 そして、
matrix_to_vcf
の最初の行を次のように変更します。def matrix_to_vcf(i): matrix_df = global_gen_matrix_df_values[i]
次に、再実行してみましょう。空きメモリ:
プロセスツリー:
そしてその合計:
したがって、実際のメモリ使用量は最大で約2.9 GiBであり(データフレームの構築中にメインプロセスがピークに達します)、コピーオンライトが役立ちました!
補足説明として、Pythonのリファレンスサイクルガベージコレクターの動作であるコピーオンリードと呼ばれる Instagram Engineeringで説明されています ( issue31558 でgc.freeze
に至りました)。しかし、gc.disable()
はこの特定の場合には影響を与えません。
更新
コピーオンライトコピーレスデータ共有の代わりに、 numpy.memmap
を使用して最初からカーネルに委任することができます。Pythonの高性能データ処理talkの 実装例 があります。 トリッキーな部分 は、Pandasを作成して、mmaped Numpy配列を使用します。
同じ問題がありました。数百万行のDataFramesのナレッジベースをメモリにロードしながら、巨大なテキストコーパスを処理する必要がありました。この問題は一般的なものだと思うので、一般的な目的のために回答の方向性を維持します。
設定のcombinationが問題を解決しました(1&3&5のみがあなたのためにそれを行うかもしれません):
_
Pool.imap
_の代わりに_imap_unordered
_(または_Pool.map
_)を使用します。これは、処理を開始する前にすべてのデータをメモリにロードするよりも遅延してデータを繰り返し処理します。chunksize
パラメーターに値を設定します。これにより、imap
も高速になります。maxtasksperchild
パラメーターに値を設定します。メモリよりもディスクに出力を追加します。瞬時に、または一定のサイズに達するたびに。
異なるバッチでコードを実行します。イテレータがある場合は、 itertools.islice を使用できます。アイデアは
list(gen_matrix_df_list.values())
を3つ以上のリストに分割し、最初の3分の1をmap
またはimap
にのみ、次に2番目の3番目を別の実行などに渡すことです。 。リストがあるので、同じコード行に単純にスライスできます。
_multiprocessing.Pool
_を使用すると、fork()
システムコールを使用して多数の子プロセスが作成されます。これらの各プロセスは、その時点での親プロセスのメモリの正確なコピーから始まります。サイズ3のPool
を作成する前にcsvをロードしているため、プール内のこれら3つのプロセスのそれぞれには、データフレームのコピーが不必要にあります。 (_gen_matrix_df
_と_gen_matrix_df_list
_は、現在のプロセスと3つの子プロセスのそれぞれに存在するため、これらの各構造の4つのコピーがメモリ内にあります)
ファイルをロードする前に(実際には最初に)Pool
を作成してみてください。これにより、メモリ使用量が削減されます。
それでも高すぎる場合は、次のことができます。
Gen_matrix_df_listをファイルにダンプします(1行に1アイテム、例:
_
import os import cPickle with open('tempfile.txt', 'w') as f: for item in gen_matrix_df_list.items(): cPickle.dump(item, f) f.write(os.linesep)
_このファイルにダンプした行に対して、イテレーターで
Pool.imap()
を使用します。例:_
with open('tempfile.txt', 'r') as f: p.imap(matrix_to_vcf, (cPickle.loads(line) for line in f))
_(上記の例では、値だけでなく_
matrix_to_vcf
_が_(key, value)
_タプルを取ることに注意してください)
それがお役に立てば幸いです。
NB:上記のコードはテストしていません。これは、アイデアを示すことのみを目的としています。
マルチプロセッシングによるメモリに関する一般的な回答
あなたは尋ねました:「これほど多くのメモリが割り当てられる原因は何ですか」。答えは2つの部分に依存しています。
最初、すでにお気づきのように、各multiprocessing
ワーカーは独自のデータのコピーを取得します(引用符付き- ここから )、したがって、大きな引数をチャンクする必要があります。または、大きなファイルの場合は、可能であれば一度に少しずつ読んでください。
デフォルトでは、プールのワーカーはn_jobs!= 1のときにPython標準ライブラリのマルチプロセッシングモジュールを使用して分岐された実際のPythonプロセスです。Parallel呼び出しへの入力として渡される引数は各ワーカープロセスのメモリ。
これは、ワーカーによってn_jobs回再割り当てされるため、大きな引数では問題になる可能性があります。
Second、メモリを再生しようとする場合、pythonは他の言語とは異なる動作をすること、およびに依存していることを理解する必要があります- delが解放されないときにメモリを解放する。最善かどうかはわかりませんが、自分のコードでは、変数をNoneまたは空のオブジェクトに再割り当てすることでこれを克服しました。
特定の例-最小コード編集
大きなデータをメモリに収まる限りtwiceとすれば、1行変更するだけで目的の処理を実行できると思います。私は非常に似たコードを書いており、変数を再割り当てしたときに機能しました(副呼び出しdelまたはあらゆる種類のガベージコレクト)。これが機能しない場合は、上記の提案に従ってディスクI/Oを使用する必要がある場合があります。
#### earlier code all the same
# clear memory by reassignment (not del or gc)
gen_matrix_df = {}
'''Now, pipe each dataframe from the list using map.Pool() '''
p = Pool(3) # number of pool to run at once; default at 1
result = p.map(matrix_to_vcf, list(gen_matrix_df_list.values()))
#del gen_matrix_df_list # I suspect you don't even need this, memory will free when the pool is closed
p.close()
p.join()
#### later code all the same
特定の例-最適なメモリ使用量
メモリに大きなデータを収めることができる限りonceで、ファイルの大きさについてある程度知っている場合は、Pandas read_csv 部分的なファイルの読み取り、読み取る 一度にnrowsのみ 読み取っているデータの量を実際にマイクロ管理する場合、または[チャンクサイズ]を使用して、一度に一定量のメモリを割り当て、イテレータを返します 5 。つまり、nrowsパラメーターは1回の読み取りにすぎません。これを使用して、ファイルを覗くだけでよいし、何らかの理由で各パーツにまったく同じ行数を持たせたい場合もあります(たとえば、データのいずれかが可変長の文字列である場合、各行は同じ量のメモリを消費しません。しかし、マルチプロセッシング用にファイルを準備する目的では、チャンクを使用する方がはるかに簡単になると思います。チャンクはメモリに直接関係しているため、懸念事項です。行数よりも特定のサイズのチャンクに基づいてメモリに収まるように試行錯誤を使用する方が簡単です。行のデータ量に応じてメモリ使用量が変化します。他の唯一の難しい部分は、アプリケーション固有の理由により、いくつかの行をグループ化しているため、少し複雑になることです。コードを例として使用する:
'''load the genome matrix file onto pandas as dataframe.
This makes is more easy for multiprocessing'''
# store the splitted dataframes as list of key, values(pandas dataframe) pairs
# this list of dataframe will be used while multiprocessing
#not sure why you need the ordered dict here, might add memory overhead
#gen_matrix_df_list = collections.OrderedDict()
#a defaultdict won't throw an exception when we try to append to it the first time. if you don't want a default dict for some reason, you have to initialize each entry you care about.
gen_matrix_df_list = collections.defaultdict(list)
chunksize = 10 ** 6
for chunk in pd.read_csv(genome_matrix_file, sep='\t', names=header, chunksize=chunksize)
# now, group the dataframe by chromosome/contig - so it can be multiprocessed
gen_matrix_df = chunk.groupby('CHROM')
for chr_, data in gen_matrix_df:
gen_matrix_df_list[chr_].append(data)
'''Having sorted chunks on read to a list of df, now create single data frames for each chr_'''
#The dict contains a list of small df objects, so now concatenate them
#by reassigning to the same dict, the memory footprint is not increasing
for chr_ in gen_matrix_df_list.keys():
gen_matrix_df_list[chr_]=pd.concat(gen_matrix_df_list[chr_])
'''Now, pipe each dataframe from the list using map.Pool() '''
p = Pool(3) # number of pool to run at once; default at 1
result = p.map(matrix_to_vcf, list(gen_matrix_df_list.values()))
p.close()
p.join()