11

C 標準はuint_fast*_t、型のファミリについて非常に明確ではありません。gcc-4.4.4 linux x86_64 システムでは、型uint_fast16_tと型uint_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;
}

プログラムで time コマンドを実行すると、

real 0m7.606s
user 0m7.557s
sys  0m0.019s

タイプをuint_fast16_t(およびprintf修飾子)に変更すると、タイミングは次のようになります

real 0m12.609s
user 0m12.593s
sys  0m0.009s

uint_fast16_tでは、stdint.h ヘッダー(および uint_fast32_t) が 4 バイト型であると定義されていれば、それほど良くないでしょうか?

4

5 に答える 5

5

(u)int_(fast/least)XX_t私の知る限り、コンパイラは、システムによってまだ定義されていない場合にのみ、独自のバージョンの型を定義します。これは、これらの型が単一システム上のすべてのライブラリ/バイナリで等しく定義されていることが非常に重要だからです。それ以外の場合、異なるコンパイラがこれらの型を異なる方法で定義する場合、CompilerA でビルドされたライブラリは CompilerBuint_fast32_tでビルドされたバイナリとは異なる型を持つ可能性がありますが、このバイナリはライブラリに対してリンクする可能性があります。システムのすべての実行可能コードが同じコンパイラーによってビルドされなければならないという正式な標準要件はありません(実際には、Windows などの一部のシステムでは、コードがあらゆる種類の異なるコンパイラーによってコンパイルされているのが一般的です)。このバイナリがライブラリの関数を呼び出すと、問題が発生します!

問題は、ここで uint_fast16_t を定義するのは本当に GCC なのか、それとも実際に Linux (ここではカーネルを意味する) なのか、あるいはそれらの型を定義する標準 C Lib (ほとんどの場合 glibc) なのか? Linux または glibc がこれらを定義している場合、そのシステム上に構築された GCC は、これらが確立した規則を採用する以外に選択肢がありません。他のすべての可変幅型にも同じことが言えます: char, short, int, long, long 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;
}

最適化されていないコード (-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)

最適化されたコード (-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)

2 番目の出力は非常に興味深いものです。@R .. は上記のコメントに次のように書いています。

x86_64 では、32 ビット演算が 64 ビット演算より遅くなることはありません。

2 番目の出力は、32/16 ビット演算については同じことが言えないことを示しています。私の x86 CPU は 16 ビット演算をネイティブに実行できますが、32/64 ビット CPU では 16 ビット演算が大幅に遅くなる可能性があります。たとえば、32 ビット演算しか実行できない PPC などの他の CPU とは異なります。ただし、これは私の CPU の乗算にのみ適用されるようです。コードを変更して加算/減算/除算を行うと、16 ビットと 32 ビットの間に大きな違いはなくなりました。

上記の結果は Intel Core i7 (2.66 GHz) のものですが、興味のある方は、このベンチマークを Intel Core 2 Duo (1 世代前の CPU) および Motorola PowerPC G4 でも実行できます。

于 2012-08-28T11:39:24.247 に答える
3

このような設計上の決定は簡単ではないと思います。それは多くの要因に依存します。現時点では、あなたの実験を決定的なものとは考えていません。以下を参照してください。

まず第一に、断食が何を意味するかについて、単一の概念のようなものはありません。ここであなたはその場での乗算を強調しましたが、これは 1 つの特定の観点にすぎません。

次に、x86_64 はアーキテクチャであり、プロセッサではありません。そのため、そのファミリのプロセッサが異なれば、結果はまったく異なる可能性があります。特定のプロセッサを最適化する特定のコマンドライン スイッチに依存して gcc の型が決定されるのは正気ではないと思います。

あなたの例に戻りましょう。アセンブラコードも見たことがあると思いますか?たとえば、コードを実現するために SSE 命令を使用しましたか? プロセッサ固有のオプションをオンに切り替えました-march=nativeか?

編集:私はあなたのテストプログラムで少し実験しました.そのままにしておくと、基本的にあなたの測定値を再現できます. しかし、それを変更して遊んでみると、それが決定的なものであるとはさらに確信が持てません.

