【问题标题】:Performance vs Correctness/Preference?性能与正确性/偏好?
【发布时间】:2010-10-16 23:48:45
【问题描述】:
typedef unsigned char uChar;
typedef signed char sChar;

typedef unsigned short uShort;
typedef signed short sShort;

typedef unsigned int uInt;
typedef signed int sInt;

typedef unsigned long uLong;
typedef signed long sLong;

我有一个 typedef 列表,所以当我定义变量时,我可以准确无误。例如,如果我只需要数字 0-5,我会使用 uChar。但我正在使用 C++ 并正在制作引擎。我正在阅读 .NET 上的布尔值占用 X 个字节,并且由于内存对齐,使用整数会更快。

由于内存对齐、性能等原因,是否有理由使用 int 而不是 uChar?

【问题讨论】:

  • 尝试使用 boost.cstdint 而不是你的 typedefs
  • 甚至不要使用 typedef,它们只是碍事,什么也没有保存。
  • 你不能使用 uchar;不能输入 unsigned char?您始终可以使用 sizeof() 测量对象的大小
  • 我会使用 int 除非有令人信服的理由使用其他东西(运输/存储包装等...)

标签: c++ optimization


【解决方案1】:

这是一种不太重要的过早优化。我会选择一个数据结构并继续使用它。一旦您的完整系统出现问题,请对其进行分析以找出问题所在。您猜测并击中糟糕性能的机会确实很小。

【讨论】:

  • 是的,达菲莫说的。这甚至不太可能成为您在项目中遇到的十大性能问题。
  • 有趣的是,我的答案是唯一被否决的答案。不过,这似乎是一个一致的意见。
  • 我不喜欢这个答案 - 一直在项目中选择效率较低的数据类型是一个巨大的过早悲观。
  • @kotlinski:这意味着“悲观”这个词的存在。显然它存在,但我想说它远非常见:)
  • @Merlyn:这与手头的问题非常相关 - stackoverflow.com/questions/312003/…
【解决方案2】:
  • 这些类型定义并不比它们的名称更准确。它们更简洁,但不标准。
  • 如果你想更准确,使用#include <stdint.h>得到int8_tuint32_t等。
  • 如果你需要担心内存对齐,你会通过其他方式找到。
  • 如果您需要存储大量布尔值,请查看 std::bitsetstd::vector<bool>
  • 如果您需要存储一个布尔值,请使用bool

