我们先来看看python等语言中的for循环,与其他语言相比,额外的努力是什么。我们知道python是解释执行的。比如执行x=1234+5678,对于编译型语言,从内存中读取两个shortint到寄存器中,然后读取加法指令,通知CPU内部的加法器动作,最后存储加法器的输出在x对应的内存单元(本质上,最后一个动作几乎总是自动优化为“将加法器的输出存储到寄存器而不是内存单元,因为访问内存通常比访问寄存器快几十倍”)。总共2~4条指令(取决于不同的CPU指令集)。对于解释性语言,情况就大不相同了。它必须先把“x=1234+5678”当成一个字符串,逐个字符比较分析语法结构——这是11个字符不计空格,至少要执行11个循环;至少每个循环需要执行的指令是:取数据(比如读取字符'x'),比较数据,根据比较结果跳转(可能要跳回),累加循环计数器,检查循环计数器是否达到最终值,根据比较结果跳转。那至少有6条指令,包括一条内存读取,至少两条分支指令(现代CPU有分支预测,如果命中则不会额外消耗,否则......)。一共66条指令,比编译型语言至少慢17倍(假设每条指令的执行时间相同。但实际上内存访问/跳转指令往往要消耗十倍甚至一百倍的加法时间指示)。这只是阅读源码的消耗,还没有计入“语法分析”的大头;加了之后,至少指令数会多几百倍(耗时。。。我估计至少会多几千倍)。但是,python还是比其他解释型语言强很多。因为它可以将文本代码预先编译成“字节码”(存放在扩展名为pyc的文件中),从而直接处理整型“指令代码”,无需从头分析文本。但是,从“字节码”到实际的CPU代码的翻译步骤仍然是必不可少的。这种消耗可以看作是“使用虚拟机”在异构CPU上执行程序。事实证明,即使优化到极致,这也需要10倍的性能消耗。还有一些方法可以减少这种消耗。这就是JIT技术。JIT说白了就是在第一遍执行一段代码之前执行编译动作,然后执行编译后的代码。如果代码中没有循环,那么这将白白付出很多额外的时间;但是如果有超过一定大小的循环,它可能会节省一点时间。这里的领导者是Java。甚至可以根据上次运行的结果实时profile,然后花大力气优化关键代码,从而获得比C更快的执行速度。然而理想很丰满,现实很骨感.虽然局部热点确实可能更快,但是Java的整体效率还是比C/C++差很多——这个原因比较复杂。与C/C++/Java投入大量资源,日积月累磨练不同,python的JIT甚至可以说是“蹩脚”。加、减、减只是一个循环,慢十倍甚至几十倍也很正常。上面的讨论只考虑了for循环本身的控制结构。其实,“慢”往往是全方位的。例如,要计算一组向量,首先要存储它。如何储存?对于C/C++,它存在于“数组”中;它的数组是一个裸露的连续内存区域;该区域中每几个字节存储一个数值数据。这种结构CPU处理起来最方便快捷,而且是缓存友好的(如果缓存不友好,可能会慢几倍甚至几十倍)。Java等其他语言会略逊一筹。因为它的“数组”是一个“真正的数组”;相对于“连续内存区”,“真实数组”每次访问都要检查数组下标是否越界。这张支票不贵,但也不小……当然,也是有好处的。至少你不用像C/C++那样成天为缓冲区溢出发愁了。而python之类的……为了适应初学者,它去掉了“变量声明”和“数据类型”——所以它的使用者不再需要也可以写intxxx。我们可以存储任何我们想要的数据,呵呵!但是,如果我告诉你变量数据类型实际上在C/C++中是这样声明的:typedefstructtagVARIANT{union{struct__tagVARIANT{WORDwReserved1;WORDw保留2;WORDw保留3;union{LONGLONGllVal;长lVal;BYTEbVal;短iVal;浮点数;双倍数;VARIANT_BOOL布尔值;_VARIANT_BOOL布尔;SCODE代码;CYcyVal;DATE日期;BSTR值;IUnknown*punkVal;皮瓦尔;长*plVal;龙龙*pllVal;浮动*pfltVal;双*pdblVal;VARIANT_BOOL*pboolVal;_VARIANT_BOOL*pbool;编码*pscode;CY*pcyVal;日期*更新;BSTR*pbstrVal;IUnknown**ppunkVal;IDispatch**ppdispVal;安全阵列**pparray;变体*pvarVal;PVOIDbyref;字符值;USHORTuiVal;乌龙ulVal;乌隆隆ullVal;INT整数值;UINT单位值;十进制*pdecVal;字符*pcVal;USHORT*puiVal;ULONG*pulVal;ULONGLONG*pullVal;INT*品脱值;UINT*点值;结构__tagBRECORD{PVOIDpvRecord;IRecordInfo*pRecInfo;}__VARIANT_NAME_4;}__VARIANT_NAME_3;}__VARIANT_NAME_2;十进制十进制;}__VARIANT_NAME_1;}VARIANT,*LPVARIANT,VARIANTARG,*LPVARIANTARG;联盟;访问时,根据标签指令转换/返回合适的类型”显然,对于C/C++/Java程序员来说,这个东西在时间和空间上都是一场灾难。而且,它对缓存也极其不友好——它本来可以有本来是连续存储的,但是现在……变成了一个结构体;而且一旦存储了某些类型的数据,就不得不通过指针跳到另外一个区域去访问(如果原地存储,浪费的空间就惨了)。所以你看,如果要基于这种结构来谈效率,是不是有点……光知道这个水平就已经很震撼了:解释执行+字节码优化至少是10倍到上百倍更慢,“初学者友好”的基础数据慢几倍到几十倍,通过容器访问(而不是性能更好,固定大小的数组,甚至不检查下标假装是数组的“内存区域”)更慢几次打瞌睡ns次……即使我们暂时不考虑其他机制带来的开销,只是把这些放在一起(在某些特定情况下,这些不同的“慢”点也可能会相互影响,起到的效果“慢成倍放大”)...另外还有python里面脚本中如何管理/索引/访问全局/局部变量的问题(一般用dict),用户数据和物理机内存严重导致的Cache未命中不匹配、python内部状态机/执行站点管理等管理问题——对于编译型语言,这些不存在,CPU/内存本身会照顾好自己;但对于解释型语言,这些将是“缓慢乘法”的罪魁祸首。这些东西的相互作用是如此复杂和微妙,以至于很少有人能够完全理解它。你看,明白了前因后果,我们是不是就可以说“python的优化真好,只慢了20万倍”?(笑~当然,如果不做这种比较复杂的处理,只是一些程序性的东西,这种语言的处理速度还是够用的——至少与之交互的人不会感觉到丝毫的延迟甚至,即使需要复杂的处理,这样的语言也可以向其他语言求助。就好比有了numpy,谁敢说python不能做向量运算?——当然,和高手交谈的时候,你要明白,这是像C这样的救援语言。作为python语言本身的能力,睁着眼睛胡说八道有点丢脸。但是如果只混在python圈里,也不会耽误什么。————————————————————————————————————————————如果你想暴露总之,专业的程序员也会因为没有数据类型而模糊界面,所以无法写出更复杂的程序等缺点,给你罗列一列火车。但这些都是不必要的题外话。毕竟python只是一门胶水语言,对初学者比较友好,应对常见的简单应用场景绰绰有余,足够了。就像把office当成傻瓜一样,这是专业程序员的工作——用户觉得好用,愿意花钱买,何必在意“做一套office的钱够盖NBurj”Khalifa”毛布。当然,如果你想更进一步,请记住“在正确的地方使用正确的工具”这句话——然后想办法了解每个工具的局限性。毕竟即使是C/C++,在做矩阵等运算时,也会求助于SIMD的MMX指令、超线程/多核CPU,甚至GPU,为自己“补充”并行处理能力。