たとえば、内側のループも下向きに変更すると、アセンブラは以前とほとんど同じように見えますが (デクリメントと 0 に対するテストを使用)、実行には約 50% の時間がかかります。したがって、タイミングは、ベンチマークする命令の環境、パイプラインのストールなどに大きく依存すると思います。命令が異なるコンテキストで発行され、アラインメントの問題とベクトル化が行われる非常に異なる性質のコードをベンチに置いて、s に適切な型を決定する必要fast typedefがあります。

于 2010-11-07T07:44:43.083 に答える
2

はい、これは単なる間違いだと思います。残念ながら、ABI を壊さずにこのような間違いを修正することはできませんが、事実上誰も (そして確かに私が知っているライブラリ関数も)*int_fast*_t型を実際に使用していないため、問題ではないかもしれません。

于 2010-11-07T03:03:00.197 に答える
1

高速な整数型に興味があったという理由だけで、セマンティック部分で配列と C++ コンテナーのインデックス付けに整数型を使用する実際のパーサーのベンチマークを行いました。単純なループではなくさまざまな操作を実行し、プログラムのほとんどは選択した整数型に依存しませんでした。実際、私の特定のデータでは、任意の整数型で問題ありませんでした。したがって、すべてのバージョンで同じ出力が生成されました。

アセンブリ レベルでは、8 つのケースがあります。サイズが 4 つ、署名が 2 つです。24 の ISO C タイプ名は、8 つの基本タイプにマップする必要があります。Jens がすでに述べたように、「適切な」マッピングでは、特定のプロセッサと特定のコードを考慮する必要があります。したがって、実際には、コンパイラの作成者が生成されたコードを知っている必要がありますが、完全な結果を期待するべきではありません。

この例の多くの実行は平均化されているため、実行時間の変動範囲は指定された最小桁の約 2 です。この特定のセットアップの結果は次のとおりです。

  • int16_t/uint16_tint64_t/の間にそれぞれ実行時の違いはありませんuint64_t
  • 署名されていないバージョンは、それぞれint8_t/uint8_tint32_t/ではるかに高速ですuint32_t
  • 署名されていないバージョンは、署名されているバージョンより常に小さい (テキストとデータ セグメント)。

コンパイラ: g++ 4.9.1、オプション: -O3 mtune=generic -march=x86-64

CPU: Intel™ Core™ 2 Duo E8400 @ 3.00GHz

マッピング

| | |整数| | |
|記号|サイズ | タイプ |
| | |[ビット] | | |
|:--:|------:|:----------------------------------- --------------------------------:|
| | あなた | 8 | uint8_t uint_fast8_t uint_least8_t |
| | s | 8 | int8_t int_fast8_t int_least8_t | int8_t
| | あなた | 16 | uint16_t uint_least16_t |
| | s | 16 | int16_t int_least16_t | int16_t
| | あなた | 32 | uint32_t uint_least32_t |
| | s | 32 | int32_t int_least32_t | int32_t
| | あなた | 64 | uint64_t uint_fast16_t uint_fast32_t uint_fast64_t uint_least64_t |
| | s | 64 | int64_t int_fast16_t int_fast32_t int_fast64_t int_least64_t |

サイズとタイミング

| | | | 整数 | | | | | | | | |
| | サイン | サイズ | テキスト | データ | BS | 時間 |
| | | | [ビット] | [バイト] |[バイト]|[バイト]| [ミリ秒] |
|:----:|--------:|--------:| -----:|------:|--------:|
| | あなた | 8 | 1285801 | 3024 | 5704 | 407.61 |
| | s | 8 | 1285929 | 3032 | 5704 | 412.39 |
| | あなた | 16 | 1285833 | 3024 | 5704 | 408.81 |
| | s | 16 | 1286105 | 3040 | 5704 | 408.80 |
| | あなた | 32 | 1285609 | 3024 | 5704 | 406.78 |
| | s | 32 | 1285921 | 3032 | 5704 | 413.30 |
| | あなた | 64 | 1285557 | 3032 | 5704 | 410.12 |
| | s | 64 | 1285824 | 3048 | 5704 | 410.13 |
于 2015-02-05T21:03:31.727 に答える