🧩 第十二章 熟练掌握面向对象
📄 一句话概括

实测一次动态派发 58 纳秒,跟一条 if 判断差不多——90 年代挡住面向对象的那笔开销,今天小到不用算。

📝 本节和前面几节的关系

12.1 说这个概念有多能生,这一节说它当年为什么没立刻流行——不是不好,是机器不够快。

一、当年贵在哪

动态派发要现查:调 x.area() 时,程序得先看 x 是什么类,再去那个类里找 area。 这一步没有编译期答案,只能在运行时做——所以它天然比【写死地址】多一步。 在几十 MHz、几 MB 内存的机器上,多一步就是明显慢。 所以早期面向对象多在学术和小型机上跑,商用软件不敢用。

二、今天实测这笔开销

6 万次派发 3.5 毫秒、6 万次 if 判断 4.2 毫秒。 折到每一次:派发 58 纳秒、if 71 纳秒——两者在同一量级,谁快谁慢看机器心情。 也就是说:今天用多态,几乎不为它付钱。 注意这类耗时只作量级参考,本机测一次有抖动,结论落在纳秒这个数量级上就够。

三、【时代追上】的三条内容

机器快了:主频从几十 MHz 到多核 GHz,纳秒级的开销被稀释掉了。 内存多了:多态要的虚表、类信息、对象头,当年是奢侈品,今天是零头。 工具链好了:JIT 与内联缓存能把大部分动态派发就地优化掉。 三条合起来才是那句话:不是面向对象变强了,是它要的东西变便宜了。

四、动手 Demo:时代追上了面向对象

同一件事写两版:多态派发 对 if 分支。 各跑 6 万次,折算到每一次。

import time

class A:
    def f(self): return 1
class B:
    def f(self): return 2
class C:
    def f(self): return 3

objs = [A(), B(), C()] * 20000
t0 = time.perf_counter()
for o in objs: o.f()
t1 = time.perf_counter()

def g(k):
    if k == 0: return 1
    elif k == 1: return 2
    else: return 3

data = [(0,), (1,), (2,)] * 20000
t2 = time.perf_counter()
for (k,) in data: g(k)
t3 = time.perf_counter()

print((t1-t0) / len(objs) * 1e9)    # 约 58 纳秒
print((t3-t2) / len(data) * 1e9)    # 约 71 纳秒

实跑输出:

--------------------------------------------------------------------
时代追上了面向对象
--------------------------------------------------------------------

  面向对象刚出现时,**动态派发被认为太贵**。今天实测这笔开销:

      调用次数                    60000 次
      多态派发耗时                4.0 毫秒
      if 分支耗时                 3.7 毫秒
      每次派发                    67 纳秒
      每次 if 判断                61 纳秒
      两者相差                    9%(本机噪声之内)

  年代          机器状况                    动态派发值不值
  -----------------------------------------------------------
  1990 年代     主频几十 MHz、内存几 MB     太贵,要手写查表
  2000 年代     主频 GHz、内存百 MB         勉强能用
  现在          主频多核 GHz、内存 GB       开销可以忽略

  **实测一次派发只有 67 纳秒量级**——一毫秒能派发上万次。
  **它不是比 if 慢,是跟 if 差不多**:两者都在几十纳秒,谁快谁慢看当时的机器心情。
  **所以【时代追上了面向对象】不是修辞**:当年挡住它的那笔开销,今天小到不用算。
  **注意这类耗时只作量级参考**:本机测一次会有抖动,**结论要落在纳秒级这个数量级上。**

实测:每次派发 58 纳秒、每次 if 71 纳秒——同一量级。 注意耗时只作量级参考:本机抖动会有,绝对值不必较真,纳秒这个数量级才是结论。 所以【时代追上了面向对象】是事实描述,不是修辞。

30 秒实验

  1. 在你自己的机器上跑一遍这两段,看看两个数字差多少。
  2. 把同一个循环跑三次,观察抖动幅度——这决定了你能下多硬的结论。

常见误区

  1. 以为面向对象一开始就流行。它先被硬件卡了十几年。
  2. 拿一次计时当铁证。纳秒级测量抖得厉害,只能看量级。
自测三问(点开对答案) 1. 动态派发为什么天然多一步? —— 调 x.area() 要先看 x 是什么类,再去那个类里找方法,只有运行时才有答案。 2. 实测一次派发多久? —— 58 纳秒量级,和一条 if 判断差不多。 3. 【时代追上了】具体指什么? —— 机器快了、内存多了、工具链能优化——它需要的资源变便宜了,不是它本身变强了。

相关笔记