【问题标题】:No benefit from Python Multi-threading in IO task?IO 任务中的 Python 多线程没有好处?
【发布时间】:2018-12-12 08:54:48
【问题描述】:

我正在尝试在 python 中读取数千小时的 wav 文件并获取它们的持续时间。这基本上需要打开 wav 文件,获取帧数并考虑采样率。下面是代码:

def wav_duration(file_name):
    wv = wave.open(file_name, 'r')
    nframes = wv.getnframes()
    samp_rate = wv.getframerate()
    duration = nframes / samp_rate
    wv.close()
    return duration


def build_datum(wav_file):
    key = "/".join(wav_file.split('/')[-3:])[:-4]
    try:
        datum = {"wav_file" : wav_file,
                "labels"    : all_labels[key],
                "duration"  : wav_duration(wav_file)}

        return datum
    except KeyError:
        return "key_error"
    except:
        return "wav_error"

按顺序执行此操作将花费太长时间。我的理解是多线程在这里应该有所帮助,因为它本质上是一个 IO 任务。因此,我就是这样做的:

all_wav_files = all_wav_files[:1000000]
data, key_errors, wav_errors = list(), list(), list()

start = time.time()

with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
    # submit jobs and get the mapping from futures to wav_file
    future2wav = {executor.submit(build_datum, wav_file): wav_file for wav_file in all_wav_files}
    for future in concurrent.futures.as_completed(future2wav):
        wav_file = future2wav[future]
        try:
            datum = future.result()
            if datum == "key_error":
                key_errors.append(wav_file)
            elif datum == "wav_error":
                wav_errors.append(wav_file)
            else:
                data.append(datum)
        except:
            print("Generated exception from thread processing: {}".format(wav_file))

print("Time : {}".format(time.time() - start))

令我沮丧的是,我得到了以下结果(以秒为单位):

Num threads | 100k wavs | 1M wavs
1           | 4.5       | 39.5
2           | 6.8       | 54.77
10          | 9.5       | 64.14
100         | 9.07      | 68.55

这是预期的吗?这是一个 CPU 密集型任务吗?多处理会有帮助吗?我怎样才能加快速度?我正在从本地驱动器读取文件,它在 Jupyter 笔记本上运行。 Python 3.5。

编辑:我知道 GIL。我只是假设打开和关闭文件本质上是 IO。 People's analysis 已经表明,在 IO 情况下,使用多处理可能会适得其反。因此我决定改用多处理。

我猜现在的问题是:这个任务IO绑定了吗?

EDIT EDIT:对于那些想知道的人,我认为它受 CPU 限制(一个核心已达到 100%)。这里的教训是不要对任务做出假设并自己检查。

【问题讨论】:

  • 请记住,如果您正在从传统(旋转)硬盘驱动器读取,一次读取多个文件可能会使速度变慢。特别是,在传统/旋转硬盘驱动器中,驱动器磁头可能需要(相对)较长的时间才能从驱动器中心的一个距离寻找到另一个距离,并从多个文件中并行读取如果它们一次只读取一个(连续)文件,则可以强制驱动器头来回搜索更多。
  • 如果从磁盘读取,这不是正确的 IO 任务。
  • @MadPhysicist 你能详细说明一下吗?
  • Python 线程是异步的,但不是并发的。当您的 io 操作是并发的(例如网络请求)时,这会有所帮助,如果不是完全的瓶颈(例如旋转磁盘),则会带来巨大的麻烦。

标签: python multithreading python-3.x multiprocessing


【解决方案1】:

按类别检查的一些事项:

代码

  • wave.open 的效率如何?当它可以简单地读取头信息时,它是否将整个文件加载到内存中?
  • 为什么 max_workers 设置为 1?
  • 您是否尝试过使用cProfile 甚至timeit 来了解代码的哪个特定部分需要更多时间?

硬件

通过一些硬盘活动、内存使用和 CPU 监控重新运行现有设置,以确认硬件不是您的限制因素。如果您看到您的硬盘以最大 IO 运行,则您的内存已满或所有 CPU 内核都达到 100% - 其中一个可能已达到极限。

全局解释器锁 (GIL)

如果没有明显的硬件限制,您很可能会遇到 Python 的全局解释器锁 (GIL) 问题,如 this answer 中所述。如果您的代码仅限于在单核上运行,或者在运行线程中没有有效的并发性,则这种行为是可以预料的。 在这种情况下,我肯定会更改为multiprocessing,首先为每个 CPU 内核创建一个进程,然后运行该进程,然后将硬件监控结果与上一次运行进行比较。

【讨论】:

  • 是的,我知道 GIL。我只是假设打开和关闭文件本质上是 IO。 People's analysis 已经表明,在 IO 情况下,使用多处理可能会适得其反。因此我决定改用多处理。我想现在的问题是:这个任务IO绑定吗?
  • 谢谢!我认为它受 CPU 限制(核心达到 100%)。这里的教训是不要对任务做出假设并自己检查。我接受这个答案。
  • 在进一步查看您的代码后,我认为问题可能在于 max_workers 设置为 1。如果您删除它,Python 会将其设置为核心数 x 5:docs.python.org/3/library/…
  • 我确实将 max_workers 更改为各种值来进行我的实验(查看表格)
  • 哦,我现在明白了,是的。很高兴你发现了限制。这些天我总是默认使用多处理,多线程似乎太古怪了。
猜你喜欢
  • 1970-01-01
  • 2020-04-09
  • 2020-06-16
  • 2021-11-09
  • 1970-01-01
  • 2015-09-03
  • 2021-02-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多