哪个更有效:Bit,byte还是int?

时间:2010-01-01 04:37:14

标签: c++ struct types

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

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
}

在性能方面,哪个是存储每个字段的最佳数据类型? (例如,bitfield,intchar等。)

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

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

15 个答案:

答案 0 :(得分:13)

  1. 不要担心;使用语义正确且最易读的内容
  2. 没有任何答案一直都是正确的。这取决于平台和编译器。如果你真的在乎,那么你必须进行测试。
  3. 总的来说,我会坚持使用整体...除了应该是枚举的性别。

答案 1 :(得分:6)

来自< stdint.h>的

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',但可以忽略另一个,哪种方式更适合缓存?)同样,这听起来不像你应该关注的主要区域。

答案 2 :(得分:4)

使用的位数: 性别; 1/2(2,如果你想包括中间性:)) 年龄; 8(0-255) 出生月; 4(16) 生日; 5(32) birthdayofweek; 3(8)

比特:小于22。

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

read(gender,int * pValue); write(gender,int * pValue);

通过使用shift和位掩码运算符来存储和 检索信息。对于你可以的领域 使用类型安全的枚举。

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

答案 3 :(得分:3)

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

答案 4 :(得分:3)

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

答案 5 :(得分:2)

一般

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

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

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

    • 他们更有效地使用缓存,这是一个巨大的胜利,轻松支付解开每个项目所需的一些额外的cpu操作
    • 他们需要更少的I / O操作才能从磁盘读入,而这个额外的巨额胜利可以轻松支付更多的CPU操作,即使每次项目必须支付cpu操作

几十年来,处理器速度一直比磁盘和网络速度快,现在单个CPU操作很少受到关注,特别是在C / C ++情况下。您已经在使用了最快的代码生成器。

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

实际上还有一个需要考虑的缓存因子。由于CPU速度很快,因此执行时间可能会受到缓存加载时DRAM访问的支配。如果这是真的,那么打包数据仍然有一个优势,但是对于通过表的线性扫描,它有些暗淡。实际上,现代DRAM的读取效率要高得多,因此您可以在不超过随机读取单个地址所需的时间内填充整个缓存块。如果执行时间由数据结构的有序遍历支配,则这对您有利,并且倾向于使使用可寻址单元和打包数据结构之间的性能差异变平。

担心重要的事情

最后,它可能是显而易见的,但无论如何我会说:数据结构在地图方面如哈希和树,以及算法的选择通常比机器操作调整具有更大的影响,这仅提供基本上线性的优化。

担心内存膨胀确实很重要,如果你的应用程序有可能不适合内存,这很重要。虚拟存储对于保护和操作系统内核级别的内存管理非常重要,但是它从未设法做到的一件事是允许程序比可用内存大得多,而不会阻塞所有内容。

答案 6 :(得分:1)

Int是最快的。

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

答案 7 :(得分:1)

克里斯说的话。如果这是一个你正在设计的假想程序,那么在这个阶段尝试选择int与uint8并不会对你有所贡献。把你的努力集中在其他地方。

如果它归结为它,你有一个巨大的复杂系统,你已经进行了几轮优化,你很好奇效果是什么,切换到uint8很可能(应该是)很容易。在那个阶段,您可以在现实世界的用例中进行统计上有效的比较 - 而不是之前。

答案 8 :(得分:0)

enum Gender { Female, Male };

struct Date {...};

class Person
{
    Gender gender;
    Date date_of_birth;
    //no age field - can be computed from date_of_birth
};

答案 9 :(得分:0)

有两个版本可以更快的原因有很多。

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

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

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

答案 10 :(得分:0)

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

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

一般情况下,问题干净利落,先做简单明了的事情,然后分析一下,看看你是否有内存问题。看看提供的案例,值得注意几点:

  • 现场年龄是模型的去标准化,是出生月份,生日,生日周期的函数,可以表示为单个长出生时间戳。因此,只需查看模型,您就可以轻松修剪8个字节。
  • 4(每个int的字节数)* 5(字段数)* 50 000(实例数)= ~1MB。我的笔记本电脑有4MB L2缓存,你不太可能有内存问题。

答案 11 :(得分:0)

这完全取决于一件事:

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

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

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

答案 12 :(得分:0)

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

在高性能多线程应用程序中,我倾向于回避小于计算机字大小的数据类型,因为对同一单词中其他字段共享的单词中的一个字段的更改如果访问其中的所有字段,则可能会出现意外的依赖性单词不是外部同步的。

答案 13 :(得分:0)

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位块,你的&#34;优化&#34;会立刻变得徒劳无益,因为无论如何都会有提取。但是,几乎可以肯定,内存缓存优化总是是相关的。这就是为什么我喜欢特权使应用程序尽可能轻松,即使在关注性能时也要记忆。

此外,如果你非常关心内存,你可以通过将一些变量打包在一起来显然低于字节。但是,除非您将存储规范化为某种通用的容器/结构,否则您需要使用位操作,因此其他指令和代码很快就会变得不可读/不可维护。

答案 14 :(得分:0)

事实是,我没有看到Person被评估的大字段。因此,速度不是问题所在。

问题是一致性。你有一个公共成员的结构,人们可以很容易地设置错误的数据,超出范围或通过索引关闭。

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

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

为什么月份从零开始,但天数不是?

即使你有这种一致性,也许一个使用你的结构的人用“从零开始的几个月”,一个人用“几个月开始”。这会引发错误。为什么年龄有限制?当然,我们不会找到一个200岁的家伙,但为什么不说最大类型?

而是使用枚举类,限制范围的特殊类型,如template<int from, int to> class RangedInt或已存在的类型,如Date(特别是因为这种类型可以检查2月份的天数那年等等。

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

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

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