Python 从业十年是种什么体验?老程序员的一篇万字经验分享( 五 )

<< 26):pass@async_timerasync def coroutine_task:"""异步协程调用"""await asleep(1)@async_timerasync def coroutine_error:"""会抛出异常的协程调用"""raise AttributeError("yo")@async_timerasync def coroutine_main:ioloop = get_event_loopr = await gather(coroutine_task,coroutine_error,ioloop.run_in_executor(thread_executor, io_blocking_task),ioloop.run_in_executor(process_executor, cpu_blocking_task),return_exceptions=True,)logger.info(f"coroutine_main got {r}")@timerdef main:get_event_loop.run_until_complete(coroutine_main)if __name__ == "__main__":main学到这一步 , 你已经能够熟练的运用协程、线程、进程处理不同类型的任务 。 接着拿上面提到的垃圾 4 核虚机举例 , 你现在应该可以比较轻松的实现达到 1k QPS 的服务 , 在白天十小时里可以处理超过一亿请求 , 费用依然仅 20元/天 。 你还有什么借口说是因为 Python 慢呢?
人们在聊到语言/框架/工具性能时 , 考虑的是“当程序员尽可能的优化后 , 工具性能会成为最终的瓶颈 , 所以我们一定要选一个最快的” 。
但事实上是 , 程序员本身才是性能的最大瓶颈 , 而工具真正体现出来的价值 , 是在程序员很烂时 , 所能提供的兜底性能 。
如果你觉得自己并不是那个瓶颈 , 那也没必要来听我讲了
在性能优化上有两句老话:

  • 一定要针对瓶颈做优化
  • 过早优化是万恶之源
所以我觉得要开放、冷静地看待工具的性能 。 在一套完整的业务系统中 , 框架工具往往是耗时占比最低的那个 , 在扩容、缓存技术如此发达的今天 , 你已经很难说出工具性能不够这样的话了 。
成长的空间很大 , 多在自己身上找原因 。
一个经验观察 , 即使在工作中不断的实际练习 , 对于异步协程这种全新的思维模式 , 从学会到能在工作中熟练运用且不犯大错 , 比较聪明的人也需要一个月 。
换成 go 也不会好很多 , await 也能实现同步写法 , 而且你依然需要面对我前文提到过的同步控制和资源用量两个核心问题 。
简单提一下性能分析 , py 可以利用 cProfile、line_profiler、memory_profiler、vprof、objgraph 等工具生成耗时、内存占用、调用关系图、火焰图等 。
关于性能分析领域的更多方法论和理念 , 推荐阅读《性能之巅》(过去做的关于性能之巅的部分摘抄 ) 。
必须强调:优化必须要有足够的数据支撑 , 包括优化前和优化后 。
性能优化其实是一个非常复杂的领域 , 虽然上面提到的工具可以生成各式各样的看上去就很厉害的图 , 但是优化不是简单的你看哪慢就去改哪 , 而是需要有极其扎实的基础知识和全局思维的 。
而且 , 上述工具得出的指标 , 在性能尚未逼近极限时 , 可能会有相当大的误导性 , 使用的时候也要小心 。
有一些较为普适的经验:
  • I/O 越少越好 , 尽量在内存里完成
  • 内存分配越少越好 , 尽量复用
  • 变量尽可能少 , gc 友好
  • 尽量提高局部性
  • 尽量用内建函数 , 不要轻率造轮子
下列方法如非瓶颈不要轻易用:
  • 循环展开
  • 内存对齐
  • zero copy(mmap、sendfile)
测试是开发人员很容易忽视的一个环节 , 很多人认为交给 QA 即可 , 但其实测试也是开发过程中的一个重要组成部分 , 不但可以提高软件的交付质量 , 还可以增进你的代码组织能力 。
最常见的划分可以称之为黑盒 & 白盒 , 前者是只针对接口行为的测试 , 后者是深入了解实现细节 , 针对实现方式进行的针对性测试 。
对 Py 开发者而言 , 最简单实用的工具就是 unitest.TestCase 和 pytest , 在包内任何以 test*.py 命名的文件 , 内含 TestCase 类的以 test* 命名的方法都会被执行 。
测试方法也很简单 , 你给定入参 , 然后调用想要测试的函数 , 然后检查其返回是否符合需求 , 不符合就抛出异常 。
"""test_demo.py"""from unittest import TestCasefrom typing import Listdef demo(l: List[int]) -> int:return l[0]class DemoTestCase(TestCase):def setUp(self):print("first run")def tearDown(self):print("last run")def test_demo(self):data = http://kandian.youth.cn/index/self.assertRaises(IndexError, demo, data)
Python 从业十年是种什么体验?老程序员的一篇万字经验分享文章插图
开始写测试后 , 你才会意识到你的很多函数非常难以测试 。 因为它们可能有嵌套调用 , 可能有内含状态 , 可能有外部依赖等等 。
但是需要强调的是 , 这不但不是不写测试的理由 , 这其实正是写测试的目的!