【问题标题】:No increase in speed when multithreading python hdf5 parsing function多线程python hdf5解析函数时速度没有提升
【发布时间】:2013-04-24 12:34:27
【问题描述】:

我有一个函数:

1) 以整数 ascii 码的形式读入 hdf5 数据集

2) 将 ascii 整数转换为字符...chr() 函数

3) 将字符连接成单个字符串函数

在进行分析时,我发现绝大多数计算都花在了第 2 步上,即将 ascii 整数转换为字符。我使用以下方法对这个调用进行了一些优化:

''.join([chr(x) for x in file[dataSetName].value])

由于我的解析函数似乎受 cpu 限制(整数到字符的转换)而不是 i/o 限制,我希望通过专用于解析的内核数量获得或多/少的线性速度增强。连续解析一个文件需要约 15 秒...解析 10 个文件(在我的 12 核机器上)需要约 150 秒,同时使用 10 个线程。也就是说,似乎根本没有增强。

我使用以下代码来启动我的线程:

    threads=[]
    timer=[]
    threadNumber=10
    for i,d in enumerate(sortedDirSet):
        timer.append(time.time())
     #   self.loadFile(d,i)
        threads.append(Thread(target=self.loadFileargs=(d,i)))
        threads[-1].start()
        if(i%threadNumber==0):
            for i2,t in enumerate(threads):
                t.join()
                print(time.time()-timer[i2])
            timer=[]
            threads=[]

    for t in threads:
        t.join()

任何帮助将不胜感激。

【问题讨论】:

标签: python multithreading hdf5 h5py


【解决方案1】:

除非您生成子进程(例如 multiprocessing),否则 Python 不能使用多个内核(由于 GIL)。因此,您不会通过为 CPU 密集型任务生成线程而获得任何性能提升。


这是一个使用multiprocessingqueue 的脚本示例:

from Queue import Empty # <-- only needed to catch Exception
from multiprocessing import Process, Queue, cpu_count

def loadFile(d, i, queue):
    # some other stuff
    queue.put(result)

if name == "main":
    queue = Queue()
    no = cpu_count()
    processes = []

    for i,d in enumerate(sortedDirSet):
        p = Process(target=self.loadFile, args=(d, i, queue))
        p.start()
        processes.append(p)

        if i % no == 0:
            for p in processes:
                p.join()
            processes = []

    for p in processes:
        p.join()

    results = []
    while True:
        try:
            # False means "don't wait when Empty, throw an exception instead"
            data = queue.get(False)
            results.append(data)
        except Empty:
            break

    # You have all the data, do something with it

另一种(更复杂的)方法是使用pipe 而不是queue

生成进程,然后创建一个作业队列并将它们(通过pipe)发送到子进程(这样您不必每次都创建一个进程)也会更有效。但这会更复杂,所以就这样吧。

【讨论】:

  • 爆炸。 GIL 充分利用了多线程 95% 的用处。起初我尝试使用 multiprocessing.imap,但我发现很难解决 freeze_support 错误。同样,我找不到在我的类方法中使用 name == 'main' 的正确方法。有什么提示吗?
  • @PaulD 好吧,Python 最初并不是为 CPU 密集型任务而设计的。但是线程可以很好地处理 I/O 绑定任务。我已经更新了答案(我会这样做,但显然取决于你的上下文)。
  • 谢谢你的例子。我遇到的问题是 1)我的进程生成点不在 main 中……它在类方法中。同样,派生方法利用类成员变量。我想在这种情况下我不能使用多处理?
  • @PaulD 你可以,只是它变得更复杂了。当您运行脚本(在主程序中)时,您始终可以创建一个进程池,然后将数据发送给它。但这需要使用管道,并且您将需要在每个子进程中都有一个侦听器线程,并且一旦主进程退出,您就必须注意杀死该池。如您所见,它变得讨厌。所以也许用其他语言实现这段代码并使用subprocess module会更容易?或者可能是 C 插件?
  • 确实很复杂...我经常用 C++ 编程。我为这个项目选择使用/学习 python 是因为它所谓的快速原型设计。叹。缺乏足够的多线程几乎迫使我切换回 matlab 及其方便的 parfor 循环。
【解决方案2】:

Freakish 的回答是正确的,这将是 GIL 阻碍你的努力。

如果您要使用 python 3,您可以使用 concurrent.futures 很好地做到这一点。我相信 PyPy 也向后移植了这个功能。

此外,您可以通过替换列表理解来加快代码速度:

''.join([chr(x) for x in file[dataSetName].value])

有地图:

''.join(map(chr, file[dataSetName].value))

我使用上述代码的测试(在大量随机列表上)显示使用列表理解为 15.73 秒,使用 map 为 12.44 秒。

【讨论】:

    猜你喜欢
    • 2017-04-24
    • 1970-01-01
    • 2011-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-22
    相关资源
    最近更新 更多