
6个库让你写出同步代码跑出异步速度摘要性能优化的新思路长期以来, 在我们谈及性能之际, 有一个无法避开的话题便是多线程。针对数据处理、网络爬取或者I/O密集型任务所面临的性能瓶颈状况, 众多开发者会下意识地去寻觅多线程方案, 冀望借助并发来加快脚本运行速度。但是, 全局解释器锁GIL以及与之相伴的上下文切换开销, 常常致使多线程代码不但没有达成预期的性能提升效果, 反倒有可能变成性能方面的“瓶颈”或者“拖累”。然而, 时代已然有所改变。当下, 存在一些极具革命性的库正促使这一状况发生转变。这些库于底层开展了深度优化工作, 一般借助Rust、C语言来予以实现, 抑或是采用高效的异步I/O模型, 如此一来, 它们的原生执行速度已然快到完全能够抵消手动管理并发的那种必要性。它们使开发者能够去编写直观、同步的代码, 却又能够以犹如“暴风雪”似的速度来处理数据。对于这六个致使作者彻底摒弃多线程的库或者工具, 本文会展开深度介绍, 并且阐释它们借助底层机制达成性能显著提升的方式, 为你呈上一条在不使代码复杂度增加的情形下, 大幅度提高性能的全新途径。一、性能瓶颈的传统解法与新挑战多线程的困境在深入知晓这些高性能库之前, 我们首先得明白为何其中的多线程并非能解决所有问题的办法。的性能硬伤GIL与上下文切换的性能并不以“原始速度”著称。这主要归咎于两个核心问题只有一个线程, 能在任何时刻于虚拟机里执行字节码这么个情况, 是由全局解释器锁GIL确保的。这就意味着, 对于那种 CPU 密集型任务, 多线程没办法借助多核优势, 反倒会因为那 GIL 的限制, 还有线程间频繁的切换, 也就是上下文切换开销, 而致使运作速度被拖慢。即便处于I/O密集型工作情形当中, 多线程尽管能够起到相应作用鉴于当一个线程处于等待I/O状态之际, GIL能够释放给另外一个线程, 然而针对线程实施管理、对数据进行同步像锁、信号量之类以及线程完成切换这其自身都会引发不容小觑的开销。以往, 在脚本运行迟缓之际, 我们常常会下意识地转向多线程, 去处理数据处理方面的事, 或是进行Web爬取, 又或是应对I/O密集型工作。然而正如同作者所感悟到的那一具有“革命性”的知识: 有的时候你压根无需依靠线程来达成速度。新时代的解决方案库内嵌的并行优化高性能库为之出现, 究其实质而言, 是把并发所具备的复杂性, 自应用层面推移至库的底层之处。这些库借由如下方式达成令人称奇之速度:下面我们将逐一介绍这六个“高性能黑科技”。二、数据分析的“一级方程式”告别的慢速困境对于那些处理大规模数据集的开发者而言, 它属于标准配置, 但是它性能方面的瓶颈也在日益明显地呈现出来。它的出现, 就好像是从一般的家庭轿车转变为F1赛车那样。它是一款借助Rust构建而成的库呀 , 并且专门是为了性能以及并行化而进行优化处理的。它的API和别的类似 , 相当容易让人上手去操作 , 不过它不需要手动去管理线程就能够达成极高的速度。为何如此之快的速度优势来源于其底层设计和实现机制简单示例自动并行化操作使用时开发者只需编写简洁的链式同步代码如下所示import polars as pl df pl.read_csv(sales.csv) result ( df.lazy() .filter(pl.col(region) Europe) .group_by(product) .agg(pl.col(revenue).sum()) .collect() )这段代码, 其执行速度, 比那采用多线程的还要快, 原因是以它而论, 在其内部, 自动实现了并行化, 针对所有操作。开发者呢, 获得了高性能, 然而情形是无需对任何线程进行管理。的应用场景三, JSON 解析, 有个被称作“闪电侠”的相关情况, 而这指的是内置 JSON 模块存在性能方面的短板。要是你应对过大型JSON文件, 或者开展过大量JSON序列化以及反序列化操作, 那你肯定深切明白内置的json模块究竟有多迟缓。它是专门为此而产生的。说的是一个采用 Rust 语言来实施的 JSON 库, 这个库的性能要好于被构建置放进去的 json 模块, 又或者更为优于叫做 ujson 的库, 它会快上二至十倍。极速背后的秘密通过以下优化实现了性能的碾压简单示例毫秒级完成大型JSON解析往昔之时, 那些需借助多线程加以解析的、规模庞大的JSON文件, 如今借助某种方式, 能够在仅仅毫秒级的时间内同步达成完成状态:import orjson data {user: Aashish, score: 99} serialized orjson.dumps(data) deserialized orjson.loads(serialized)应用场景四, 异步I/O的“增压器”, 默认事件循环存在性能局限。对于I/O密集型任务而言, 有这样一个库, 它有着内置的事件循环, 这是一个优秀的解决方案, 然而, 我们能够做得更好。它是一个用来替代事件循环的东西, 此构建正是根基附着于那些Node.js一直依存使用的同一个名称为libuv且属于C程序语言范围类库之中。的性能提升效果至作者把它替换到后端之际, 响应时间降低了百分之四十到百分之五十。完整过程并不需要增添线程, 也不需要额外代码。为何能大幅提速简单示例一行代码实现性能飞跃在仅需一行代码的情况下, 便能够把默认循环予以替换, 进而可以促使你的异步代码仿若瞬间就拥有了“涡轮增压”的效果:import asyncio import uvloop asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())“加速魔法”的应用场景五, 是PyPy运行时进行的一项, 无需改代码便操作的内容, 其实现方式不是换代码, 具体是只换跑道。有那么些时候, 能够起到提升性能作用的最终极秘诀并非是去更换一个库, 而是要去更换一个运行时环境。有一种即时编译JIT的实现叫做PyPy, 对于CPU密集型代码而言, 它能够给予2到4倍的速度提升, 甚至于更多。PyPy的加速机制适用条件假设你的代码是单纯性代码编写形式, 而且这种形式对C语言扩展这块的依赖程度并不高, 在所这样的情形之下, PyPy将会以显著的优势击败该标准意义之上的运态之时。简单示例直接运行获得速度只需用pypy3命令替换来运行你现有的脚本pypy3 my_script.py就这么简单无需多线程无需异步只有速度。PyPy的应用场景六, 函数级别的“原生指令”, 是Numba科学计算的性能利器。有个叫Numba的东西是个“作弊神器”, , 其具备那样功能, , 即能够把函数编译制成已优化后的机器码。需添加一个装饰器的开发者, 要是凭借此, 能让字节码在LLVM它是一个流行的编译器基础设施呢之下编译为优化过后的原生指令, 那就达成目的了。Numba的加速原理简单示例一个装饰器实现数十倍加速只需在函数前添加njit装饰器from numba import njit import numpy as np njit def compute(arr): total 0 for x in arr: total x ** 2 return total print(compute(np.arange(1_000_000)))运行这段代码的速度, 相较于纯的情况, 要快出数十倍。同样地, 既不需要线程, 也不需要异步, 有着的只是一个单一的装饰器。七、Numba的应用场景, 分布式计算的“无感扩展”, Ray告别需要手动去管理并发所带来的痛苦。Ray乃是一个针对分布式计算所设计而成的库, 然而即便处于单机之上, 它依旧能够提供极为高的效率。Ray, 借由直观的API, 把底层的线程, 进程等, 甚至分布式节点, 予以抽象以及隐藏起来了。Ray的效率优势瑞允许开发者去定义参与者持久化对象以及任务无状态函数, 这些任务以及参与者会被自动在多核甚至是是多台机器上面去进行扩展以及调度, 它给出了一种替换手动处理并发的方式。简单示例自动并行任务执行下面呈现出来的代码小片段儿, 能够自动以并行的方式去执行全部的任务, 开发者并不需要去操心任何有关线程的逻辑方面的事儿, 以及锁之类的情况, 还有同步这类的问题。import ray ray.init() ray.remote def square(x): return x * x results ray.get([square.remote(i) for i in range(1_000)])Ray的应用场景八、为什么是时候放弃手动多线程了多线程, 于理论层面而言, 具备强大的特性, 然而, 在实际开展操作的过程当中, 给开发以及维护方面带来了诸多的负担:竞态条件, 也就是Race, 以及让人揪心的死锁, 有着复杂得让人抓狂的调试过程, 还有GIL的限制, 以及高昂到头大的维护成本。像本文所介绍的那般, 现代的库, 尤其是那些借助Rust、C或者异步I/O的库, 把并发转化成了隐含的状态。它们把性能的改善置于库的内部实现范畴, 使得开发者能够专注于业务逻辑, 而非同步机制。简而言之: 要是库可更为出色地替你去处理并行化, 那你便没必要再亲自手工编写并行代码了。进阶组合与协同如果你想将性能提升到更高的层次可以将这些库进行组合当你的技术栈可以跟性能协同, 并非对抗性能之时, 多线程会完全沦为最后的备选, 而非必需品。九、总结速度与简洁性并行不悖过去的时候, 追求性能这件事常常就意味着要去接纳复杂性, 这种复杂性包含线程, 还有锁, 以及队列, 另外还有各种会让人感到头疼不已的问题。如今, 能够在幕后为你完成所有并发工作的是正确的工具, PyPy、Numba和Ray等六个库证实可以兼具优雅的代码结构以及闪电般的速度, 且无需以牺牲开发者的幸福感为代价。所以, 下次你察觉到脚本运行迟缓之际, 别再习惯性地抓取线程, 要去寻觅一个更具智能、更经优化的库。并非是真正的速度, 在于同时所做事情的数量, 而是在于把一件事情做到极为出色。