首先我想提一下,“性能”的概念并不真正脱离实际硬件和低级实现而存在。当本书讲述某种数据组织方式“更快”时,它会对实际的编译器/解释器实现和硬件(von Neumann architecture、CPU 指令集、内存布局)做出许多假设。
扯远了,让我们谈谈静态记录,同构和异构数组的实现中可能存在的差异。
1。静态记录
struct Point {
int x;
int y;
};
这是一个简单的 C++ 结构。当你这样做时:
Point *p = new Point();
内存分配器在堆上分配8个字节,并将这个块的开始地址存储在p中。
编译器预先知道Point 的大小(8 字节),因为Point 编译器的每个字段都知道它的大小和移位(即y 的大小为4 和移位4)。
当您访问 Point 的字段 (p -> x) 时,编译器将其替换为计算字段 x 的实际内存地址(p 的地址 + x 的移位)并访问此地址。没有任何运行时开销。
2。齐次数组
int *arr = new int[10];
这是一个简单的 C++ 同构数组。与struct类似,allocator在堆上分配4 * 10字节,并将其起始地址存储在arr中。
当你访问数组元素arr[i]时,编译器知道arr的地址,它的元素大小(它们是相同的,因为数组是齐次的),所以它可以计算你的内存地址'以arr + i * element_size 的身份重新访问(大多数架构上的一条 CPU 指令)。
同样,如果您不计算访问 i 和 arr,那么开销并不大。
3。异构数组
现在,事情变得有趣了。异构数组没有直接的低级表示。有多种可能的实现可以将这个高级概念映射到实际的内存和机器代码中。
让我们考虑其中之一。
enum ElType {
INT, POINT
};
struct ArrEl {
ElType type;
char* elPtr;
};
ArrEl *arr = new ArrEl[10];
arr[1].type = POINT;
arr[1].elPtr = (char*)(new Point {3,4} );
arr[2].type = INT;
arr[2].elPtr = (char*)(new int {2} );
注意,由于数组现在可以存储任何类型的元素(仅适用于本演示 int 或 Point):
- 数组元素可以有不同的大小(
Point 是 8 个字节,int 是 4),因此必须在堆上分配实际元素,并且数组只存储指针(替代方法将分配内存相等到每个元素的最大可能元素的大小,在一般情况下太浪费;注意:见下面的 cmets。
- 要了解存储元素的实际类型,必须存储其他元数据。这种方法选择将其存储在数组中,但大多数实际实现(包括python 和java)将其与实际对象一起存储为“对象头”。请参阅simplified implementation here。
现在要访问此类数组的元素,必须检查此元数据:
ArrEl el = arr[2];
if (el.type == INT) {
int *value = (int *) el.elPtr;
std::cout << *value;
} else if (el.type == POINT) {
Point *p = (Point *) el.elPtr;
std::cout << p->x;
}
访问异构数组的开销包括额外的indirection:首先你必须访问数组的元素,然后按照指针指向堆上的实际值,并检查元数据。
存储所有指针和元数据也会产生额外的内存开销。当您将“简单”类型的同构数组(例如 int)与可以存储的异构数组(例如 int 和 float)进行比较时,这一点尤其明显(注意:请参阅下面的 cmets)。
好的,现在,当我给出了异构数组在理论上会变慢的一般概念后,让我们来谈谈现实世界。
大多数用于具有异构数组的语言的现代 VM 都有JIT。与静态编译器(如 C++)相反,JIT 可以对执行的代码做出一些乐观的假设,如果这些假设失败,则在运行时将代码重新编译为更悲观的变体。
回到数组,虽然 Javascript 和 Python 等动态语言中的数组是异构的,但当数组以同构方式使用时,JIT 可能会将其编译为内部同构!例如,V8 certainly does that。我认为 Python 目前不会这样做,但将来可能会这样做。
此外,现代的optimizing compilers,包括static compilers,可以以你意想不到的方式重写你的代码。例如,您的代码创建一个对象并对其字段执行一些操作,但实际上编译器将用 CPU 寄存器替换字段访问。根本没有创建“实际”对象。
这就是为什么用实际基准验证关于性能的理论假设总是很重要的原因。
附:此演示中的所有 C++ 代码都不是惯用的、不安全的和糟糕的。不要在家里使用它。