【问题标题】:Pros/cons to using char for small integers in C在 C 中将 char 用于小整数的利弊
【发布时间】:2010-12-23 17:40:40
【问题描述】:

在 C 中对小整数使用 char 有什么缺点吗?除了占用/内存优势之外,还有其他优势吗?

特别是,处理器在处理char 上的整数运算时是否可能比在 (long/short) int 上处理整数运算更好或更差?

我知道这将是特定于处理器/系统/编译器的,但我希望在一般情况下得到答案,或者至少是 32 位 Windows 和 Solaris 的一般情况,即我所使用的系统目前正在努力。我还假设已经处理了诸如溢出/环绕问题之类的问题。

更新:Visual Studio 6.0 实际上并没有 Christoph 建议的 stdint.h。在带有少量堆栈循环的 Windows(VS 6.0,调试版本,32 位)上进行一些基准测试,intlong 提供了相似的性能,大约是 char 的两倍。在 Linux 上使用 gcc 运行相同的测试,intlong 相似,并且都比 char 快,尽管差异不那么明显。

附带说明,我没有花太多时间寻找,但the first implementation of stdint.h for VS 6.0 I found(通过Wikipedia)将uint_fast8_t 定义为unsigned char,尽管至少在我的测试中这似乎更慢。因此,正如 Christoph 正确建议的那样,这个故事的寓意是:始终作为基准!

