2014-10-04-python-object-memory-model

Posted on October 4, 2014

在最纯粹,最贴近底层的c语言里,结构是不包含任何语言带来的冗余结构, 你定义的内容就是计算机最后会搬到RAM里进行利用的内容, 完美的一一对应。 可是我们并非就想事事亲力亲为, 也无法事事都亲力亲为。 如果建筑物的一砖一瓦都要坚持自己烧制,那么现代的建筑行业没有可能筑起摩天大楼。 于是在C的基础上,很多更高层次的语言工具被构建出来。 他们出于各种各样的目的,为对象最底层的内存布局添加一些字段。

在c++里这种冗余被最大程度的抑制,程序员基本保持着对象布局和内存之间的一一对应。 有一些例外: c++的面向对象特性有一个重要的特点:多态。 多态本质上要求对象实例能够延迟绑定,在运行时根据对象的特定类型调用特定的方法。 为了给c++程序员提供这样的便利, 编译器必须给对象实例添加这样的信息,使得这些实例能够在运行时绑定到相应的方法。 于是对象内存布局的完美一一对应被打破, 每一个c++对象为了支持多态,会被编译器嵌入vptr来支持多态形式的表达。

同样面向对象,而且自然多态的java语言,同样无法避免对象布局的这点冗余。 毕竟为了能够让对象在运行时动态决定调用的方法,除了把每个对象和相应的方法构建起映射之外还有什么办法么? Java更甚一步, 为了对多线程的支持, Java提供了让多个线程同步访问一个对象的方法。 为了避免多个线程同时访问同一个对象的时候出现数据竞争的情况, Java语言为每一个对象引入了一个同步标示符, 一旦有多个线程同时访问同一个对象, 可以通过置同步标示符来获得对象的独家访问权。 Java语言的对象布局中引入额外的同步块获得的好处就是可以更加方便的进行线程间的同步操作。

C#借鉴了很多Java的优秀设计思想, C#的对象布局中同样含有冗余的同步对象块和对象索引块, 这两块冗余的内容同样带给了c#延迟绑定的多态特性和多线程同步的便利。 另外一点值得注意的是:即使是在高级的编程语言下,也并不是任何时候都需要这样的便利。 比如再纯粹进行浮点数运算的时候,不需要多态也不需要多线程同步,如果这个时候每个浮点数仍然带有这样的冗余信息,那么真的一点好处都没有。 所以C++/Java/c#的对象模型区分了基本类型模型(Primitive)和对象类型模型(Ojbect), 对于整数,浮点这些纯粹的数据类型而言没有这些冗余的内存布局。 java/c#也提供给了打包/解包的方法赋予这些基本类型对象的便利特性。

程序语言提供的便利总是有代价的,这种代价总是计算和存储的一种折中。 程序员其实需要了解这些便利,这些便利背后的代价, 才能充分发掘一门编程语言的潜力。 如果在不了解的情况下大量使用某一特性最后导致不能满足具体项目的性能规格需求,这显然不是大家愿意看到的。

通过在RAM里存储一些冗余的语言结构,程序员获得了便利,他就可以把精力集中在业务逻辑部分,才有可能在有限的时间里借助工具的力量打造一栋又一栋的软件摩天大楼。 通常这样的假设都是成立的,然而我们不能用设计摩天大楼的思路去设计航天飞船,外太空探索飞行器。 这样的冗余在很多应用场景下是有效的并不意味着它们在所有情况下都是有效的。 任何假定都是有适用范围的,例如说开放的Java/C#程序根本不涉及多线程同步,那么为什么需要线程同步块这样的冗余结构呢? 是不是说Java/C#应该给予这样的应用场景足够的应用,在这样的场景下,对象只有对象索引块这种冗余结构呢?

了解其他语言的通行做法能够更好的评价Python在对象布局冗余和语言特性上得这种情况, 这样的冗余到底是不是值得, 这样的做法是不是仍然有很大的改进空间等等。 总而言之,比较也是学习的一种基本方法。

首先python没有区分基本类型和对象类型,在Python语言里一切皆对象, 你声明一个复杂的数据结构和一个简单的整数类型,他们都含有Python所设计的对象冗余。 Python的对象布局包含了对象引用块,引用计数。变长对象还包含了对象长度信息。 通过对象引用块可以获得对象实例的操作方法,同样也是延迟类的动态绑定。 通过引用计数Python接管所有对象的内存管理。 将Python的对象布局冗余和其他语言比较: 发现Python独此一家的多了引用计数, 而同样提供自动内存管理的Java C#并没有使用引用计数。 可见引用计数对于自动内存管理并不是必须的。 实际上后续于Python的实现版本,有一些分支去除了Python的引用计数。

==