为什么uint_least16_t比uint_fast16_t快于x86_64中的乘法?

Luí*_*ier 11 c unsigned x86-64 multiplication

关于uint_fast*_t类型族,C标准还不太清楚.在gcc-4.4.4 linux x86_64系统上,类型uint_fast16_tuint_fast32_t大小均为8个字节.但是,8字节数的乘法似乎比4字节数的乘法慢得多.以下代码演示了:

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int
main ()
{
  uint_least16_t p, x;
  int count;

  p = 1;
  for (count = 100000; count != 0; --count)
    for (x = 1; x != 50000; ++x)
      p*= x;

  printf("%"PRIuLEAST16, p);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

我得到了在程序上运行time命令

real 0m7.606s
user 0m7.557s
sys  0m0.019s
Run Code Online (Sandbox Code Playgroud)

如果我将类型更改为uint_fast16_t(和printf修饰符),则时间变为

real 0m12.609s
user 0m12.593s
sys  0m0.009s
Run Code Online (Sandbox Code Playgroud)

那么,如果stdint.h头文件uint_fast16_t(以及uint_fast32_t)定义为4字节类型,那会不会更好?

Mec*_*cki 5

(u)int_(fast/least)XX_t如果系统尚未定义AFAIK编译器,则仅定义它们自己的类型版本。这是因为在单个系统的所有库/二进制文件中均等地定义这些类型非常重要。否则,如果不同的编译器对这些类型的定义不同,则使用CompilerA构建的库可能具有uint_fast32_t与使用CompilerB构建的二进制文件不同的类型,但该二进制文件仍可能链接到该库。没有正式的标准要求,系统的所有可执行代码都必须由同一编译器来构建(实际上在某些系统上,例如Windows,由各种不同的编译器来编译代码是相当普遍的)。如果现在此二进制文件调用该库的函数,则一切都会中断!

所以问题是:是在这里真正是GCC定义uint_fast16_t,还是实际上是Linux(在这里我是指内核)或什至是标准C Lib(在大多数情况下是glibc)来定义这些类型?由于如果Linux或glibc定义了这些约定,那么在该系统上构建的GCC只能采用这些约定建立的约定。同样适用于所有其他可变宽度的类型,太真:charshortintlonglong long; 所有这些类型在C标准中仅具有最小保证位宽度(并且int实际上是16位,因此在int32位平台上,它已经比标准要求的要大得多)。


除此之外,我实际上想知道您的CPU /编译器/系统出了什么问题。在我的系统上,64位乘法与32位乘法同样快。我修改了代码以测试16、32和64位:

#include <time.h>
#include <stdio.h>
#include <inttypes.h>

#define RUNS 100000

#define TEST(type)                                  \
    static type test ## type ()                     \
    {                                               \
        int count;                                  \
        type p, x;                                  \
                                                    \
        p = 1;                                      \
        for (count = RUNS; count != 0; count--) {   \
            for (x = 1; x != 50000; x++) {          \
                p *= x;                             \
            }                                       \
        }                                           \
        return p;                                   \
    }

TEST(uint16_t)
TEST(uint32_t)
TEST(uint64_t)

#define CLOCK_TO_SEC(clock) ((double)clockTime / CLOCKS_PER_SEC)

#define RUN_TEST(type)                             \
    {                                              \
        clock_t clockTime;                         \
        unsigned long long result;                 \
                                                   \
        clockTime = clock();                       \
        result = test ## type ();                  \
        clockTime = clock() - clockTime;           \
        printf("Test %s took %2.4f s. (%llu)\n",   \
            #type, CLOCK_TO_SEC(clockTime), result \
        );                                         \
    }

int main ()
{
    RUN_TEST(uint16_t)
    RUN_TEST(uint32_t)
    RUN_TEST(uint64_t)
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用未优化的代码(-O0),我得到:

Test uint16_t took 13.6286 s. (0)
Test uint32_t took 12.5881 s. (0)
Test uint64_t took 12.6006 s. (0)
Run Code Online (Sandbox Code Playgroud)

使用优化的代码(-O3),我得到:

Test uint16_t took 13.6385 s. (0)
Test uint32_t took 4.5455 s. (0)
Test uint64_t took 4.5382 s. (0)
Run Code Online (Sandbox Code Playgroud)

第二个输出非常有趣。@R ..在上面的评论中写道:

在x86_64上,32位算术永远不应比64位算术周期慢。

第二个输出显示对于32/16位算术无法说相同的话。即使32位CPU本身可以执行16位算术,但16位算术在32/64位CPU上的运行速度可能会明显降低。与某些其他CPU(例如PPC)不同,后者只能执行32位算术运算。但是,这似乎仅适用于我的CPU上的乘法,当将代码更改为加/减/除时,16位和32位之间不再有明显的区别。

上面的结果来自Intel Core i7(2.66 GHz),但如果有兴趣的人,我也可以在Intel Core 2 Duo(较旧的一个CPU一代)和Motorola PowerPC G4上运行此基准测试。


R..*_*R.. 2

是的,我认为这根本就是一个错误。不幸的是,你不能在不破坏 ABI 的情况下修复这样的错误,但这可能并不重要,因为几乎没有人(当然我知道没有库函数)真正使用这些*int_fast*_t类型。

  • @Steve Jessop:Linux 使用 [SYS V x86-64 ABI](http://www.x86-64.org/documentation/abi.pdf),并且它没有指定这些类型。 (2认同)