Python 3.13自由线程速度提升探索:能用于 CPU 密集型任务吗?

图片

随着 Python 3.13 的发布,我想看看 Python 即将发生的最大变化,我认为到目前为止最令人兴奋的功能是来自 PEP-703 的自由线程 Python,因为它让Python抛弃GIL成为可能,大大提升了python性能。

在 Python 3.13 之前,由于 GIL 的原因,线程用于 IO 绑定任务,Asyncio 也用于 IO,我们可以使用 asyncio.to_thread 包装线程。例如:

await asyncio.to_thread(io_bound_task, "first_arg", optional="optional")

但我们可以将它用于 CPU 密集型任务吗?以下是直接从 Asyncio 文档中提取的引述:

注意 由于 GIL 的原因,asyncio.to_thread() 通常只能用于使 IO 绑定的函数成为非阻塞函数,但是,对于发布 GIL 的扩展模块或没有 GIL 的替代 Python 实现,asyncio.to_thread() 也可用于 CPU 绑定的函数,唯一阻止我们的是 GIL,因此 CPU 密集型任务应该不是问题,但是 AsyncIO 这个名字仍然让人感觉有点傻。

以下是使用 AsyncIO 测试脚本:

import argparseimport osimport sysimport timefrom asyncio import get_running_loop, run, to_thread, TaskGroupfrom concurrent.futures import ThreadPoolExecutorfrom contextlib import contextmanager

@contextmanagerdef timer():    start = time.time()    yield    print(f"Elapsed time: {time.time() - start}")

def cpu_bound_task(n):    """A CPU-bound task that computes the sum of squares up to n."""    return sum(i * i for i in range(n))

async def main():    parser = argparse.ArgumentParser(description="Run a CPU-bound task with threads")    parser.add_argument("--threads", type=int, default=4, help="Number of threads")    parser.add_argument("--tasks", type=int, default=10, help="Number of tasks")    parser.add_argument(        "--size", type=int, default=5000000, help="Task size (n for sum of squares)"    )    args = parser.parse_args()
    get_running_loop().set_default_executor(ThreadPoolExecutor(max_workers=args.threads))
    with timer():        async with TaskGroup() as tg:            for _ in range(args.tasks):                tg.create_task(to_thread(cpu_bound_task, args.size))

if __name__ == "__main__":    print("Parallel with Asyncio")    print(f"GIL {sys._is_gil_enabled()}")  # type: ignore    run(main())

我在 M3 Macbook Pro 上运行了有和没有 GIL的对比:

不带GIL(python3.13版本)

 python parallel_asyncio.pyParallel with AsyncioGIL FalseElapsed time: 0.5552260875701904

带GIL运行:

  python parallel_asyncio.pyParallel with AsyncioGIL TrueElapsed time: 1.6787209510803223

结果正如预期的那样,当我们使用 AsyncIO 并发运行代码时,我们观察到了我们期望的结果,速度上的确进行了提升。

来源:python运维技术

THE END