【问题标题】:Why is that data structures usually have a size of 2^n?为什么数据结构的大小通常为 2^n?
【发布时间】:2009-11-29 20:51:19
【问题描述】:

是有历史原因还是什么?我见过很多次类似char foo[256];#define BUF_SIZE 1024 的东西。即使我大多只使用 2n 大小的缓冲区,主要是因为我认为它看起来更优雅,这样我就不必考虑具体的数字。但我不太确定这是否是大多数人使用它们的原因,更多信息将不胜感激。

【问题讨论】:

    标签: c data-structures size buffer


    【解决方案1】:

    可能有很多原因,尽管很多人会如你所说的那样只是出于习惯。

    它非常有用的一个地方是循环缓冲区的有效实现,尤其是在 % 运算符很昂贵的架构上(那些没有硬件划分 - 主要是 8 位微控制器)。通过在这种情况下使用 2^n 缓冲区,模数只是对高位进行位掩码的情况,或者在 256 字节缓冲区的情况下,只需使用 8 位索引并让它环绕。

    在其他情况下,与页面边界、缓存等对齐可能会为某些架构的优化提供机会——但这将是非常特定于架构的。但可能只是这样的缓冲区为编译器提供了优化的可能性,所以所有其他条件都相同,为什么不呢?

    【讨论】:

    • 一些系统也能够根据一些标准对用户空间进行零复制 i/o,其中一个是读/写是系统页面大小的倍数。
    【解决方案2】:

    缓存行通常是 2 的倍数(通常是 32 或 64)。是该数量整数倍的数据将能够适应(并充分利用)相应数量的高速缓存行。您可以将更多数据打包到缓存中,性能就越好。所以我认为以这种方式设计结构的人正在为此进行优化。

    【讨论】:

      【解决方案3】:

      除了其他人提到的另一个原因是,SSE 指令需要多个元素,并且输入的元素数量始终是 2 的幂。将缓冲区设为二的幂可以保证您不会读取未分配的内存。这仅适用于您实际使用 SSE 指令的情况。

      我认为,在大多数情况下,最重要的原因是程序员喜欢二的幂。

      【讨论】:

        【解决方案4】:

        哈希表,按页分配

        这对哈希表很有帮助,因为您计算索引的模数是大小,如果该大小是 2 的幂,则可以使用简单的 bitwise-and 或 @987654321 计算模数@ 而不是使用实现% 运算符的慢得多的除类指令。

        看一本旧的 Intel i386 书,and 是 2 个周期,div 是 40 个周期。由于分工的基本复杂性要大得多,今天的差异仍然存在,尽管 1000 倍更快的整体周期时间往往会掩盖即使是最慢的机器操作的影响。

        也有一段时间,malloc 开销偶尔会被大量避免。直接从操作系统获得的分配将是(仍然是)特定数量的页面,因此 2 的幂可能会充分利用分配粒度。

        而且,正如其他人所指出的,程序员喜欢二的幂。

        【讨论】:

        • 值得注意的是,一些哈希算法建议使用等于某个素数而不是 2^n 的哈希大小,以使其行为更可预测;这些算法通常具有强大的理论计算机科学背景。
        • "分配粒度" 但是,可能会出错。如果您的内存分配器有任何每个项目的开销(可能是为了存储分配的大小),那么 2 的幂实际上会使分配器粒度的使用最差。最好忽略这个问题,先按你说的做,即选择最方便的尺寸,希望最好,并在必要时进行特定于平台的优化。或者,如果您想依靠它,那就做许多游戏都会做的事情:编写自己的分配器以消除所有疑虑。
        • 不仅仅是哈希表使用“以数组大小为模”操作——循环缓冲区是另一个例子。
        【解决方案5】:

        我能想到几个原因:

        1. 2^n 是所有计算机尺寸中非常常见的值。这与计算机中位的表示方式(2 个可能值)直接相关,这意味着变量往往具有边界为 2^n 的值范围。
        2. 由于以上几点,您经常会发现值 256 作为缓冲区的大小。这是因为它是一个字节中可以存储的最大数。因此,如果您想将字符串与字符串的大小一起存储,那么将其存储为:SIZE_BYTE+ARRAY 将是最有效的,其中大小字节告诉您数组的大小。这意味着数组可以是 1 到 256 之间的任意大小。
        3. 很多时候,大小是根据物理因素来选择的(例如,操作系统可以选择的内存大小与 CPU 寄存器的大小等有关),这些也将是一个特定数量的比特。这意味着,您可以使用的内存量通常为 2^n(对于 32 位系统,为 2^32)。
        4. 这些值可能存在性能优势/对齐问题。大多数处理器一次可以访问一定数量的字节,因此即使您有一个大小为 20 位的变量,无论如何,32 位处理器仍将读取 32 位。因此,将变量设为 32 位通常效率更高。此外,某些处理器要求变量与一定数量的字节对齐(因为它们无法从内存中的奇数地址读取内存)。当然,有时它不是关于奇数内存位置,而是 4 的倍数或 8 的 6 等位置。所以在这些情况下,让缓冲区始终对齐会更有效.

        好的,这些点有点混乱。如果您需要进一步解释,请告诉我,尤其是第 4 点,IMO 最重要。

        【讨论】:

          【解决方案6】:

          因为电子学中以 2 为底的算术很简单(另请阅读成本):左移(乘以 2)、右移(除以 2)。

          在 CPU 域中,许多构造都围绕以 2 为底的算术。访问内存结构的总线(控制和数据)通常在 2 次幂上对齐。电子设备(例如 CPU)中逻辑实现的成本使得以 2 为基数的算术具有吸引力。

          当然,如果我们有模拟计算机,情况就会不同。


          仅供参考:位于 X 层的系统的属性是位于下面的系统的 server 层属性的直接结果,即 layer

          例如可以在“编译器”级别操作的属性是继承并派生自其下系统的属性,即 CPU 中的电子设备。

          【讨论】:

            【解决方案7】:

            我打算使用 shift 论点,但可以想出一个很好的理由来证明它的合理性。

            缓冲区是 2 的幂的一个优点是循环缓冲区处理可以使用简单的与而不是除:

            #define BUFSIZE 1024
            
            ++index;                // increment the index.
            index &= BUFSIZE;       // Make sure it stays in the buffer.
            

            如果不是 2 的幂,则有必要使用除法。在过去(现在是小筹码)很重要。

            【讨论】:

            • 我想你想要 index &= BUFSIZE - 1 那里
            • +1 表示“聪明”的代码以效率的名义得到错误的答案。
            • 你是绝对正确的。它应该是 index %= BUFSIZE,这在所有情况下都是正确的,并且在任何体面的编译器中也可以使用 AND 实现。
            【解决方案8】:

            页面大小为 2 的幂也很常见。

            在 linux 上,我喜欢在执行诸如分块缓冲区并将其写入套接字或文件描述符之类的操作时使用 getpagesize()。

            【讨论】:

            • 常见的声音就像您看到的页面大小是 not 2 的幂。我很难相信任何硬件都能做到这一点(所有页表查找我已经所见是基于虚拟地址的特定位。其余位必然形成 2 的幂)
            • 取决于您的计数方式。 :-) PDP-8 有 128 个字的页面,但是 12 位字,所以它是 2 的幂,但不是位。
            • 我说的是共同的原因,我知道有人会发现一些古老的建筑,这是规则的例外。那么在那种情况下,PDP-8 使用了 12 位 CPU? CPU 位大小始终是我认为的字长。
            • 1970 年代是“古老的”?妈的,感觉自己老了:-) 那么在 1995 年改用 PPC 之前使用 48 位处理器的 IBM AS/400 怎么样? 21 世纪的 x86 单一文化令人沮丧。
            • 谁谈到了 x86 ? powerpc,一些 risc,68000 ......我们也有很多游乐场:D。说真的,我同意你的观点,这取决于你如何计算。我坚持我的评论,硬件实现者将继续使用位子集来选择页面和/或位子集来从单个页面中选择元素。
            【解决方案9】:

            这是一个很好的以 2 为底的整数。就像 10、100 或 1000000 是很好的以 10 为底的整数一样。

            如果它不是 2 的幂(或接近的值,例如 96=64+32 或 192=128+64),那么您可能想知道为什么会增加精度。不是以 2 为基础的舍入大小可能来自外部约束或程序员的无知。你会想知道它是哪一个。

            其他答案也指出了许多在特殊情况下有效的技术原因。此处不再赘述。

            【讨论】:

            • 10、100 或 1000000 都是不错的整数,任何基数。 :)
            【解决方案10】:

            在哈希表中,2^n 以某种方式更容易处理关键冲突。通常,当发生键冲突时,您要么制作子结构,例如具有相同哈希值的所有条目的列表;或者您找到另一个空闲插槽。您可以将 1 添加到插槽索引,直到找到空闲插槽;但是这种策略并不是最优的,因为它会创建阻塞位置的集群。更好的策略是计算第二个哈希数h2,使得gcd(n,h2)=1;然后将 h2 添加到插槽索引,直到找到一个空闲插槽(环绕)。如果 n 是 2 的幂,找到满足 gcd(n,h2)=1 的 h2 很容易,每个奇数都可以。

            【讨论】:

              猜你喜欢
              • 2014-12-29
              • 2017-08-25
              • 2018-11-08
              • 2011-05-14
              • 2019-02-03
              • 1970-01-01
              • 2016-02-15
              • 1970-01-01
              • 2020-11-23
              相关资源
              最近更新 更多