【问题标题】:How to parallelize this nested loop in python如何在python中并行化这个嵌套循环
【发布时间】:2017-04-26 05:01:30
【问题描述】:

我正在尝试提高代码的性能,但不知道如何在其中实现多处理模块。

我使用的是 linux (CentOS 7.2) 和 python 2.7

我需要在并行环境中运行的代码:

def start_fetching(directory):
    with open("test.txt", "a") as myfile:
        try:
            for dirpath, dirnames, filenames in os.walk(directory):
                for current_file in filenames:
                    current_file = dirpath + "/" + current_file
                    myfile.write(current_file)
            return 0
        except:
            return sys.exc_info()[0]

if __name__ == "__main__":
    cwd = "/home/"
    final_status = start_fetching(cwd)
    exit(final_status)

我需要将所有文件的元数据(这里只显示文件名)保存在数据库中。这里我只是将文件名存储在文本文件中。

【问题讨论】:

  • 每次在第二个for 循环中执行某项操作时,您只需创建一个新的Thread。和往常一样。 docs.python.org/2/library/threading.html#thread-objects
  • this 可能会有所帮助!
  • 尝试同时从多个线程追加到一个文件通常不是一个好主意。
  • @AndreyShipilov 谢谢,我会试试看是否有帮助。
  • 您可以尝试找出它,因为它取决于您的系统配置。但是根据您的描述,您正在写入数据库。在这里,数据库几乎总是瓶颈(除非它在内存中)。您可以尝试并行化写入,但这并不意味着数据库可以扩展并处理争用。在“HPC 集群”上运行它并不一定意味着并行性应该总是更快。

标签: python python-2.7 nested-loops python-multiprocessing


【解决方案1】:

我猜你想要并行化很大的任务。您所提供的只是文件名中的文件。 我为每个线程输出创建了一个单独的文件,稍后您也可以组合所有这些文件。还有其他方法可以实现这一点。

如果主要问题是并行化,下面可能是一个解决方案。

Python 支持多线程和多处理。多线程并不是真正的并行处理,在 IO 块的情况下,我们可以进行并行执行。如果您想要并行代码,请使用多处理[https://docs.python.org/2/library/multiprocessing.html]。您的代码可能如下所示。

from multiprocessing import Process

def task(filename):
    with open(filename+"test.txt", "a") as myfile:
         myfile.write(filename)

def start_fetching(directory):
    try:
        processes = []
        for dirpath, dirnames, filenames in os.walk(directory):
            for current_file in filenames:
                current_file = dirpath + "/" + current_file
                # Create Seperate process and do what you want, becausee Multi-threading wont help in parallezing
                p = Process(target=f, args=(current_file,))
                p.start()
                processes.append(p)

        # Let all the child processes finish and do some post processing if needed.
        for process in processes:
            process.join()

        return 0
    except:
        return sys.exc_info()[0] 

if __name__ == "__main__":
    cwd = "/home/"
    final_status = start_fetching(cwd)
    exit(final_status)

【讨论】:

  • 您能详细说明一下吗?
  • 你启动一个进程然后立即等待它完成,这里没有并行运行。
  • 看起来好多了
  • 谢谢@ArunKumar,请检查我刚刚发布的答案。
【解决方案2】:

感谢大家帮助我将这个脚本的处理时间减少到几乎一半。 (我将其添加为答案,因为我无法在评论中添加这么多内容)

我找到了两种方法来实现我的愿望:

  1. 使用@KeerthanaPrabhakaran 提到的与多线程有关的this 链接。

    def worker(filename):
        subprocess_out = subprocess.Popen(["stat", "-c",
                                   "INSERT INTO file VALUES (NULL, \"%n\", '%F', %s, %u, %g, datetime(%X, 'unixepoch', 'localtime'), datetime(%Y, 'unixepoch', 'localtime'), datetime(%Z, 'unixepoch', 'localtime'));", filename], stdout=subprocess.PIPE)
        return subprocess_out.communicate()[0]
    
    def start_fetching(directory, threads):
        filename = fetch_filename() + ".txt"
        with contextlib.closing(multiprocessing.Pool(threads)) as pool:   # pool of threads processes
            with open(filename, "a") as myfile:
                walk = os.walk(directory)
                fn_gen = itertools.chain.from_iterable((os.path.join(root, file) for file in files) for root, dirs, files in walk)
    
                results_of_work = pool.map(worker, fn_gen)  # this does the parallel processing
                print "Concatenating the result into the text file"
                for result in results_of_work:
                    myfile.write(str(result))
        return filename
    

    这是在 0m15.154s 内遍历 15203 个文件。

  2. @ArunKumar 提到的第二个与多处理有关:

    def task(filename, process_no, return_dict):
        subprocess_out = subprocess.Popen(["stat", "-c",
                                   "INSERT INTO file VALUES (NULL, \"%n\", '%F', %s, %u, %g, datetime(%X, 'unixepoch', 'localtime'), datetime(%Y, 'unixepoch', 'localtime'), datetime(%Z, 'unixepoch', 'localtime'));",
                                   filename], stdout=subprocess.PIPE)
        return_dict[process_no] = subprocess_out.communicate()[0]
    
    
    def start_fetching_1(directory):
        try:
            processes = []
            i = 0
            manager = multiprocessing.Manager()
            return_dict = manager.dict()
    
            for dirpath, dirnames, filenames in os.walk(directory):
                for current_file in filenames:
                    current_file = dirpath + "/" + current_file
                    # Create Seperate process and do what you want, becausee Multi-threading wont help in parallezing
                    p = multiprocessing.Process(target=task, args=(current_file, i, return_dict))
                    i += 1
                    p.start()
                    processes.append(p)
    
            # Let all the child processes finish and do some post processing if needed.
            for process in processes:
                process.join()
    
            with open("test.txt", "a") as myfile:
                myfile.write(return_dict.values())
    
            return 0
        except:
            return sys.exc_info()[0]
    

    这是在 1m12.197s 内遍历 15203 个文件

我不明白为什么多处理要花这么多时间(我的初始代码只用了 0m27.884s),但占用了几乎 100% 的 CPU。

以上代码是我正在运行的确切代码,(我将这些信息存储在一个文件中,然后使用这些 test.txt 文件来创建数据库条目)

我正在尝试进一步优化上面的代码,但想不出更好的方法,正如@CongMa 所说,它可能最终会遇到 I/O 瓶颈。

【讨论】:

  • 总结——遍历15203个文件:顺序实现耗时0m27.884s,多线程耗时0m15.154s,多进程耗时1m12.197s。它是逻辑多线程比顺序执行更好,因为任务有很多 I/O。并且由于创建的进程数(15203 个文件),多进程表现不佳。因为,我们为每个文件创建了一个进程并分配了一个单独的任务。进程的创建确实很昂贵,而且 15K 进程确实很大,CPU 将被挤在这些进程之间进行调度。虽然它与创建线程不同。
  • 我就是这么想的。我计划在更多的文件(x10^4 次)上运行这个脚本。你认为它会稳定在那个水平吗?
  • 多进程不应该是如此大规模并行的方法。数量的好处是
猜你喜欢
  • 1970-01-01
  • 2017-06-20
  • 1970-01-01
  • 2020-09-27
  • 1970-01-01
  • 2020-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多