如题,联动一下自己去年发的帖吧。
Python 因为本身比较慢,我觉得应该大多数程序员,跟我一样,写东西时都有最优化实现的需求。典型场景比如生产环境遇到连接两个大的字节流,用那种实现比较快?随便一想就有很多种写法,比如
ret = b'1'+b'2'
# 或
ret = bytearray();ret += b'1';ret += b'2'
# 或
ret = ''.join([b'1', b'2'])
再比如生成一个静态元组时是使用列表更快还是元组更快。等等等等不一而足,总之生产环境写码时是避免不了各种测试的,大概也是 py 独一份的问题了。
大家都知道 python 自带有 timeit 模块用来计时足以应付上述场景。但是我一直觉得 timeit 很不好用。首先需要引入包,然后把需要测试的部分单独封装起来,最后还要重载入才能得到结果,尤其在面对小型测试时很麻烦,所以我往常通常在类似测试时更喜欢自己封装一个上下文管理器做替代。由于 python 的上下文管理器生成新的 block 但不生成独立 scope ,不需要进行剥离封装,也不需要担心对原功能产生任何影响,总之不用动用任何脑细胞,非常哈皮。
也就是大概变成这样:
>>> foo = [b'1'] * 100
>>> with timeit():
>>> for _ in range(1000000):
>>> bar = ''.join(foo)
[line 1] time cost: 0.013489246368408203
唯一一点不太爽的是,测试时需要自己写 for 循环,多打一行不短的代码,在深度思考场或者大量测试的场合下会让人非常烦躁。
所以去年发了个帖子,问问有没有大佬知道奇技淫巧,可以以 hook 的方式提前下个钩子把管理的内容提取出来,这样就不用写烦人的 for _ in range 了,可惜当时讨论了一下大家都没有思路。昨天周末摸鱼时间突然开窍,按照 hack 进字节码解释器在 python 里实现 goto 的思路摸索了一下,写了一会搞了个雏形。应该还是有不少坑,欢迎大家测试:
pip install git+https://github.com/GoodManWEN/pipeit.git@test_install
大体思路是通过 py 的高动态特性和反射功能,可以抽取当前 scope 的状态流然后动态生成一份字节码,再然后再反过来实例化,这样就达到了提取出上下文管理器中间部分的目的。实现上到也说不上是很优雅,但是因为依赖的都是久经测试的 bil ,所以同样也说不是上是很肮脏。
目前的 demo 可以做到以下的用法:
>>> # 前两年在 v2 很多人讨论的用魔术方法重载运算符,达到解放传统 py 函数式痛苦的反写体验的功能
>>> # 但由于涉及到对象和方法调用,理所当然会比原生慢一些,假设我们现在想知道具体慢多少
>>> from pipeit import *
>>>
>>> foo = list(range(50))
>>> with timeit(1e6): # 循环百万次,自动转换结果到单轮时间
>>> bar = foo | Filter(lambda x: x%3==0) | Map(lambda x: x*10) | Reduce(lambda x, y:x+y) | int
[line 6] time cost per loop: 8.272400856018066μs
>>> bar
4080
>>> with timeit(1e6):
>>> bar = reduce(lambda x,y:x+y, map(lambda x:x*10, filter(lambda x:x%3 == 0, foo)))
[line 10] time cost per loop: 5.8983259201049805μs
>>> bar
4080
也就是直接 with timeit(循环次数):再加 tab 键就可以对单独行进行测速了。 在我的机器上跑的结果来看原生是比封装版快了 30%左右,不过考虑到本身也是 1 微秒级的差距,我通常开发中还是喜欢使用修改后的写法。
使用场景:
目前的问题:
总之还是有很多问题,权当抛砖引玉吧。希望以后 v 友遇到测速都能少写几行代码
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.