我是否更喜欢在C ++代码中使用小类型的int(int8和int16)?

时间:2015-07-02 19:49:13

标签: c++ performance qt integer embedded

我在嵌入式Linux的C ++ / Qt项目中工作,在那里我们经常"决斗"特别是在更新用户界面中的图形时,特别是我们的处理器的局限性。由于这些限制(特别是我们前一段时间甚至更糟糕的情况),我会尽可能地优化代码,如果优化成本最低的话。我正在做的一个优化是总是使用正确的整数值来处理我的情况:qint8,qint16和qint32,具体取决于我需要的值有多大。

但是前段时间我在某个地方读到,在可能的情况下,我不应该尝试使用最小的整数,我应该总是更喜欢使用与处理器容量相关的整数值,也就是说,如果我的处理器是32-面向比特,然后我宁愿使用qint32,即使不需要这么大的整数。在第一时刻,我无法理解为什么,但是the answer to this question建议这是因为处理器的性能必须更高,因为它必须使用"默认大小的整数"。 / p>

嗯,我不相信。首先没有提供实际参考来证实这样的论点:我只是不明白为什么从32位存储器空间写入和读取比使用32位整数更慢(并且给出的解释不是&#39 ;很容易理解,顺便说一下。其次,当我需要将数据从一侧传输到另一侧时,例如使用Qt的信号和插槽机制时,我的应用程序上有一些时刻。由于我将数据从一个点传输到另一个点,因此不应该对较大的数据进行改进吗?我的意思是发送两个字符(不是通过引用)的信号是不是应该更快地完成工作然后发送两个32位整数?

事实上,虽然&#34;处理器解释&#34;建议使用处理器的特性,其他情况则表明相反。例如,在处理数据库时,thisthis线程都表明在使用较小版本的整数时有一个优势(即使只是在某些情况下)。< / p>

所以,毕竟,当上下文允许或不允许时,我是否应该使用小类型的int?或者,当一种方法或另一种方法更有可能给出更好或更差的结果时,是否有一个案例列表? (例如,在使用数据库时我应该使用int8和int16,但在所有其他情况下我应该使用我的处理器的默认类型)

作为最后一个问题:Qt通常具有基于int的函数实现。在这种情况下,施法操作是否会消除使用次要整数可能带来的任何改进?

6 个答案:

答案 0 :(得分:4)

如果没有指定特定的CPU,这个问题实在太宽泛了。因为一些32位CPU有足够的指令来处理较小的类型,有些不是。一些32位CPU可以平滑地处理错位访问,有些会因为它而产生较慢的代码,而有些则在遇到它时会停止并着火。

话虽如此,首先在每个C和C ++程序中都存在标准整数提升的情况,它会隐式地将您使用的所有小整数类型转换为int

只要结果与非优化代码相同,编译器就可以自由地使用标准中指定的整数提升,或者优化它,无论哪种方法都能产生最有效的代码。

隐式促销可能会创建更有效的代码,但如果程序员不了解各种隐式类型提升规则的工作原理,它也可能会产生意外类型和签名更改的微妙,灾难性错误。可悲的是,很多想成为C和C ++的程序员都没有。当使用较小的整数类型时,你需要成为一个更有能力/更清醒的程序员,而不是只使用32位大小的变量。

因此,如果您正在阅读此内容,但从未听说过整数提升规则通常的算术转换 / 平衡,那么我会强烈建议您立即停止任何手动优化整数大小的尝试,而是转而使用read up这些隐式促销规则。

如果您了解所有隐式促销规则,则可以使用较小的整数类型进行手动优化。但是使用那些为编译器提供最大灵活性的方法。那些是:

#include <stdint.h>

int_fast8_t
int_fast16_t
uint_fast8_t
uint_fast16_t

当使用这些类型时,编译器可以自由地将它们更改为更大的类型,如果这会产生更快的代码。

上述变量仅仅依赖于整数提升/表达式优化的区别在于,对于快速类型,编译器不仅可以决定哪种类型最适合给定计算的CPU寄存器,还可以决定内存消耗和对齐时变量已分配。

答案 1 :(得分:3)

反对使用小变量的一个强有力的论据是,当映射到寄存器时(假设它们没有被隐式扩展),如果你的ISA使用部分寄存器,它们可能会导致意外的错误依赖。 x86就是这种情况,因为一些旧程序仍然使用AH或AX,而它们的对应物则是8/16位大小的寄存器。如果您的寄存器在上部有一些值(由于先前写入完整寄存器),您的CPU可能被强制携带并将其与您计算的任何部分值合并以保持正确性,从而导致依赖性的串行链即使你的计算是独立的。

你链接的答案提出的内存声明也有,虽然我发现它有点弱 - 内存子系统通常工作在完整的缓存行粒度(这些天通常是64字节),然后旋转和掩码但是,仅此一点不会对性能产生影响 - 如果您的数据访问模式显示空间局部性,它可以提高性能。在某些情况下,较小的变量也可能会增加引起对齐问题的风险,特别是如果您密切包装不同大小的变量,但大多数编译器应该知道更好(除非明确强制不要)。

我认为小变量超过内存的主要问题是再次 - 增加错误依赖的可能性 - 合并是由内存系统隐式完成的,但如果其他内核(或套接字)正在共享某些变量,你冒着将整条线从缓存中敲出的风险)

