当前位置: 首页 > 编程日记 > 正文

[QA]Python字节码优化问题

这篇文章来自在Segmentfault 上面我提出的一个问题

问题背景: Python在执行的时候会加载每一个模块的PyCodeObject,其中这个对象就包含有opcode,也就是这个模块所有的指令集合,具体定义在源码目录的 /include/opcode.h 中定义了所有的指令集合,在执行的时候通过加载opcode完成指令的流水线执行过程,opcode也就是所有指令集合生成的字符串。执行体位于源码目录的 /Python/ceavl.c 中PyEval_EvalFrameEx()函数就是虚拟机的执行体函数,它会加载指令集合并完成运算。

问题描述: 在PyEval_EvalFrameEx()函数中,同样是通过标准状态机模型完成的指令解析,一个巨大无比的switch结构,类似这样:

请输入图片描述

在C中,switch语句的执行是逐条对比的,也就是说每一条指令在执行的时候都需要从头对比,因为这里的指令集合是不平均分布的,但是我们可以假设每个指令平均需要匹配n次,n > 1,其实是远远大于1的。

具体问题: 是否可以做优化,为什么作者没有做优化? 如果不采用switch状态机,因为指令码也是有编号的,是否可以直接采用类hashtable的形式来做?

@felix021 回答了这个问题:

修改方案做了个测试,基于python 2.7.3,把PyEval_EvalFrameEx这个函数里的case都改成了label,然后利用gcc的labels as values特性,将里面用到的118个opcode与对应的label构成数组:static void *label_hash[256] = {NULL};
static int initialized = 0; 
if (!initialized)
{    #include "opcodes.c"#include "labels.c"int i, n_opcode = sizeof(opcode_list) / sizeof(*opcode_list);for (i = 0; i < n_opcode; i++) label_hash[opcode_list[i]] = label_list[i];initialized = 1; 
}
然后把 switch (opcodes) 改成void *label = label_hash[opcode];
if (label == NULL)goto default_opcode;
goto *label;
并逐个替换每个case里的break。编译后通过了所有的测试(除了test_gdb,这个跟未修改版一样,都是没有sys.pydebug),也就是说这个修改是正确的。性能测试接下来的问题是性能……这个该怎么测试呢……没有好的想法,所以随便找了两段代码:直接loop 5kw次:i = 50000000
while i > 0:i -= 1
修改前运行4次:[4.858, 4.851, 4.877, 4.850],去掉最大的一次,平均4.853s修改后运行4次:[4.558, 4.546, 4.550, 4.560],去掉最大的一次,平均4.551s性能提升 (100% - (4.551 / 4.853)) = 6.22%递归Fibonacci,计算第38个def fib(n):return n if n <= 2 else fib(n - 2) + fib(n - 1)
print fib(37)
修改前 [6.227, 6.232, 6.311, 6.241],去掉最大的一次,平均6.233s修改后 [5.962, 5.945, 6.005, 6.037],去掉最大的一次,平均5.971s性能提升 (100% - (5.971 / 6.233)) = 4.20%结论综合看来,这个小小的改动,的确可以提高5%左右的性能,不知道对各位而言意义有多大……不过因为用到了只有GCC支持的、非C标准的特性,所以不方便移植。根据StackOverflow的这个帖子,MSVC可以在一定程度上支持,但是貌似很tricky。不知道这个在Python的发展历程中是否有人做过这样的尝试,也许官方会偏好可移植性?也许抽空可以发个帖子去问问。 根据 @方泽图 的说法(见他答案里的评论),Python 3.0+引入了这个优化。详情可以参见他的答案。

这里提到了GCC的一个特性,非标准的C语法 Labels as Values

具体就是把标签的地址放入到一个数组中可以实现直接跳转

static void *array[] = { &&foo, &&bar, &&hack };
goto *array[i];

这个特性MSVC不知道是否支持,但是gcc是可以的。那么在switch里面可以把标签放入到静态数组中实现直接跳转,类似hash表。

后来又印来了 @方泽图的回答:

