理论
假设您有一个数组,它按照事件发生的顺序存储事件。如果每个事件在计算机内存中占用相同数量的空间,您就知道该数组从哪里开始,并且您知道您感兴趣的事件编号,那么您可以预先计算每个事件的位置。
假设您想存储记录并通过电话号码键入它们。由于有很多数字,您可以计算每个数字的hash。您可能应用的最简单的散列是将电话号码视为常规号码,并将其取模为您想要存储号码的数组的长度。同样,您可以假设每条记录占用相同数量的空间,您知道记录的数量,您知道数组从哪里开始,并且您知道感兴趣的事件的偏移量。通过这些,您可以预先计算每个事件的位置。
如果数组项的大小不同,则用指向实际项的指针填充数组。然后,您的查找有两个阶段:找到适当的数组元素,然后将其跟踪到有问题的项目。
就像我们可以使用 shmancy GPS 系统来告诉我们地址在哪里,但我们仍然需要做开车到那里的工作,访问内存的问题是不知道物品在哪里,它正在到达那里。
回答您的问题
考虑到这一点,您的问题的答案是查找几乎从来都不是免费的,但也很少是 O(N)。
磁带内存:O(N)
Tape memory 需要 O(N) 寻道,原因很明显:您必须卷起和解卷磁带以将其定位到所需位置。它很慢。它还便宜且可靠,因此今天仍在长期备份系统中使用。 Special algorithms 考虑到磁带的物理性质,可以稍微加快对它的操作。
请注意,根据上述内容,磁带的问题不在于我们不知道要查找的东西在哪里。问题是让物理介质到达那里。一个好的磁带算法的本质是尽量减少在一组操作中假脱机和取消假脱机的磁带总量。
说到这个,如果我们用两个较短的磁带而不是一个长磁带怎么办:这将减少点对点的旅行时间。如果我们有四盘磁带呢?
磁盘内存:O(N),但更小
硬盘驱动器通过将磁带变成一系列环,大大减少了寻道时间。现在,即使磁盘上有 N 个内存空间,只要将驱动器磁头和磁盘移动到适当的位置,就可以在短时间内访问任何一个内存空间。 (弄清楚如何用大哦符号表达这一点是一个挑战。)
同样,如果您使用更快的磁盘或更小的磁盘,您可以optimize performance。
RAM:O(1),但有一些注意事项
几乎所有回答这个问题的人都会关注 RAM,因为这是程序员最常使用的东西。查看他们的答案以获得更全面的解释。
但是,简单地说,RAM 是上述想法的自然延伸。 RAM 包含 N 个项目,我们知道我们想要的项目在哪里。然而,这一次没有什么需要机械地移动来让我们到达那个项目。此外,我们发现通过使用更多短磁带或更小、更快的驱动器,我们可以更快地获得我们想要的内存。 RAM 将这个想法发挥到了极致。
出于实际目的,您可以将 RAM 视为小内存存储的集合,它们全部串在一起。您的计算机不知道特定项目在 RAM 中的确切位置,只知道它所属的集合。所以它会抓取整个集合,由数千或数百万字节组成。它将它存储在类似于 L3 缓存的东西中。
但是该缓存中的特定项目在哪里?同样,您可以认为计算机并不真正知道,它只是抓取保证包含该项目的子集并将其传递给 L2 缓存。
同样,对于 L1 缓存。
而且,在这一点上,我们已经从千兆字节(或太字节)的 RAM 增加到了 3-30 KB。而且,在这个级别上,您的计算机(最终)知道该项目的确切位置并抓取它进行处理。
这种分层行为意味着访问 RAM 中的相邻项目比随机访问整个 RAM 中的不同点要快得多。磁带驱动器和硬盘也是如此。
但是,与磁带驱动器和硬盘不同,丢失所有缓存的最坏情况时间不依赖于内存量(或者,至少,非常弱依赖:路径长度,光速等)!出于这个原因,你可以将其视为内存大小的O(1)操作。
比较速度
知道了这一点,我们可以通过查看Latency Numbers Every Programmer Should Know来谈谈访问速度:
Latency Comparison Numbers
--------------------------
L1 cache reference 0.5 ns
Branch mispredict 5 ns
L2 cache reference 7 ns 14x L1 cache
Mutex lock/unlock 25 ns
Main memory reference 100 ns 20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy 3,000 ns 3 us
Send 1K bytes over 1 Gbps network 10,000 ns 10 us
Read 4K randomly from SSD* 150,000 ns 150 us ~1GB/sec SSD
Read 1 MB sequentially from memory 250,000 ns 250 us
Round trip within same datacenter 500,000 ns 500 us
Read 1 MB sequentially from SSD* 1,000,000 ns 1,000 us 1 ms ~1GB/sec SSD, 4X memory
Disk seek 10,000,000 ns 10,000 us 10 ms 20x datacenter roundtrip
Read 1 MB sequentially from disk 20,000,000 ns 20,000 us 20 ms 80x memory, 20X SSD
Send packet CA->Netherlands->CA 150,000,000 ns 150,000 us 150 ms
在更人性化的术语中,这些看起来像:
Minute:
L1 cache reference 0.5 s One heart beat (0.5 s)
Branch mispredict 5 s Yawn
L2 cache reference 7 s Long yawn
Mutex lock/unlock 25 s Making a coffee
Hour:
Main memory reference 100 s Brushing your teeth
Compress 1K bytes with Zippy 50 min One episode of a TV show (including ad breaks)
Day:
Send 2K bytes over 1 Gbps network 5.5 hr From lunch to end of work day
Week:
SSD random read 1.7 days A normal weekend
Read 1 MB sequentially from memory 2.9 days A long weekend
Round trip within same datacenter 5.8 days A medium vacation
Read 1 MB sequentially from SSD 11.6 days Waiting for almost 2 weeks for a delivery
Year:
Disk seek 16.5 weeks A semester in university
Read 1 MB sequentially from disk 7.8 months Almost producing a new human being
The above 2 together 1 year
Decade:
Send packet CA->Netherlands->CA 4.8 years Average time it takes to complete a bachelor's degree