【讨论】:

    【解决方案3】:

    您真的不想把时间浪费在不在关键路径上的东西上。一旦你的工作正常,那么你应该分析并查看问题出在哪里。然后,您可以加快故障点。

    一个快速但不起作用的系统是毫无价值的。一个运行缓慢的系统对某些人有用,并且随着它变得更快对更多人有用。

    还要记住,一个未优化但合适的算法几乎每次都会击败一个超级优化的差算法。

    【讨论】:

      【解决方案4】:

      由于 C 标准明确定义了超出无符号类型边界的影响,编译器可能必须添加额外代码以使其行为符合指示。因此,一种数据类型可能对内存中的事物更快,而另一种数据类型对保存在寄存器中的事物可能更快。例如,考虑代码:

      uInt16 var1; Int32 变量 2; 变量1++; 变量 2 = 变量 1;

      我使用的 ARM 处理器只有 32 位指令用于寄存器操作,但它可以执行 8、16 和 32 位的加载和存储。如果 var1 在内存中,它可以像 32 位整数一样被操作,但如果它在寄存器中,编译器必须在将其复制到 var2 之前添加一条指令来清除高位字。如果 var1 是有符号的 16 位整数,从内存中加载它会比无符号时慢(因为需要符号扩展),但如果它保存在寄存器中,编译器就不需要担心上位。

      【讨论】:

        【解决方案5】:

        在您开始考虑微优化性能之前,先模拟一个原型并在其上使用分析器。请记住:如果它是一个常数(或者甚至是系数的微小变化),Big-O 会以同样的方式对待它。

        根据我的经验,使用无符号类型会破坏许多常见的错误检查方法,并且会使您几乎立即遇到整数存储包装错误(和错误),但同时也使推理变得更加困难关于解决方案。

        此外,隐式转换在使用无符号类型时更容易出现错误。

        例如:

        #include<iostream>
        
        void SomeFunction(uint32_t value)
        {
          if(value < 0)
          {
            // unreachable code.  What do we do instead?
            throw std::runtime_error("value must be non-negative");
          }
        }
        
        uint32_t SomeOtherFunction()
        {
          return (uint32_t)2000000000 + (uint32_t)2000000000;
        }
        
        int main(int argc, char* argv[])
        {
          int someValue = -1;
          SomeFunction(someValue);
          someValue = SomeOtherFunction();
          std::cout << someValue;
        }
        

        -294967296

        【讨论】:

          【解决方案6】:

          是的,使用 int 而不是 char 通常会产生显着的性能免费赠品。因此,C 语言使用 int 来尝试匹配处理器中寄存器的本机大小。

          尽可能使用无符号整数是个好主意,并且仅在极少数/特定情况下,使用无符号整数以外的东西。除非你有充分的理由,否则避免使用小于 int 的东西。如果您尝试使用小于 int 的项目从编程习惯中获得一些免费的性能,您需要以另一种方式改变您的习惯。未签名也是如此,除非您有充分的理由使用已签名,否则请使用未签名的任何内容。

          基本上反汇编(或编译为asm),看看你最喜欢的和其他编译器正在生成什么,注意chars引起的未对齐寻址,注意高位的掩码,signed chars的符号扩展等。这些是有时免费,有时不取决于该字节的来源和去向以及平台。还可以尝试至少 x86 和 arm,也许是 mips、gcc 3.x、4.x 和 llvm。特别注意单个字符如何与声明行中的整数列表混合可能导致后面的整数不对齐,从地址的角度来看这对于 x86 来说很好,但会降低性能(在 x86 上,即使使用缓存)。首先放置对齐的变量,然后最后放置未对齐的变量。其他不能或不喜欢不进行未对齐访问的平台将浪费额外的字节作为填充,因此您不一定会节省内存。过早的优化正在尝试调整到可变长度。使用简单的习惯,例如对所有内容都使用无符号整数,除非您有特定原因,将较大的、对齐的变量和结构放在声明列表的首位,未对齐的内容放在最后(短裤然后是字符)。

          乘法(和除法)使这种习惯变得丑陋,避免代码中的乘法和除法是最好的习惯。如果您必须使用一个,请对其实现非常了解。例如,将两个字符相乘而不是两个整数要好得多(如果数字支持的话),所以如果你碰巧知道整数实际上是 7 位或 5 位或任何数量,请将它们向下键入以进行乘法并允许发生硬件乘法而不是软乘法。 (如果这些变量大小发生变化,可能是休眠错误!!)。尽管许多处理器都有硬件乘法器,但实际上很少能直接使用它。除非您帮助编译器,否则它必须进行库调用以检查溢出等,并且最终可能会执行软乘法,这非常昂贵。除法不好,因为大多数处理器不包括除法。如果他们这样做,您可能会落入同一个陷阱。乘以 N 位 * N 位变成 2*N 位的结果,这就是乘法问题出现的地方。除法时,数字保持不变或变小。在这两种情况下,isas 并不总是提供足够的位来覆盖溢出,并且需要一个库调用来解决处理器硬件限制。

          浮点是一个类似的故事,只是要小心浮点。除非绝对必要,否则不要使用它。大多数人都不记得了

          浮动一个; 浮动 b; ... b = a * 1.0;

          除非另有说明,否则 C 假定为双精度,因此上述乘法需要将 a 转换为双精度,然后相乘,然后将结果转换回单精度。有些 fpus 可以在同一条指令中以时钟为代价进行精度转换,有些则不能。精度转换是大多数浮点处理器错误存在(或确实存在)的地方。因此,要么对所有内容都使用双打,要么小心编码以避免这些陷阱:

          浮动一个; 浮动 b; ... b = a * 1.0F;

          此外,大多数 isas 没有 FPU,因此避免浮点数学比避免定点乘法和除法更重要。假设大多数 fpus 都有错误。很难编写好的浮点代码(程序员经常因为不知道如何使用它并为其编写代码而丢掉了相当多的精度)。

          一些简单的习惯和你的代码作为免费赠品运行起来明显更快、更干净。此外,编译器不必努力工作,因此您会遇到更少的编译器错误。

          编辑添加浮点精度示例:

          浮动乐趣1(浮动一个) { 返回(a*7.1); } 浮动乐趣2(浮动一个) { 返回(a*7.1F); } 第一个函数包含: mulsd .LC0(%rip), %xmm0 使用 64 位浮点常量 .LC0 .long 1717986918 .long 1075603046 第二个函数包含所需的单精度乘法 mulss .LC1(%rip), %xmm0 具有单个精度常数 .LC1 .long 1088631603 char fun1 ( 字符 a ) { 返回(a+7); } int fun2 ( int a ) { 返回(a+7); } 乐趣1: 添加 r0, r0, #7 和 r0, r0, #255 bx lr 乐趣2: 添加 r0, r0, #7 bx lr

          【讨论】:

          • 编译器不会优化浮点乘法和除法吗?我这里不是用 ASM 写的,它是 C++。另外,你有任何资料来支持这一点吗?不是我不信任你,而是..
          • 它不应该优化,就像你的程序说我希望这个变量是一个字符而不是一个 int 编译器必须遵守的那样。同样,像 1.0 这样的常量是双精度数,编译器应该尊重将整个方程式提升为双精度数。与使用 int 和 char 进行数学运算相同,char 被转换为 int 然后进行数学运算。然后将结果转换为匹配任何结果变量类型。
          • 我说的是用 C/C++ 编写代码。
          猜你喜欢
          • 1970-01-01
          • 2011-04-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-10-02
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多