【问题标题】:Which is more efficient: Bit, byte or int?哪个更有效:位、字节还是整数?
【发布时间】:2010-01-01 04:37:13
【问题描述】:

假设您有一个类似于以下的结构:

struct Person {
  int  gender;         // betwwen 0-1
  int  age;            // between 0-200
  int  birthmonth;     // between 0-11
  int  birthday;       // between 1-31
  int  birthdayofweek; // between 0-6
}

就性能而言,存储每个字段的最佳数据类型是哪种? (例如位域、int、char 等)

它将在 x86 处理器上使用并完全存储在 RAM 中。需要存储相当大的数量(50,000+),因此需要考虑处理器缓存等。

编辑:好的,让我重新表述这个问题。如果内存使用不重要,并且无论使用哪种数据类型,整个数据集都无法放入缓存中,那么通常是使用较小的数据类型将更多的数据放入 CPU 缓存中更好,还是使用更大的数据类型更好允许 CPU 执行更快操作的数据类型?我要求这个仅供参考,因此不应考虑代码可读性等。

【问题讨论】:

  • 性能可能取决于您对结构的访问模式是什么,您可以描述预期的访问模式以获得合适的答案。
  • 感谢您的建议,我已经添加了我的 senario。
  • 性别可以表示为二进制 0 或 1,但更准确地表示为 0.0 和 1.0 之间的浮点数。
  • 我更喜欢将性别表示为单位圆中某处的复杂值。我还没有看到不复杂的涉及性别的东西。
  • 我最近在一个数据库上做了一些工作,其中“性别识别”与“生理性别”是不同的……这并不像大多数人想象的那么简单!

标签: c++ struct types


【解决方案1】:
  1. 别担心;使用语义正确且可读性最强的内容
  2. 没有始终正确的答案。这取决于平台和编译器。如果您真的在乎,那么您必须进行测试。

一般来说,我会坚持使用整数...除了可能应该是枚举的性别。

【讨论】:

  • 我很确定你知道这一点,但仅作记录:enum 也是 int。我同意你关于enum 的建议:-)。
  • 在 C++ 中,枚举有其 自己的 类型特征,并在 int 上下文中隐式转换为 int。
  • 你不想使用 unsigned 整数吗?这不像年龄/月份/..可以是负数。
  • @stijn:这有关系吗?年龄/月/...也不可能超过 2^31。
  • 是的,对我来说,有签名的你有可能得到否定而你没有没有签名,这似乎是不合逻辑的。还是真的变老而不是,呃,重生?不管怎样,到最后都无所谓,那是真的。
【解决方案2】:

int_fast#_t 来自 或 <boost/cstdint.hpp>。

也就是说,您将放弃简单性和一致性(这些类型可能是字符类型,例如,它们是 C/C++ 中的整数类型,这可能会导致令人惊讶的函数解析)而不仅仅是使用 int .

通过专注于算法复杂性和访问模式等其他领域,您会看到更显着的性能优势。

它将在 x86 处理器上使用并完全存储在 RAM 中。需要存储相当大的数量(50,000+),因此需要考虑处理器缓存等。

您仍然需要担心缓存(在您处于该优化级别之后),即使不会缓存整个数据。例如,您是否按顺序访问每个项目?不可预料的?还是按顺序从每个项目中提取一个字段?比较 struct { int a, b; } data[N]; 和 int data_a[N], data_b[N];。 (假设您一次需要所有的“a”,但可以忽略另一个,哪种方式对缓存更友好?)再一次,这听起来不像是您应该关注的主要领域。