比较稀疏的switch(指case的值之间相差得比较大)确实是需要一次次地比较才能选定到底应该进入哪个分支。不过CPython的这个ceval.c里的switch是非常稠密的,case之间的值相差都是1,因此流行的编译器(gcc/msvc/llvm-clang)都能够将这个switch转化为简单的indirect branch,比如以x86-64,linux,gas syntax为例:cmp $MAX_OP, %opcode
ja .handle_max_op
jmp *.op_dispatch_table(,%opcode,8)
翻译成C的话,大致意思是这样:static void *op_dispatch_table[] = {&&handle_NOP,&&handle_LOAD_FAST,// etc etc...
};if (opcode > MAX_OP) {goto *handle_max_op;
}
else {goto *op_dispatch_table[opcode];
}
所以其实并不会像你所说的那样逐条比较。Interpreter的优化是很有意思的。switch看似高效,但是实际上由于生成的代码会在同一个地方进行所有的indirect branch(分支目标可以是任何地方),处理器的分支预测就变得毫无用处了。分支预测在CPython这种基于栈的解释器里非常重要,这是因为大部分的OPCODE都较短,opcode dispatch(也就是分支预测)花的时间经常能占到大头。在大家常用的Sandy Bridge处理器里,分支预测失败相当于15个cycle(来源),而IPC(每cycle能执行的CPU指令)在分支预测成功的情况下一般能达到3。相比之下,LOAD_FAST这种小型的OPCODE仅仅只用到了不到10个CPU指令.. 也就是说,分支预测所花的时间,甚至能占到整个code运行时间的80%。因此,CPython使用了另外两个优化技巧,一是对常用连续指令的预测,二是如果编译器支持则使用indirect threading。连续指令的预测,指的是由于Python中,有很多指令经常成对出现(比如在if x < y then xxx else xxx里会出现COMPARE_OP -> POP_JUMP_IF_FALSE)。这种情况下,前一个OPCODE并不需要依靠switch来执行后一个OPCODE,它可以自己跳到后一个OPCODE上去,需要做的只是检查一下后一个OPCODE是不是自己所想要的而已。这里需要添加两个分支,但是这两个分支一个是条件判断,一个是直接跳过去,所以处理器的分支预测可以在这里发挥作用。在ceval.c里,如果你看到了PREDICT(...)的字样,那就说明这里有连续指令的预测了。indirect threading,指的是将indirect branch放在每个OPCODE处理的结尾部分。这样一来,每个OPCODE就会获得处理器针对自己下一个指令的分支预测。虽然这依然是indirect branch,但是由于每个OPCODE分开预测,这大大提高了预测的准确度。CPython2.7并没有用到这个优化。CPython3+会根据编译器支持与否,来开关这个选项。CPython的解释器,经过多年的打磨,优化那是刚刚的。

后来据 方说,Python3.3+以后的确是用到了labelHash来优化,需要根据不同的编译环境来做。

相关文章:

【滴滴专场】深度学习模型优化技术揭秘

滴滴拥有海量数据以及 AI 推理需求&#xff0c;GPU 在人脸识别、自动驾驶、地图导航等场景大量使用&#xff0c;滴滴云IFX团队针对斯坦福 DAWNBench 榜单的 ImageNet 模型也进行了深入优化&#xff0c;在 NVIDIA T4 上达到了 0.382 ms 的 Inference Latency 性能&#xff0c;打…

11.python并发入门(part9 多进程模块multiprocessing基本用法)

一、回顾多继承的概念。由于GIL&#xff08;全局解释器锁&#xff09;的存在&#xff0c;在python中无法实现真正的多线程&#xff08;一个进程里的多个线程无法在cpu上并行执行&#xff09;&#xff0c;如果想充分的利用cpu的资源&#xff0c;在python中需要使用进程。二、mul…

将不确定变为确定~Flag特性的枚举是否可以得到Description信息

回到目录 之前我写过对于普通枚举类型对象&#xff0c;输出Description特性信息的方法&#xff0c;今天主要来说一下&#xff0c;如何实现位域Flags枚举元素的Description信息的方法。 对于一个普通枚举对象&#xff0c;它输出描述信息是这样的 public enum Status{[Descriptio…

中科大“九章”历史性突破,但实现真正的量子霸权还有多远?

作者 | 马超 出品 | AI科技大本营 头图 | CSDN下载自视觉中国 10月中旬&#xff0c;政府高层强调要充分认识推动量子科技发展的重要性和紧迫性&#xff0c;加强量子科技发展战略谋划和系统布局&#xff0c;把握大趋势&#xff0c;下好先手棋。 今天&#xff0c;我国的量子科技领…

php析构函数的用法

简单的说,析构函数是用来在对象关闭时完成的特殊工作,比如我写的上例,在实例化同时打开某文件,但是它什么时候关闭呢,用完就关闭呗,所以析构函数直接关闭它, 又或者在析构时,我们将处理好的某些数据一并写进数据库,这时可以考虑使用析构函数内完成,在析构完成前,这些对象属性仍…

泛型中? super T和? extends T的区别

经常发现有List<? super T>、Set<? extends T>的声明&#xff0c;是什么意思呢&#xff1f;<? super T>表示包括T在内的任何T的父类&#xff0c;<? extends T>表示包括T在内的任何T的子类&#xff0c;下面我们详细分析一下两种通配符具体的区别。 …