答案 2 :(得分:1)

总是使用int的概念通常适用于临时值(如循环变量),因为对于许多操作或库调用,它很可能被提升为int。

在存储大量数据时,特别是在数组中,使用较小的类型要好得多。问题是,有多少是大的,不幸的是情境化。

结构填充还可以为您提供一些关于什么时候可以免费使用完整int的空间。例如,如果有3 short s,则最常用的可能是int。另外,您应该按大小对成员进行排序,以避免因填充而产生不必要的间隙。

不幸的答案,特别是如果您正在使用像嵌入式Linux这样的资源受限环境,那就是进行测试。有时它会值得空间,有时它不会。

答案 3 :(得分:1)

一般来说,过早优化几乎没有用处。对于局部变量和较小的类和结构,使用非本机类型几乎没有任何好处。根据过程调用标准,将较小类型打包/解包到单个寄存器中甚至可能会添加比字大小类型成本更多的代码。

对于较大的数组,列表/树节点(IOW:较大的数据结构),但事情可能会有所不同。这里可能值得使用合适的类型,而不是自然的,使用没有方法的C-stype结构等。对于大多数&#34;现代&#34; (自上世纪末)Linux兼容架构,对于较小的整数类型,大多数都没有惩罚。对于浮点类型,可能存在仅支持浮点数的架构,而不是硬件加倍或双重处理需要更长时间。对于这些,使用较小的类型不仅可以减少内存占用,而且还可以更快。

不是减少成员/变量的类型,而是优化类层次结构,甚至对某些部分使用本机C代码(或C样式编码)。像虚拟方法或RTTI这样的东西可能非常昂贵。前者使用大型跳转表,后者为每个类添加描述符。

请注意,某些语句假定代码和数据驻留在RAM中,这是(嵌入式)Linux系统的典型代码。如果代码/常量存储在Flash中,则必须根据对相应内存类型的影响对语句进行排序。

答案 4 :(得分:1)

这取决于你在做什么,如果每个项目经过一些数学运算,那么根据指令集,它可能需要屏蔽掉并且签名扩展小于寄存器的任何东西,如果你匹配寄存器大小那么你不会有这个问题,并且一直生成额外的代码。我假设这就是int或任何标准C / C ++变量类型与大小无关的原因,因此使用它们。

还存在对齐的问题,较新的处理器更好,但无论如何,如果您未对齐ram总线或处理器总线边界,即使您匹配处理器大小,您也可以/将消耗额外的时钟周期。同样取决于处理器和从其扩展的所有控制器逻辑,同一区域内的多字节访问可能导致单独的总线周期,即使处理器可以在指令内为您扩展或零扩展。当然,一些处理器本身或延伸出来的总线可能会优化,你只需要读取该字,这里是一个字节,然后你点击缓存,如果你有一个字节读取可以/将导致多个字被提取和存储,因此即使处理器确实需要多个周期,它们也会更短更快。在4或8或更大字节边界上保持对齐的小小努力有助于几乎所有平台上的性能(如果您从高级代码的角度以大块或更大的数量移动数据)

对于blob数据,你可能只是四处移动而不是处理单个字节,然后烧32位存储8或16显然是浪费ram,如果你有一个就浪费缓存,性能因为缓存空间可能已被用于其他事情或持续时间更长。

你进入了过早优化的境界。你可以做一些这样的事情作为习惯来避免在这里和那里浪费一些指令(例如使用unsigned int或int作为循环变量,让编译器选择大小,构建具有对齐/更大项目的结构然后更小项目最后),但让你的代码工作和调试,隔离性能问题,如果有的话,然后权衡优化平台/编译器与可移植性和可维护性。或者只是扁平化保留功能的高级语言版本,并为特定平台提供备用手动调整的汇编版本,这样您就可以返回(甚至可以编译然后调整/修复asm)。但是,只有当你真的需要额外的性能并且你已经隔离了低性能部分并且愿意为它付出代价时才会再次。

答案 5 :(得分:-1)

有关整体推广的信息:

http://en.cppreference.com/w/cpp/language/implicit_cast

  

小积分类型(例如char)的Prvalues可以转换为   较大整数类型的prvalues(例如int)。特别是,   算术运算符不接受小于int的类型   参数和积分促销会在之后自动应用   如果适用,左值到右值的转换。总是这种转换   保留价值。