一个语言的诞生(Act II)
第二幕 对象内存布局
那些不怎么“动态”的语言,比如C#和JAVA,一般都会把数据类型分成“值”和“引用”,值类型是分配在栈上的,引用类型是分配在堆上。而“动态”的语言,比如“PHP”和“Python”所有的数据都是动态分配在堆上的。这么做的缺点一是浪费内存,二是访问慢。浪费内存是因为PHP和Python的对象都经过重度的封装,一个简单的整型经过封装后的大小是原来的4、5倍。访问慢的原因是每次创建一个对象都要申请一次内存,而且访问对象必须通过指针,这样一搞CPU的1、2级缓存根本就没有用了嘛。
PHP的优化方法是写入时复制(Copy-On-Write)。Python的优化方法是对象池。都算不上完美的解决方案。更令人吃惊的是Python的对象池还会造成类似“内存泄漏”的额外伤害。
Lua作为一种拥有无限多个寄存器的虚拟机,它的对象也分成值类型和引用类型,值类型是保存在寄存器上的,引用类型是保存在堆内存上的,这就类似于.NET和JVM了。Lua的寄存器和栈一样,是一块事先分配好的连续内存空间。分配在寄存器上的值类型无需ARC或GC,函数返回后直接整块回收就行了,和栈一样方便。
对象模型决定模仿 Lua,由一个包含布尔、整型、双精度浮点数和对象指针的共同体以及一个表示对象类型的整型组成的结构体。结构体长度为8字节,类型域长度为4字节,合计12字节。再算上内存对齐需要补足4字节,对象总计大小为16字节。对象指针指向包括:字符串、列表、字典、元组、对象、用户数据等结构。
其中布尔、整型、双精度浮点数、字符串、列表、字典、元组、用户数据属于基础类型,类型不可继承,方法不可重写,所有运算和方法都是静态的。对象类型使用元表实现操作符重载,使用哈希表实现方法动态调用,再辅以Inline caching实现方法调用的优化。
系列: