Python高效编程3.0版:迈向高性能之路与未来展望

2周前发布 gsjqwyl
12 0 0

1.4 怎样成为高性能程序员

打造高性能代码只是长期成功项目中实现高性能的一部分。团队因素远比单纯提升速度和复杂解决方案重要得多。这里有几个关键要素——良好的架构、文档、可调试性以及统一的标准。

假设你创建了一个原型,却没有进行全面测试,也没让团队审核。它看似“足够好”就被投入生产。由于代码未以结构化方式编写,缺乏测试和文档。接着,又会有需要他人维护的拖沓代码出现,而管理层往往难以量化团队为此付出的成本。

这类难以维护的解决方案通常会被搁置——既未被重构,也没有测试来助力团队重构,没人愿意接手,只能靠一名开发者维持运转。这在压力大时可能造成可怕的瓶颈,引发重大风险:若这名开发者离开项目,该怎么办?

通常,若管理团队不了解难以维护代码导致的持续拖沓,就会出现这种开发模式。要证明从长远看,测试和文档能助力团队保持高效,并说服管理者花时间“清理”这类原型代码。

在研究环境中,迭代各种思路和不同数据集时,常用糟糕编码实践创建众多Jupyter笔记本。我们总想着后续“把它写好”,但往往未能实现。最终,虽得到可行结果,却缺少重现、测试和信任结果的架构。同样,风险因素高,对结果的信任度低。

以下是对你有帮助的通用方法:

    1. 让其运转起来
      首先,构建一个足够好的解决方案。打造一个“权宜”方案作为原型很明智,可用于在第二版中采用更好架构。编码前做些前期规划总是明智的,不然会出现“我们花一下午编码节省一小时思考时间”的情况。在某些领域,这被称作“两次测量,一次裁剪”。
    1. 确保正确
      接下来,添加强大的测试套件,提供文档和明确的可重现说明,以便其他团队成员能接手。这也是探讨代码意图、方案挑战及构建工作版本注意事项的好时机。当需要重构、修复或重建代码时,对未来团队成员有帮助。
    1. 快速开发
      最后,可专注于剖析、编译或并行化,并用现有测试套件确认新的更快解决方案是否仍按预期运行。

1.4.1 良好工作实践

有几个“必备项”——文档、良好架构和测试是关键。

一些项目级文档有助于保持简洁架构,未来也能帮到你和同事。若跳过这部分,没人会感激你(包括你自己)。将这部分写入顶层README文件是明智起点,如需可后续扩展到docs/文件夹。

解释项目目的、文件夹内容、数据来源、关键文件以及如何运行所有文件,包括如何运行测试。

对于临时存储有用命令、函数默认值或其他使用代码的智慧、技巧,NOTES文件也是不错选择。虽然这些信息最好放入文档,但在其(希望)进入文档前,有个记录板保存这些信息,对不遗漏重要小细节很有价值。

Micha还建议使用Docker。一个顶级Dockerfile能向未来的自己解释,为让项目成功运行需从操作系统获取哪些库,还能消除在其他机器运行代码或部署到云环境的难题。通常,接手新代码时,仅让其运行起来就可能是大障碍,Dockerfile能消除此障碍,让其他开发者立即与你的代码互动。

添加tests/文件夹并添加一些单元测试。我们更倾向使用pytest作为现代测试运行器,因其基于Python内置unittest。开始时只需几个测试,逐步构建。进而使用覆盖率工具,它会报告测试实际覆盖的代码行数——有助于避免意外。

若接手传统代码且缺乏测试,一项高价值活动是在前期添加一些测试。一些“集成测试”可检查项目整体流程,确认通过特定输入数据能否得到特定输出结果,这有助于后续修改时保持理智。

每当代码出问题,就添加一个测试。重复被同一问题困扰无意义。

在代码中为每个函数、类和模块添加文档说明,这对你总是有帮助。尽量对函数实现功能进行有用描述,尽可能包含简短示例演示预期输出。若想获取灵感,可参考numpy和scikit-learn的文档。

每当代码过长(例如函数长度超一屏),就可重构代码使其更短。较短代码更易测试和维护。

开发测试时,可考虑遵循测试驱动开发方法。当明确知道要开发什么,且手头有可测试示例时,此方法很高效。

你可编写测试、运行测试、观察测试失败,然后添加函数及必要最小逻辑以支持所写测试。所有测试正常运行时,就大功告成。通过提前确定函数预期输入和输出,会发现实现函数逻辑相对简单。

若不能提前定义测试,自然会有问题:你真的了解函数需要做什么吗?若不了解,能高效正确编写吗?若处于创作过程且研究不太了解的数据,此方法不太适用。

一定要使用版本控制——只有在关键内容未被覆盖时才会感谢自己。养成频繁提交习惯(每天甚至每10分钟提交一次),并每天推送到版本库。

遵守PEP 8编码标准。更好的是,在提交前的版本控制钩子上使用black(有主见的代码格式化工具),它能帮你按标准重写代码。使用flake8润色代码以避免其他错误。