【问题讨论】:

    标签: c performance char int


    【解决方案1】:

    嗯,第一个问题是 C 标准没有定义纯 char 是有符号还是无符号 - 所以您可以移植依赖的唯一范围是 0 到 127。

    除此之外,一般int 应该是与架构的本机字长相对应的类型(但当然这不是由任何东西强制执行的)。这往往是算术性能最好的类型,但您只能说这些。

    请注意,在表达式求值期间,窄于 int 的操作数无论如何都会扩大到 intunsigned int

    【讨论】:

    • 是的,我被告知 int 具有处理器寄存器的大小,所以如果您使用 char,寄存器将不会满,但仍会使用相同的时间进行基本操作寄存器是否完全使用。
    • 此外,int8_t 和 uint8_t 是比 char 更好用的类型。
    • @Aif:据我所知,上/下转换需要更多时间。
    • 我遇到了这 2 个:eventhelix.com/realtimemantra/basics/… int over char 和 short> 和 en.wikibooks.org/wiki/Optimizing_C%2B%2B/Writing_efficient_code/…>,但我认为两者都不具有权威性。抱歉,没有确凿的证据,看起来更像是轶事。
    【解决方案2】:

    我会看到的主要问题是,您的代码使用的类型对具有其他含义的值意味着一件事——例如,存在可能是维护问题的语义问题。如果你这样做了,我可能会建议对它进行类型定义:

    typedef char REALLYSHORT;
    

    这样,A) 更清楚您在做什么,并且 B) 如果遇到麻烦,您可以轻松更改它(例如,只有一个地方)。

    你有非常好的理由不使用int吗?

    【讨论】:

    • 您的语义点已经在我见过的唯一实现的地方使用过。不,我没有任何充分的理由,我只是好奇它是否会有所作为。
    【解决方案3】:

    我能想到的另一个问题是(据我所知)“现代”处理器以“完整”整数(通常为 32 位)进行所有数学运算。因此,处理char 通常意味着从内存中拉出一个字节,在传输到寄存器时用 0 填充,对其进行处理,然后仅将结果的最低有效位压缩回内存。尤其是如果char 没有在方便的边界上对齐,则此内存访问需要更多的工作才能完成。

    char 用于int 仅在您拥有很多 个数字(即一个大数组)并且您需要节省空间时才真正有用。

    【讨论】:

    • 字符需要更少的缓存行填充并且更适合缓存。
    【解决方案4】:

    在内部,处理器通常对机器字执行算术运算。这意味着在执行其他类型的计算时,尽管计算本身将花费相同的时间,但取决于可用的指令集,可能需要做额外的工作来读取输入并将计算结果强制转换为目标类型(例如符号扩展/零填充、移位/屏蔽以避免未对齐的内存访问等)。

    这就是 C 定义类型和操作的原因 - int 的大小不是标准强制要求的,允许编译器作者使其对应于机器字,并且定义表达式评估以促进更小的整数类型到int,大大减少了必须将结果强制为某些目标类型的点数。

    使用char 存储整数值的正当理由是,当空间真的很重要(不像您想象的那么频繁)时,以及在描述您正在编组数据的某些外部数据格式/协议时。预计使用 char 会导致轻微的性能损失,尤其是在 Cell SPU 等硬件上,其中只有机器字大小的内存访问可用,因此访问内存中的字符需要多次移位和掩码。

    【讨论】:

      【解决方案5】:

      几乎可以肯定,对字符的算术实际上将使用与对整数的算术相同的寄存器来执行。例如:

      char c1 = 1;
      char c2 = c1 + 2;
      

      用 VC++ 编译成以下内容:

      00401030   movsx       eax,byte ptr [ebp-4]
      00401034   add         eax,2
      00401037   mov         byte ptr [ebp-0Ch],al
      

      其中 eax 是一个 32 位寄存器。

      因此,在算术性能方面,使用字符而不是整数没有优势。

      【讨论】:

      • 是的,但是由于对齐问题,访问字节不会变慢吗?访问是单行 ASM 的事实并不意味着没有更多的微代码正在执行。你对此有什么见解吗?
      • 有罪,因为“用 0 填充”和“压缩成一个字节”可能过于简化。但我确实相信在某种级别上会发生类似的事情。
      • 我不知道现代处理器的总线访问时间(我认为这是真正的问题),恐怕 - 我正在回答关于算术性能的问题。
      【解决方案6】:

      C99 添加了所谓的“最快”最小宽度整数类型来解决这个问题。对于您感兴趣的范围,类型为int_fast8_tuint_fast8_t,可在stdint.h 中找到。

      请记住,可能没有性能提升(内存消耗的增加甚至可能会减慢速度);一如既往,基准!不要过早地优化或仅根据可能有缺陷的假设进行优化。

      【讨论】:

      • 如果您关心可移植性(就像我在嵌入式目标和 PC 之间共享代码的地方),请不要使用快速类型来实际“存储”任何东西(类成员、结构成员等)。使用快速类型来迭代、访问数组或作为本地缓存变量。
      • @MaR:由于填充和字节顺序,使用固定大小的整数类型不足以保证结构可以跨架构进行序列化和反序列化;因为无论如何你都必须按顺序序列化结构成员,我认为对成员变量使用快速类型没有什么问题
      • @Christoph:这不仅仅是关于序列化,而且是真的——我的陈述太强了。它应该“小心”,因为在某些情况下增加尺寸会对您造成严重影响。
      • 基准测试当然是这个故事的寓意;我刚刚用我所做的一些基准测试的结果更新了这个问题。值得注意的是,我为 Visual Studio 找到的 stdint.h 实现将 uint_fast8_t 定义为远非我测试中最快的类型。
      • stdint.h 类型的一个主要 陷阱是,对于所有实际目的,无法保证有符号和无符号类型之间的数学运算何时会产生有符号或无符号结果(因为这取决于类型和问题的大小,而不是使用的类型,如果知道所讨论类型的大小,则不需要像 uint_fast8_t 这样的类型首先)。例如,int32_tuint_fast8_t 的总和可能是有符号或无符号 32 位整数,并且(在 8 位或 16 位机器上)两个 uint8_fast8_t 值的乘积......跨度>
      猜你喜欢
      • 2012-02-21
      • 1970-01-01
      • 1970-01-01
      • 2011-08-02
      • 1970-01-01
      • 1970-01-01
      • 2014-08-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多