我们知道内存的运行速度远超硬盘,其速度差异大约在一两个数量级之间(价格上也有显著的差异)。硬盘的问题并非仅仅是速度较慢。
硬盘的一个基本特性是不适宜进行频繁的小量读取操作。所谓频繁小量读取,指的是每次读取的数据量很小,但读取次数很多。对于内存来说,无论是取100字节并进行100万次操作,还是一次取100万字节,总的数据获取量是相同的,耗时也相差无几。获取数据的最小单位在内存中就是字节。
硬盘的机制完全不同。硬盘中的数据是分块存储的,读取数据有一个最小的基本单位,在操作系统中通常是4K。这意味着从硬盘上读取1个字节和读取4K字节的时间是一样的。即使总读取量相同,如果采用频繁小量读取和批量大次读取,耗时可能会有巨大差异。对于硬盘来说,由于其读取是一系列复杂的动作,不像内存那样执行一条CPU指令就可以完成,所以频繁的读取操作会导致性能显著下降。
这个结论的成立与否还和数据在硬盘上的存储方式是否连续有关。如果数据在硬盘上连续存储,那么即使进行多次小量读取也不会太慢,因为后续要读的数据可能已经在之前读出的数据块中了。硬盘和操作系统都有缓存功能,实际硬盘读取次数并没有那么多,性能下降也不会非常严重。我们还需要在“频繁小量”前面加上“随机”这个定语。也就是说,当读取的内容不连续时,从读出的数据块中取出需要的部分外,其他内容就无法利用,只能浪费掉。这种情况下的性能会显著下降。
对于机械硬盘来说,还有一个寻道问题。寻道是找到数据所在位置的过程,这个过程是一个比实际读取数据更慢的机械动作。即使在连续读取的情况下,寻道成本可能会超过实际的读取操作。使用机械硬盘时需要特别注意避免频繁的随机读取操作。而固态硬盘的情况虽然有所改善,但仍然需要注意避免频繁的随机小量读取操作,因为硬盘的数据块仍然相对较大。
那么,对于计算任务只需要连续批量读取数据的情况(如遍历汇总),硬盘的性能是否只取决于其本身的读取速度呢?对于单个的单线程任务来说,确实如此。但在现代高性能计算中,我们还需要考虑并行和并发运算。并行和并发运算会使原本可以连续读取的硬盘数据变成随机读取。这是因为多线程共享同一套硬盘时,不同线程的读取请求可能不会连续,导致硬盘需要频繁跳转以响应这些请求。对于机械硬盘来说,这种情况可能导致严重后果。如果线程切换频繁,甚至可能出现多线程运行比单线程更慢的情况。为了改善这种情况,我们可以加大读数的缓冲区,使每次读出的数据足够多且连续,从而减少寻道时间的影响。但这会增加内存占用,并且线程越多对内存的需求就越大。
类似的场景是列式存储的情况。当数据按列存放时,需要进行多列计算时即使是单线程也可能导致硬盘发生随机读取现象。因此由于硬盘的这个性能特性内存和外存的运算实现采用了完全不同的算法甚至对运算本身的定义也应该有所不同。在设计关系代数时并没有考虑到内外存的区别只是笼统地定义了运算但关系数据库在实现这些运算时会发现一些运算在外存的场景下要比在内存中复杂得多典型的例子就是 JOIN 运算数据库常用的 HASH JOIN 算法虽然可以应用于内存但却不适用于外存在外存的场景下这种算法的遍历成本可能高于计算本身可能会导致性能陡降的问题在这种情况下如果重新思考并定义 JOIN 运算充分考虑外存即硬盘的性能特征就可以设计出更为高效的算法从而获得更高的性能这也是 esProc SPL 与关系数据库的主要区别所在。