如何使用Pool.map()进行多处理时解决内存问题?

时间:2018-03-22 12:59:17

标签: python pandas multiprocessing threadpool python-multiprocessing

我已将程序(如下)写入:

  • 读取一个巨大的文本文件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=0

有人可以建议,我怎样摆脱这个问题?

我的python脚本:

#!/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绑定过程)结构的模块应该没问题。
  • 为了测试目的,我已经包含了两个测试文件来使用代码。
  • 附加代码是完整代码,因此它应该按照预期的方式工作,就像复制粘贴时一样。任何更改都应仅用于改进多处理步骤中的优化。

4 个答案:

答案 0 :(得分:15)

前提条件

  1. 在Python中(以下我使用64位版本的Python 3.6.5),一切都是对象。这有其开销,使用getsizeof,我们可以看到一个对象的大小,以字节为单位:

    >>> import sys
    >>> sys.getsizeof(42)
    28
    >>> sys.getsizeof('T')
    50
    
  2. 当使用fork系统调用时(默认在* nix上,请参阅multiprocessing.get_start_method())来创建子进程,不会复制父进程的物理内存,并使用copy-on-write技术。
  3. fork子进程仍将报告父进程的完整RSS(驻留集大小)。由于这个事实,PSS(比例集大小)是估计分叉应用程序的内存使用量的更合适的度量。以下是该页面的示例:
  4.   
        
    • 进程A具有50 KiB的非共享内存
    •   
    • 进程B具有300 KiB的非共享内存
    •   
    • 进程A和进程B都具有相同共享内存区域的100 KiB
    •   
         

    由于PSS被定义为进程的非共享内存和与其他进程共享的内存比例的总和,因此这两个进程的PSS如下:

         
        
    • 方法A的PSS = 50 KiB +(100 KiB / 2)= 100 KiB
    •   
    • 方法B的PSS = 300 KiB +(100 KiB / 2)= 350 KiB
    •   

    数据框

    不要只看你的DataFramememory_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
    

    我们可以看到情节:

    memory_profile

    和逐行跟踪:

    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')
    

    我们可以看到数据帧需要~2 GiB,峰值在〜3 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')(&#34;深度&#34;意味着通过询问object dtype来深入反省数据,见下文)给出:

    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的值。它们是int64object(这是一个64位指针;有关详细说明,请参阅using pandas with large data)。因此,我们仅对数据帧中的值具有134 * 10 ** 6 * 8 / 2 ** 20 ~1022 MiB。剩下的〜6.93 GiB怎么样?

    字符串实习

    要了解Python必须知道Python执行字符串实习的行为。关于Python 2中的字符串实习,有两篇好文章(onetwo)。除了Python 3中的Unicode更改和Python 3.3中的PEP 393之外,C结构已经发生了变化,但是想法是一样的。基本上,每个看起来像标识符的短字符串都将由Python在内部字典中缓存,引用将指向相同的Python对象。换句话说,我们可以说它表现得像一个单身人士。我上面提到的文章解释了它给出的重要内存配置文件和性能改进。我们可以使用PyASCIIObject的{​​{3}}字段检查字符串是否被实习:

    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
    

    使用两个字符串,我们也可以进行身份​​比较(在CPython的情况下在内存比较中解决)。

    >>> a = 'foo'
    >>> b = 'foo'
    >>> a is b
    True
    >> gen_matrix_df.REF[0] is gen_matrix_df.REF[6]
    True
    

    由于这个事实,关于object dtype,数据框最多分配20个字符串(每个氨基酸一个)。但是,值得注意的是,Pandas建议interned进行枚举。

    熊猫记忆

    因此,我们可以解释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个字节,而不是50个,因为我们在上面看到1个字符的文字。这是因为PEP 393定义了紧凑和非紧凑的字符串。您可以使用sys.getsizeof(gen_matrix_df.REF[0])进行检查。

    gen_matrix_df.info()报告的实际内存消耗应为~1 GiB,它的两倍。我们可以假设它与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原作者的categorical types引用来完成本节。

      

    pandas经验法则:RAM的数量是数据集大小的5到10倍

    流程树

    最后,让我们来到池中,看看是否可以使用写时复制。我们将使用fresh article about design issues and future Pandas2(可从Ubuntu存储库获得)来估计进程组内存共享,并使用smemstat来记下系统范围的可用内存。两者都可以编写JSON。

    我们将使用Pool(2)运行原始脚本。我们需要3个终端窗口。

    1. smemstat -l -m -p "python3.6 script.py" -o smemstat.json 1
    2. glances -t 1 --export-json glances.json
    3. mprof run -M script.py
    4. 然后mprof plot产生:

      glances

      总和图表(mprof run --nopython --include-children ./script.py)如下所示:

      3 processes

      请注意,上面的两个图表显示了RSS。假设是由于写时复制,它并不能反映实际的内存使用情况。现在我们有两个来自smemstatglances的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内存。

      enter image description here

      第一和最小之间的差异是~4.15 GiB。以下是PSS数据的样子:

      enter image description here

      总和:

      enter image description here

      因此我们可以看到,由于写时复制实际内存消耗约为4.15 GiB。但我们仍然会将数据序列化,以便通过Pool.map将其发送给工作进程。我们也可以在这里利用写时复制吗?

      共享数据

      要使用copy-on-write,我们需要全局访问list(gen_matrix_df_list.values()),因此fork之后的worker仍然可以读取它。

      1. 让我们在del gen_matrix_df main之后修改代码,如下所示:

        ...
        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)))
        ...
        
      2. 删除稍后的del gen_matrix_df_list
      3. 修改matrix_to_vcf的第一行,如:

        def matrix_to_vcf(i):
            matrix_df = global_gen_matrix_df_values[i]
        
      4. 现在让我们重新运行它。自由记忆:

        enter image description here

        流程树:

        free

        及其总和:

        process tree

        因此,我们实际内存使用量的最大值为2.9 GiB(构建数据帧时的峰值主进程)并且写入时复制有所帮助!

        作为旁注,有所谓的copy-on-read,Python的参考周期垃圾收集器sum的行为(导致gc.freeze在{ {3}})。但gc.disable()在这种特殊情况下并没有产生影响。

        更新

        写入时复制无副本数据共享的替代方法可以是使用described in Instagram Engineering从头开始将其委派给内核。来自 Python中的高性能数据处理的对话issue31558。然后numpy.memmap使Pandas使用mmaped Numpy数组。