创建与操作系统隔离的环境会让工作更轻松。Ian喜欢用Anaconda,Micha喜欢将pyenv与virtualenv结合使用,或直接用Docker。两者都是明智解决方案,比使用操作系统全局Python环境好得多!

记住,自动化是你的帮手。减少手工操作意味着减少出错机会。自动构建系统、与自动测试套件运行程序的持续集成以及自动部署系统将乏味易错任务转化为任何人都能运行支持的标准流程。建立持续集成工具包(如代码提交到代码库时自动运行测试)绝非浪费时间,因这会加快简化未来开发。

“艰苦探索”是我们的学习方式——若将思维外包给GenAI系统,总会得到答案,有时可能正确。通过创造性找出自己的解决方案,你将继续在头脑中构建新模式,而非强化已存在于更广阔世界中的模式。这对你有益。举个简单例子,GitHub Copilot为Ian写了个简单正则表达式——但那不是Ian会写的,所以花了点时间才弄明白。事后看,若多思考会儿,写出“符合Ian思考方式”的解决方案,而非引入随机网络打字员的方案,会更快。

早期项目间建立库是节省复制粘贴解决方案的好方法。复制粘贴代码片段有诱惑,因快速,但久而久之会有一系列略有不同但基本相同的解决方案,每个都很少或无测试,会有更多错误和边缘情况影响工作。有时,退一步找机会编写第一个库,能为团队带来重大胜利。

最后,记住可读性远比“聪明”重要。对自己和同事而言,短小复杂难读的代码片段难维护,人们会害怕接触。取而代之,编写较长、易读的函数,用有用文档说明将返回什么,辅以测试确认是否如期望运行。

1.4.2 优化团队而非代码块

构建解决方案时有很多会浪费时间的方法。最坏情况,也许你研究的是错误问题或用错方法;也许你走在正确道路上,但开发过程中有些杂事拖慢进度;也许你未估算出可能阻碍前进的真实成本和不确定因素。又或,你误解利益相关者需求,花时间构建了实际不存在的功能或解决了问题。

确保解决的是有用问题至关重要。找到有尖端技术和大量酷炫缩略语的酷项目可能有趣,但不太可能带来其他成员欣赏的价值。若所在组织试图带来积极变化,应将重点放在起阻碍作用的问题上,因解决这些问题会带来明显积极结果。

找到潜在有用问题后,值得反思——我们能实现有意义变革吗?仅修复问题背后的“技术”不能改变现实世界。解决方案需部署维护,需被人类用户采用。若技术解决方案遇阻力障碍,工作将无成果。

确定这些阻力不令人担忧后,你是否估算了能产生的潜在影响?若发现问题某部分能产生100倍影响,很棒!这部分问题对组织日常工作有意义吗?若你能对每年仅几小时的问题产生100倍影响,这项工作(很可能)无用。若你能对每天都伤害团队的问题改进1%,你就是英雄。

估算所提供价值的一种方法是思考当前状态成本和未来状态潜在收益(当写出解决方案时)。如何量化成本和改进?将估算与金钱挂钩(因“时间就是金钱”,我们都在消耗时间)是找出能产生何种影响及如何传达给同事的好方法,也是对潜在项目选项优先排序的好方法。

找到有用且有价值问题后,需确保以合理方式解决。遇到棘手问题立即决定用棘手解决方案可能明智,但从简单解决方案入手,了解其有效或无效原因,能迅速获得宝贵见解,为解决方案后续迭代提供参考。怎样最快最简单学到有用东西?

Ian曾与一些客户合作,其NLP管道近乎复杂,但对实际运作信心不足。经审查,发现一个团队建立复杂系统,却忽略上游数据标注不完善问题,而该问题困扰着NLP ML流程。通过改用简单得多的解决方案(不用深度神经网络,用老式NLP工具),发现问题所在并一致重新标注数据;只有这样,才能建立更复杂解决方案,因上游问题已合理消除。

你的团队是否向利益相关者清楚传达成果?团队内部沟通是否清晰?缺乏沟通易给团队进步带来令人沮丧代价。

回顾协作实践,检查诸如频繁代码审查等流程是否到位。忽略代码审查易“节省时间”,却忘了让同事(和自己)带着未经审查的代码离开,而这些代码可能解决错误问题,或可能含错误,可在产生更严重影响前被新眼睛发现。

1.4.3 远程办公

自COVID-19大流行以来,我们见证向完全远程和混合实践转变。虽有些组织试图让团队回到现场,但因最佳实践已被合理理解,大多数采用混合或完全远程实践。

远程意味着可生活在任何地方,招聘和合作者范围更广,无论是限于相似时区还是完全不受时区限制。一些组织已注意到,Python、pandas、scikit-learn等开源项目与分布全球、成员很少见面的团队合作很成功。

加强沟通至关重要,且往往需发展“文档优先”文化。有些团队甚至说,“若我们聊天工具(如Slack)上没记录,那就没发生过”——意味着每个决定最终要写下来,以便交流和搜索。

长期完全远程工作易让人感到孤立。定期与团队成员交流(即使不在同一项目),以及在无安排时间进行更高层次交流(或只是聊聊生活!),对感受团队联系和自己是团队一员很重要。

