多处理时间随着更多核心

时间:2017-04-10 23:23:48

标签: python multiprocessing arcpy

我有一个arcpy进程,需要在一堆层上进行联合,运行一些计算并编写HTML报告。鉴于我需要生成的报告数量(~2,100),我需要尽可能快地完成此过程(我的目标是每个报告2秒)。当我遇到一个问题时,我尝试了很多方法来实现这一点,包括多处理,即运行多进程部分基本上需要相同的时间,无论我使用多少核心。

例如,对于相同数量的报告:

  • 每轮2个内核需要约30秒(因此40个报告需要40/2 * 30秒)
  • 4芯需要~60秒(40/4 * 60)
  • 10个核心耗时约160秒(40/10 * 160)

等等。它可以达到相同的总时间,因为每次搅拌两倍的时间需要两倍的时间。

这是否意味着我的问题是I / O绑定,而不是CPU绑定?(如果是这样 - 我该怎么办呢?)我会认为这是后者,鉴于我的时间安排的大瓶颈是联合(它占用了大约50%的处理时间)。在ArcGIS中,工会通常很昂贵,因此我假设将其分解并立即以2 - 10运行,速度将提高2到10倍。或者,我可能错误地实施了多进程?

## Worker function just included to give some context

def worker(sub_code):
    layer = 'in_memory/lyr_{}'.format(sub_code)
    arcpy.Select_analysis(subbasinFC, layer, where_clause="SUB_CD = '{}'".format(sub_code))
    arcpy.env.extent = layer
    union_name = 'in_memory/union_' + sub_code

    arcpy.Union_analysis([fields],
                     union_name,
                     "NO_FID", "1 FEET")
    #.......Some calculations using cursors

    # Templating using Jinjah
    context = {}
    context['DATE'] = now.strftime("%B %d, %Y")
    context['SUB_CD'] = sub_code
    context['SUB_ACRES'] = sum([r[0] for r in arcpy.da.SearchCursor(union, ["ACRES"], where_clause="SUB_CD = '{}'".format(sub_code))])
    # Etc

    # Then write the report out using custom function
    write_html('template.html', 'output_folder', context)


if __name__ == '__main__':
    subList = sorted({r[0] for r in arcpy.da.SearchCursor(subbasinFC, ["SUB_CD"])})
    NUM_CORES = 7
    chunk_list = [subList[i:i+NUM_CORES] for i in range(0, len(subList), NUM_CORES-1)]
    for chunk in chunk_list:
        jobs = []
        for subbasin in chunk:
            p = multiprocessing.Process(target=worker, args=(subbasin,))
            jobs.append(p)
            p.start()

        for process in jobs:
            process.join()

3 个答案:

答案 0 :(得分:3)

此处没有太多内容,我没有使用ArcGIS的经验。所以我可以注意到两个更高层次的事情。首先,“通常”的方法是将NUM_CORES = 7下面的所有代码替换为:

pool = multiprocessing.Pool(NUM_CORES)
pool.map(worker, subList)
pool.close()
pool.join()

map()负责保持所有工作进程尽可能繁忙。按原样,您启动7个进程,然后等待所有完成。在最慢的消失之前完成的所有进程,以及它们的核心闲置等待下一个外循环迭代。 Pool使这7个进程在作业期间保持活动状态,并在完成最后一项工作后立即为每个进程提供新的工作。

其次,这部分以逻辑错误结束:

chunk_list = [subList[i:i+NUM_CORES] for i in range(0, len(subList), NUM_CORES-1)]

您希望NUM_CORES而不是NUM_CORES-1。原样,第一次提取

subList[0:7]

然后

subList[6:13]

然后

subList[12:19]

等等。 subList[6]subList[12](等)每次提取两次。子列表重叠。

答案 1 :(得分:1)

你没有给我们足够的信息以确定你在做什么。例如,您的env.workspace是什么? subbasinFC的价值是多少?您似乎在每个流程开始时进行分析,将数据过滤到layer。但subbasinFC是来自磁盘,还是来自内存?如果它来自磁盘,我建议您在任何进程尝试过滤之前将所有内容都读入内存。如果你有记忆支持它,这应该可以加快速度。否则,是的,您对输入数据的I / O限制。

请原谅我的arcpy无能为力,但为什么要在context['SUB_ACRES']的总和中插入where子句?你没有在开始时对sub_code进行过滤吗? (我们不知道工会是什么,所以也许你正在与未经过滤的事情结合......)

答案 2 :(得分:0)

我不确定您是否正确使用Process池来跟踪您的工作。这样:

for subbasin in chunk:
    p = multiprocessing.Process(target=worker, args=(subbasin,))
    jobs.append(p)
    p.start()

    for process in jobs:
        process.join()

应改为:

for subbasin in chunk:
    p = multiprocessing.Process(target=worker, args=(subbasin,))
    p.start()
    p.join()

您是否有针对spec of using the multiprocessing library的具体原因?你不会等到线程终止之前再旋转另一个进程,这只会创建一大堆不由父调用进程处理的进程。