答案 1 :(得分:4)

我有同样的问题。我需要处理一个巨大的文本语料库,同时保留内存中加载数百万行的少数数据帧的知识库。我认为这个问题很常见,所以我会将答案放在一般用途上。

组合设置为我解决了这个问题(1&amp; 3&amp; 5只能为你做到):

  1. 使用Pool.imap(或imap_unordered)代替Pool.map。这将在开始处理之前懒惰地迭代数据,而不是将所有数据加载到内存中。

  2. 将值设置为chunksize参数。这也会使imap更快。

  3. 将值设置为maxtasksperchild参数。

  4. 将输出附加到磁盘而不是内存。当它达到一定大小时立即或每次。

  5. 以不同批次运行代码。如果您有迭代器,则可以使用itertools.islice。我们的想法是将您的list(gen_matrix_df_list.values())拆分为三个或更多列表,然后将前三个列表仅传递给mapimap,然后将另一个三分之一传递给另一个列表,等等。因为您有一个列表,你可以简单地将它切成相同的代码行。

答案 2 :(得分:4)

使用multiprocessing.Pool时,将使用fork()系统调用创建许多子进程。这些进程中的每一个都以当时父进程的内存的精确副本开始。因为您在创建大小为3的Pool之前正在加载csv,所以池中的这3个进程中的每个进程都将不必要地拥有数据帧的副本。 (gen_matrix_df以及gen_matrix_df_list将存在于当前进程以及3个子进程中的每一个中,因此每个结构的4个副本将存在于内存中)

尝试在加载文件之前创建Pool(实际上是开头)这会减少内存使用量。

如果它仍然太高,你可以:

  1. 将gen_matrix_df_list转储到一个文件中,每行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)
    
  2. 在迭代器上使用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)元组,而不只是一个值)

  3. 我希望有所帮助。

    注意:我还没有测试过上面的代码。它只是为了证明这个想法。

答案 3 :(得分:2)

关于多处理存储的一般答案

你问:“导致分配的内存是多少”。答案取决于两个部分。

首先,正如您已经注意到的,每个multiprocessing工作人员都获得了自己的数据副本(引用from here),所以你应该大块的争论。或者对于大文件,如果可能的话,一次一点地读取它们。

  

默认情况下,池中的worker是分叉的真正Python进程   使用Python标准库的多处理模块时   n_jobs!= 1.作为并行调用的输入传递的参数是   序列化并重新分配在每个工作进程的内存中。

     

这对于大型论证来说可能会有问题   工人重新分配了n_jobs次。

第二个,如果您正在尝试回收内存,则需要了解python的工作方式与其他语言不同,并且您依赖于del to release the memory when it doesn't 。我不知道它是否最好,但在我自己的代码中,我已经克服了这个问题,将变量重新分配给None或空对象。

针对您的具体示例 - 最小代码编辑

只要您可以将大数据放在内存中两次,我认为只需更改一行即可完成您要执行的操作。我编写了非常相似的代码,当我重新分配变量(副调用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

针对您的具体示例 - 最佳内存使用

只要您可以将大数据放入内存一次,并且您对文件的大小有所了解,就可以使用 Pandas read_csv部分文件读取,如果您真的想要微观管理正在读取的数据量,或者[使用chunksize一次使用固定数量的内存],则读入only nrows at a time,这将返回迭代器{{3} }}。我的意思是,nrows参数只是一个读取:您可以使用它来查看文件,或者如果由于某种原因您希望每个部分具有完全相同的行数(例如,因为如果您的任何数据是可变长度的字符串,则每行不会占用相同数量的内存)。但我认为,为了准备多处理文件,使用块会更容易,因为这与内存直接相关,这是您关注的问题。试用和试用会更容易基于特定大小的块而不是行数来适应内存的错误,这将根据行中的数据量来更改内存使用量。唯一困难的部分是,由于某些应用程序特定的原因,您正在对某些行进行分组,因此它只是使它更复杂一点。以您的代码为例:

   '''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()