1.4.4 关于良好笔记本实践的一些想法

若使用Jupyter笔记本,可能发现其非常适合可视化交流,但也易让人偷懒。若发现自己在笔记本中留下冗长函数,可将其提取到Python模块中并添加测试。
在IPython或QtConsole中练习原型化代码;将代码行转化为Notebook中的函数,然后从Notebook中提取转化为模块并辅以测试。最后,若封装和数据隐藏有用,可考虑用类封装代码。

在Notebook中大量使用断言语句检查函数是否按预期运行。在Notebook中测试代码不易,在将函数重构为独立模块前,assert检查是简单方法,可增加一定程度验证。在将代码提取到模块并编写合理单元测试前,不应信任这些代码。

使用断言语句检查代码中数据不可取。断言某些条件满足是简单方法,但不是Python惯用方式。为让其他开发者易读代码,检查预期数据状态,然后在检查失败时引发适当异常。若函数遇意外值,常见异常是ValueError。Pandera库是测试框架示例,重点在Pandas和Polars,用于检查数据是否符合指定约束。

可能还想在Notebook结尾添加一些合理性检查——逻辑检查、raise和print的混合,以证明刚生成的正是所需内容。六个月后再看这段代码,会感谢自己让它易看出自始至终正确运行!

使用Notebook的一个困难是如何与版本控制系统共享代码,它是助你与同事协作的救星。

1.5 Python的未来

在本文撰写时,有两个有趣进展——全局解释器锁最终可能被移除,同时可能添加即时编译器。实验工作在进行,但目前不清楚这些变化将如何影响Python解释器未来生产版本。

1.5.1 GIL

如在“计算单元”所讨论,全局解释器锁(GIL: global interpreter lock)是标准内存锁定机制,不幸的是,它会使多线程代码以最差单线程速度运行。GIL作用是确保一次只能有一个线程修改一个Python对象,因此若程序中多个线程试图修改同一对象,实际每次只能改一个。

这大大简化Python早期设计,但随着处理器数量增加,编写多核代码负担加重。GIL是Python引用计数垃圾回收机制核心部分。

2023年,Python决定研究构建无GIL版本Python,除长期使用的GIL构建外,该版本仍支持线程。由于第三方库(如NumPy、Pandas、scikit-learn)编译的C代码依赖当前GIL实现,外部库需进行一些代码调整以支持Python两种构建,并在较长时期内转向无GIL构建。没人希望重蹈Python 2向Python 3过渡10年覆辙!

Python增强提案PEP 703以科学和人工智能应用为重点描述该提案。该领域主要问题是,在CPU密集型代码和10 – 100个线程情况下,GIL开销会大幅减少并行化机会。若改用本书介绍的标准解决方案(如多处理),会带来大量开发者开销和通信开销。这些方案不付出巨大努力无法最大限度利用计算机资源。

本PEP指出需控制非原子对象修改问题,以及新的线程安全小对象内存分配器。
我们可期待无GIL版本Python从2028年开始普遍可用——若过程中无重大阻碍。

1.5.2 JIT吗

从Python 3.13开始,预计几乎所有人使用的主CPython将内置即时编译器(JIT: just-in-time)。
这种JIT遵循2021年一种设计,称为“复制和修补”,最早用于Lua语言。相比之下,在PyPy和Numba等技术中,分析器会发现速度较慢代码段(又称热点),然后编译机器代码版本,将该代码块与机器CPU可用专用功能匹配。这样能获非常快代码,但编译过程早期可能较昂贵。
“复制和修补”过程与对比方法略有不同。构建Python可执行文件时(通常由Python软件基金会构建),LLVM编译器工具链用于构建一组预定义“模板”。这些模板是Python虚拟机中关键操作码半编译版本。称为“模板”是因有“漏洞”,稍后会填上。
运行时,发现热点——通常是数据类型不变的循环,就可用一组与操作码匹配的模板,通过粘贴相关变量内存地址填补“漏洞”。这有望比编译每个识别的热点快得多;可能不是最佳,但希望不进行缓慢分析编译也能带来显著收益。

在Python主要版本中,JIT实现经历几个演变阶段:

  • 3.11引入自适应类型专用解释器,速度提高10 – 25%。
  • 3.12引入内部清理和特定领域语言来创建解释器以便构建时修改。
  • 3.13引入热点检测器,利用复制和修补JIT在专用类型上构建。
    值得注意的是,虽然Python 3.13中引入JIT是一大进步,但不太可能影响我们的Pandas、NumPy和SciPy代码,因内部这些库通常用C和Cython预编更快解决方案。JIT将对编写本地Python,尤其是数值Python的人产生影响。

1.6 小结

我们已了解Python底层组件及其更广泛生态系统,思考了如何用Python高效开发科学代码,触及当前Python核心语言一些变化,这些变化未来几年可能从根本上提高Python效率。现在,来了解剖析,以便快速找出瓶颈,将时间集中在正确之处。
“`

© 版权声明

相关文章

暂无评论

暂无评论...