【讨论】:

    【解决方案3】:

    使用的位数: 性别; 1/2(如果您想包括双性恋,则为 2 :)) 年龄; 8 (0-255) 出生月; 4 (16) 生日; 5 (32) 周生日; 3 (8)

    全部位数:小于 22。

    知道它在 x86 上运行,我们有 32 位的 int 数据类型。 所以建立你自己的可以接受 int 的例程

    读取(性别,int* pValue); 写(性别,int* pValue);

    通过使用移位和位掩码运算符来存储和 检索信息。对于您可以的字段 使用类型安全的枚举。

    速度非常快,内存占用极低。

    【讨论】:

      【解决方案4】:

      这取决于。你的内存不足了吗?那么内存效率就变得至关重要。是不是时间太长了?然后 CPU 时间,或者至少是感知到的用户响应时间,就变得至关重要。

      【讨论】:

        【解决方案5】:

        这取决于。许多处理器可以直接访问字,但需要额外的指令来访问八位字节或位。根据编译器和处理器,int 可能与可寻址字相同。但是速度真的很重要吗?可读性和可维护性可能更重要。

        【讨论】:

        • 你说的是内存对齐,对吧? AFAIK,在 Intel IA-32 (x86) 处理器上,访问不是int 对齐的内存位置(即最低两位不是00 的地址)会受到惩罚。因此,一旦您引入一个小于int 的字段,您的字段对齐方式最终可能不适合快速访问。例如,char 字段应该被“打包”在一起,以便下一个int 字段最终位于对齐的位置(从struct 的开始测量)。 (我相信 C++ 编译器不允许随机排列 struct 字段以自动对齐字段。)
        【解决方案6】:

        一般

        每种类型都有优点和缺点,特别是在某些情况下,每种类型的性能都会最高。

        • 可寻址类型(byte、char、short、int 和 x86-64 上的“long int”)都可以在单个操作中从内存中加载,因此它们在每个操作中的 CPU 开销最小基础。

        • 但是,将位字段或标志打包到一个或多个位中可能会导致整个程序更快,因为:

          • 他们更有效地使用缓存,这是一个巨大的胜利,轻松支付解包每个项目所需的一些额外 cpu 操作
          • 它们需要更少的 I/O 操作来从磁盘读取数据,而且这种额外的巨大优势很容易为更多的 CPU 操作支付费用,即使再次必须为每个项目支付 cpu 操作费用

        几十年来,处理器速度的发展速度一直快于磁盘和网络速度,现在单个 CPU 操作很少成为问题,尤其是在 C/C++ 情况下。您已经在使用武器库中最快的代码生成器。

        你提到的 in-RAM/not-in-cache 场景

        碰巧还有一个缓存因素需要考虑。由于 CPU 速度如此之快,因此执行时间很可能将由缓存负载上的 DRAM 访问主导。如果这是真的,那么打包数据仍然有优势,但对于通过表进行线性扫描,它会有所减弱。碰巧的是,现代 DRAM 按顺序读取的效率要高得多,因此您可以在不比随机读取单个地址所需的时间多得多的时间内填充整个缓存块。如果执行时间主要由数据结构的按序遍历决定,那么这对您有利,并且会缩小使用可寻址单元和打包数据结构之间的性能差异。

        担心重要的事情

        最后,这可能很明显,但无论如何我还是要说:哈希和树等映射方面的数据结构,以及算法的选择通常比机器操作调整具有更大的影响,机器操作调整仅提供本质上的线性优化。

        担心内存膨胀确实很重要,如果您的应用有任何可能不适合内存,这很重要。事实证明,虚拟存储对于保护和操作系统内核级别的内存管理非常重要,但它从未设法做到的一件事是允许程序增长到比可用 RAM 更大而不会使一切陷入困境。

        【讨论】:

          【解决方案7】:

          Int 是最快的。

          如果你在数组中使用它,你会浪费更多的内存,所以在这种情况下你可能想坚持使用字节。

          【讨论】:

            【解决方案8】:

            克里斯说的。如果这是您正在设计的假设程序,那么在此阶段尝试选择 int 与 uint8 从长远来看不会对您有所帮助。将精力集中在其他地方。

            如果归根结底,你有一个巨大的复杂系统,你已经进行了几轮优化,并且你很好奇效果是什么,那么将 int 切换到 uint8 可能(无论如何应该)很漂亮反正很容易。在那个阶段,您可以在实际用例中进行统计上有效的比较——而不是以前。

            【讨论】:

              【解决方案9】:
              enum Gender { Female, Male };
              
              struct Date {...};
              
              class Person
              {
                  Gender gender;
                  Date date_of_birth;
                  //no age field - can be computed from date_of_birth
              };
              

              【讨论】:

                【解决方案10】:

                任何一个版本都可以更快的原因有很多。

                通过使每个成员成为最少位数来打包结构意味着更少的内存流量和缓存使用,这将导致加速。这也意味着提取和访问/修改单个字段的更多工作,这将降低您的性能。这也可能意味着更多的代码,这将占用更多的指令缓存,因此会降低一些性能。

                真的不可能确定哪个因素会占主导地位。在大多数情况下,根据需要使用整数和字节是最有效的。不要打扰位域。

                但在某些情况下,情况并非如此。所以测量,基准,配置文件。 了解每个实现如何在您的代码中执行。

                【讨论】:

                  【解决方案11】:

                  在大多数情况下,您应该坚持 CPU 支持的默认字长。在许多情况下,大多数 CPU 会填充或要求编译器填充小于默认字长的值(以便结构可以字对齐),因此较小数据类型的任何内存使用增益都可能没有实际意义。

                  你可能有一个大数组的主要例外,例如将文件加载到字节[]数组中比 int[] 更可取。

                  一般来说,干净地建模问题,先做简单明显的事情,然后分析你是否有内存问题。查看提供的案例,有几点值得注意:

                  • 字段年龄是模型的反规范化,是birthmonth、birthday、birthdayofweek 的函数,可以表示为一个长的出生时间戳。因此,只需查看模型,您就可以轻松修剪 8 个字节。
                  • 4(每个 int 的字节数)* 5(字段数)* 50 000(实例数)= ~1MB。我的笔记本电脑有 4MB 二级缓存,您不太可能遇到内存问题。

                  【讨论】:

                    【解决方案12】:

                    这完全取决于一件事:

                    What is the ratio of blindly copying the data and actually reading the data?

                    如果您主要将数据视为 cookie,并且很少需要访问实际记录,请使用占用空间更少且对 IO 更友好的位字段。

                    如果您经常访问它,并且很少复制它(作为容器重新分配的一部分)或将其序列化到磁盘,请使用 CPU 友好的较大整数。

                    【讨论】:

                      【解决方案13】:

                      处理器字长是计算机可以读取/写入内存的最小单位。如果您在 CPU 中读取或写入位、char、short 或 int,则北桥开销在所有情况下在合理的系统/编译器行为下都是相同的。显然,较小的字段大小有 sram 缓存的好处。 x86 的一个问题是确保在字大小的倍数上正确对齐。其他架构(如 sparc)的容错性要小得多,如果内存访问未对齐,实际上会崩溃。

                      我倾向于回避在高性能多线程应用程序中小于计算机字长的数据类型,因为如果对与同一个字中的其他字段共享的字中的一个字段进行更改,如果访问word 不是外部同步的。

                      【讨论】:

                        【解决方案14】:
                        struct Person 
                        {
                          uint8_t gender;         // betwwen 0-1
                          uint8_t age;            // between 0-200
                          uint8_t birthmonth;     // between 0-11
                          uint8_t birthday;       // between 1-31
                          uint8_t birthdayofweek; // between 0-6
                        }
                        

                        如果您需要按顺序访问它们,缓存优化可能会胜出。因此,将数据结构减少到严格的最少信息可能会更好。

                        有人可能会争辩说,CPU 无论如何都必须在内存中获取 32 位块,然后需要提取字节,因此需要更多时间。但是,这是 CPU 的任意选择,它不应该影响您的数据结构。想象一下,如果明天 CPU 开始使用 64 位块,您的“优化”将立即变得徒劳,因为无论如何都会有提取。但是,几乎可以肯定,内存缓存优化总是是相关的。这就是为什么我喜欢优先考虑让应用程序尽可能轻巧,内存方面,即使涉及性能。

                        此外,如果您非常关心内存,您显然可以通过将其中一些变量打包在一起来达到字节以下。但是随后您将需要使用位操作,因此需要额外的指令,并且您的代码可能很快变得不可读/不可维护,除非您将存储形式化为某种通用容器/结构。

                        【讨论】:

                          【解决方案15】:

                          问题是,我没有看到Person 的大字段被评估。因此,速度不是这里的问题。

                          问题在于一致性。您有一个包含公共成员的结构,很容易将其设置为错误的数据,无论是超出范围还是被索引关闭。

                          我的意思是,你自己在这里并不一致,是吗?

                          int  birthmonth;     // between 0-11
                          int  birthday;       // between 1-31
                          

                          为什么月份从零开始,而天却不是?

                          即使你有这样的一致性,也许一个使用你的结构的人会使用“月份从零开始”,一个人使用“月份从一开始”。这会引发错误。为什么有年龄限制?当然,我们不会找到一个 200 岁的人,但为什么不说 max of type 呢?

                          而是使用诸如枚举类之类的东西,一种限制您的范围的特殊类型,如template&lt;int from, int to&gt; class RangedInt 或已经存在的类型,如Date(特别是因为这种类型可以检查当年 2 月有多少天等等)。

                          这里有些人用性别理论的东西评论了性别,但无论如何,零不是性别。你的意思是男性和女性,但是当你说那一天在 0..31 范围内时我们有一些直觉,我们如何猜测零是男性还是女性?不应该被cmets管理。 (如果你真的想解决性别理论的问题,我会说正确的类型是string。)

                          此外,在大多数情况下,一旦创建了一个人,您就不会对其进行任何更改。现实生活中发生的变化(例如通过婚姻获得不同的名字)将发生在数据库级别等,而不是在班级级别。因此,我将其设为只能由构造函数设置的类。当然,这显然取决于使用情况。但如果你能做到不可变,那就让它不可变。

                          编辑:而且很容易使年龄与出生日期不一致。那不好。删除冗余数据,使其成为方法等。虽然缺少年份?奇怪的设计,我必须说。

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 2022-01-02
                            • 2010-10-08
                            • 1970-01-01
                            • 1970-01-01
                            • 2018-03-20
                            • 2021-11-25
                            • 1970-01-01
                            • 1970-01-01
                            相关资源
                            最近更新 更多