两大AI技术集于一身,有道词典笔3从0到1的飞跃

作者 | Just 出品 | AI科技大本意&#xff08;ID:rgznai100&#xff09; “双十一”结束的钟点刚刚敲响&#xff0c;拥有电子消费品的企业便很快对外界秀了一把今年的销售战绩&#xff0c;网易有道也不例外。在电子词典单品品类榜单上&#xff0c;有道词典笔稳稳拿下天猫和京东…

VIM进阶功能

2019独角兽企业重金招聘Python工程师标准>>> http://www.cnblogs.com/gunl/archive/2011/08/15/2139588.html 该网页上介绍了以下内容&#xff1a; 查找快速移动光标复制、删除、粘贴数字命令组合快速输入字符替换多文件编辑宏替换TABautocmd常用脚本常用配置另一篇…

最简便的清空memcache的方法

如果要清空memcache的items&#xff0c;常用的办法是什么&#xff1f;杀掉重启&#xff1f;如果有n台memcache需要重启怎么办&#xff1f;挨个做一遍&#xff1f; 很简单&#xff0c;假设memcached运行在本地的11211端口&#xff0c;那么跑一下命令行&#xff1a; $ echo ”f…

mongodb启动

将mongodb安装为Windows服务&#xff0c;让该服务随Windows启动而启动&#xff1a; 开启服务&#xff1a; 转载于:https://www.cnblogs.com/dabinglian/p/6861413.html

赠书 | AI 还原宋代皇帝,原来这么帅?!

整理 | 王晓曼来源 | 程序人生 &#xff08;ID&#xff1a;coder _life&#xff09;封图 | 大谷视频《人工智能还原的宋代皇帝&#xff0c;原来这么帅&#xff1f;&#xff01;》*文末有赠书福利昨日&#xff0c;一条“人工智能还原的宋代皇帝”喜提热搜&#xff0c;博主大谷借…

Deep learning:三十六(关于构建深度卷积SAE网络的一点困惑)

前言&#xff1a; 最近一直在思考&#xff0c;如果我使用SCSAE&#xff08;即stacked convolution sparse autoendoer&#xff09;算法来训练一个的deep model的话&#xff0c;其网络的第二层开始后续所有网络层的训练数据从哪里来呢&#xff1f;其实如果在这个问题中&#xff…

用memcache.php监测memcache的状况

最新的memcache pecl中&#xff0c;新增了一个memcache.php&#xff0c;这个php文件可以用来方便的查看memcache的状况&#xff0c;界面上与apc自带的apc.php风格一致。 如图: 应该算是最方便的监测memcache的办法了。 memcache.php源文件下载 是一个PHP源文件&#xff0c;…

想知道垃圾回收暂停的过程中发生了什么吗?查查垃圾回收日志就知道了!

\关键点\垃圾回收日志中包括着一些关键性能指标&#xff1b; \要做一次正确的垃圾回收分析需要收集许多数据&#xff0c;所以好的工具是非常必要的&#xff1b; \除了垃圾回收之外还有很多事件都可能会让应用程序暂停&#xff1b; \让你的应用程序暂停的事件也会让垃圾回收器暂…

Linux必学的网络操作命令

因为Linux系统是在Internet上起源和发展的&#xff0c;它与生俱来拥有强大的网络功能和丰富的网络应用软件&#xff0c;尤其是TCP/IP网络协议的实现尤为成熟。Linux的网络命令比较多&#xff0c;其中一些命令像ping、ftp、telnet、route、netstat等在其它操作系统上也能看到&am…

丢弃Transformer,FCN也可以实现E2E检测

作者 | 王剑锋来源 | 知乎CVer计算机视觉专栏我们基于FCOS&#xff0c;首次在dense prediction上利用全卷积结构做到E2E&#xff0c;即无NMS后处理。我们首先分析了常见的dense prediction方法&#xff08;如RetinaNet、FCOS、ATSS等&#xff09;&#xff0c;并且认为one-to-ma…

Linux命令基础--uname

uname 显示系统信息 语 法&#xff1a;uname [-amnrsvpio][--help][--version] 补充说明&#xff1a;uname可显示linux主机所用的操作系统的版本、硬件的名称等基本信息。 参 数&#xff1a; -a或-all 详细输出所有信息&#xff0c;依次为内核名称&#xff0c;主机名&am…

FEC之我见一

顾名思义&#xff0c;FEC前向纠错&#xff0c;根据收到的包进行计算获取丢掉的包&#xff0c;而和大神沟通的结果就是 纠错神髓&#xff1a;收到的媒体包冗余包 > 原始媒体包数据 直到满足 收到的媒体包 冗余包 > 原始媒体包数据 则进入恢复模式&#xff0c;恢复出…

改变Repeater控件中按钮颜色

昨晚有在论坛看到一帖&#xff0c;手上的工作一直忙到现在&#xff0c;Insus.NET现在抽点时间尝试实现它。 Insus.NET没有使用数据库作为数据源&#xff0c;而是使用List<T>作为数据源。因此你在这篇博文中学到很多有关泛型的知识。另外Insus.NET使用CheckBoxList来替代多…

CSDN湘苗培优,遇见更好的自己

CSDN 湘苗培优报名火热进行中&#xff01;JOIN US号外&#xff01;号外&#xff01;“湘苗培优”报名火热进行中&#xff01;????????????????????????为什么要报名“湘苗培优”只要你想学&#xff0c;这里有CSDN技术认证、企业导师零距离技术交流求职…

两个无序单链表,排序后合并成一个有序链表

两个无序单链表&#xff0c;排序后合并成一个有序链表算法思想&#xff1a;用冒泡法&#xff0c;对链表1和2进行排序&#xff0c;对排序后的两个链表&#xff0c;从小到大进行循环&#xff0c;装入链表3中。#include<stdio.h>#include<stdlib.h>struct stud/*定义链…

FEC之我见三

继续上文讲解&#xff1a; 3&#xff09;标准的RTP头结构如下所示&#xff1a; 其中第一个字节中的x标志位是否扩展了RTP头&#xff0c;RTP协议允许用户自定义的扩展&#xff0c;扩展的字段紧挨上述RTP固定头。RTP扩展投中承载如下信息&#xff1a;1).当前包所在的Group组序号&…

集体智慧及其常用算法

集体智慧定义是指由许多的个体通过合作与竞争中所显现出来的智慧&#xff0c;集体智慧是一种共享的或者群体的智能。它是从许多个体的合作与竞争中涌现出来的。集体智慧在细菌、动物、人类以及计算机网络中形成&#xff0c;并以多种形式的协商一致的决策模式出现。常用算法如下…

带你「周游世界」的 MODNet 算法

来源 | Jack Cui责编 | 晋兆雨头图 | CSDN下载自视觉中国最近又有一个算法火了&#xff0c;不知道你们看到没&#xff1f;直接看效果&#xff01;效果这么稳定的人像 Image Matting 算法真的不多&#xff0c;并且还能进行实时处理&#xff01;处理视频、图像&#xff0c;不在话…

ASP.NET格式化日期

1.绑定时格式化日期方法: <ASP:BOUNDCOLUMN DATAFIELD "JoinTime " DATAFORMATSTRING "{0:yyyy-MM-dd} " > <ITEMSTYLE WIDTH "18% " > </ITEMSTYLE &g…

docker的网络架构配置

http://xiaorenwutest.blog.51cto.com docker 网络架构模默认情况下&#xff0c;容器可以建立到外部网络的连接&#xff0c;但是外部网络无法连接到容器。Docker 允许通过外部访问容器或容器互联的方式来提供网络服务外部访问容器:容器中可以运行一些网络应用&#xff0c;要让外…

【官方福利】CSDN内测师限时申请,参与赢年末礼包

各位程序猿们都下载CSDN官方出品的插件了吧&#xff1f;什么&#xff1f;还有不知道插件是什么的同学&#xff1f;&#xff1f;你错过了太多&#xff01;更酷更高效的浏览器插件&#xff0c;一键万能操作&#xff0c;新标签页极简个性&#xff0c;让你的工作效率UP UP UP&#…

Sql年月日计算方法

通常&#xff0c;你需要获得当前日期和计算一些其他的日期&#xff0c;例如&#xff0c;你的程序可能需要判断一个月的第一天或者最后一天。你们大部分人大概都知道怎样把日期进行分割&#xff08;年、月、日等&#xff09;&#xff0c;然后仅仅用分割出来的年、月、日等放在几…

读《每天懂一点成功概率学》

概率出现某种结果的数量/出现所有结果的数量 所谓“数学概率”&#xff0c;就是理论上计算出来的概率&#xff0c;例如抛硬币时&#xff0c;只有正面和反面两种结果&#xff0c;因此正面出现的概率就是1/2。 另一方面&#xff0c;我们反复抛硬币&#xff0c;根据实际结果计算出…

AV1时代要来了,超高清视频时代视频编码技术的机遇与挑战

近些年随着视频行业的迅猛发展&#xff0c;尤其像短视频、点播、直播、VR等领域的爆发&#xff0c;人们对于高清、超高清视频体验的追求越来越强烈&#xff0c;流媒体平台如何在提升观众观看体验&#xff0c;同时降低播放成本&#xff0c;利用技术降低带宽消耗的同时又能最大化…