web-dev-qa-db-ja.com

組み込み型のパフォーマンス:char、short、int、float、double

これは少しばかげた質問のように見えるかもしれませんが、他のトピックでAlexandre Cの reply を見ると、組み込み型とパフォーマンスの違いがある場合に知りたいと思います。

char vs short vs int vs. float vs. double

通常、実際のプロジェクトではこのようなパフォーマンスの違いは考慮されませんが、教育目的でこれを知りたいと思います。一般的な質問は次のとおりです。

  • 整数演算と浮動小数点演算の間にパフォーマンスの違いはありますか?

  • どちらが速いですか?速くなる理由は何ですか?これを説明してください。

64
Nawaz

フロートと整数:

歴史的に、浮動小数点は整数演算よりもはるかに遅い可能性がありました。現代のコンピューターでは、これはもはや事実ではありません(一部のプラットフォームでは多少遅くなりますが、完全なコードを記述してすべてのサイクルで最適化しない限り、その違いはコードの他の非効率性によって圧倒されます)。

ハイエンド携帯電話のプロセッサのように、ある程度制限されたプロセッサでは、浮動小数点は整数よりも若干遅くなる場合がありますが、ハードウェア浮動小数点が利用可能な限り、一般に1桁以内(またはそれ以上)です。携帯電話がますます一般的なコンピューティングワークロードを実行するように求められているため、このギャップが急速に縮まっていることは注目に値します。

very限られたプロセッサ(安価な携帯電話とトースター)では、一般に浮動小数点ハードウェアがないため、浮動小数点演算をソフトウェアでエミュレートする必要があります。これは遅く、整数演算より数桁遅いです。

しかし、私が言ったように、人々は自分の電話やその他のデバイスがますます「本物のコンピュータ」のように振る舞うことを期待しており、ハードウェア設計者はその需要を満たすためにFPUを急速に強化しています。最後のサイクルごとに追跡している場合、または浮動小数点をほとんどまたはまったくサポートしていない非常に限られたCPUのコードを記述している場合を除き、パフォーマンスの違いは重要ではありません。

異なるサイズの整数型:

通常、CPUは、ネイティブのWordサイズの整数での動作が最も高速です(64ビットシステムに関するいくつかの注意事項があります)。 32ビット操作は、最新のCPUでの8ビットまたは16ビット操作よりも高速であることがよくありますが、これはアーキテクチャによってかなり異なります。また、CPUの速度を単独で考慮することはできません。それは複雑なシステムの一部です。 16ビット数での操作が32ビット数での操作よりも2倍遅い場合でも、32ビットではなく16ビット数で表すと、キャッシュ階層に2倍のデータを収めることができます。キャッシュミスを頻繁に行うのではなく、すべてのデータをキャッシュから取得することで違いが生じる場合は、メモリアクセスが速いほどCPUの動作が遅くなります。

その他の注意事項:

ベクトル化では、より狭い型(floatおよび8ビット整数と16ビット整数)を優先してバランスをさらに整えます。同じ幅のベクトルでより多くの操作を実行できます。ただし、優れたベクターコードを記述するのは難しいため、多くの注意深い作業を行わなくてもこのメリットを享受できるわけではありません。

パフォーマンスに違いがあるのはなぜですか?

操作がCPUで高速であるかどうかに影響する要因は、実際には2つだけです。それは、操作の回路の複雑さと、操作を高速にするためのユーザーの要求です。

(理由の範囲内で)チップ設計者が問題に十分なトランジスタを投入することをいとわなければ、どんな操作でも高速にできます。しかし、トランジスタにはお金がかかります(むしろ、多くのトランジスタを使用するとチップが大きくなり、ウエハあたりのチップ数が減り、歩留まりが低下し、コストがかかります)ため、チップ設計者はどの操作でどの程度複雑にするかをバランスを取る必要がありますこれは、(認知された)ユーザーの要求に基づいて行われます。おおまかに言って、操作を4つのカテゴリに分けることを考えるかもしれません。

                 high demand            low demand
high complexity  FP add, multiply       division
low complexity   integer add            popcount, hcf
                 boolean ops, shifts

ほぼすべてのCPUで、需要の高い、複雑さの低い操作が高速になります。これは、手間のかからない成果であり、トランジスタあたりの最大のユーザー利益をもたらします。

ユーザーは喜んで支払いを行うため、高価なCPU(コンピューターで使用されるCPUなど)では、需要が高く複雑な操作が高速になります。おそらく、トースターに高速のFP乗算を行うために余分な3ドルを支払うつもりはないので、安価なCPUはこれらの命令をスキップします。

通常、ほとんどすべてのプロセッサで、低需要で複雑度の高い操作が遅くなります。コストを正当化するのに十分なメリットがありません。

低需要、低複雑度の操作は、誰かがそれらについて考えることに煩わされると高速になり、そうでなければ存在しなくなります。

さらに読む:

  • Agner Fogは、ニース website を維持し、低レベルのパフォーマンスの詳細について多くの議論をしています(そして、それをバックアップするための非常に科学的なデータ収集方法論を持っています)。
  • Intel®64およびIA-32アーキテクチャ最適化リファレンスマニュアル (PDFダウンロードリンクはページの途中)では、これらの問題の多くについても説明していますが、特定のアーキテクチャファミリに焦点を当てています。
112
Stephen Canon

絶対に。

まず、もちろん、問題のCPUアーキテクチャに完全に依存します。

ただし、整数型と浮動小数点型は非常に異なる方法で処理されるため、次の場合がほぼ常に当てはまります。

  • 単純な操作の場合、整数型はfastです。たとえば、整数の加算には1サイクルのレイテンシしかなく、整数の乗算は通常、IIRCで約2〜4サイクルです。
  • 浮動小数点型を使用すると、実行速度が大幅に低下します。ただし、今日のCPUでは、優れたスループットがあり、各浮動小数点ユニットは通常サイクルごとに操作を廃止でき、整数操作と同じ(または同様の)スループットになります。ただし、一般的に待ち時間はさらに長くなります。多くの場合、浮動小数点加算のレイテンシは約4サイクルです(intの場合は1)。
  • 一部の複雑な操作では、状況が異なるか、逆になります。たとえば、FPでの除算は、整数の場合よりもless遅延が発生する可能性があります。どちらの場合でも操作が実装が複雑であるためです。 FP値なので、そのケースの最適化により多くの労力(およびトランジスタ)が費やされる可能性があります。

一部のCPUでは、doubleはfloatよりも大幅に遅い場合があります。一部のアーキテクチャでは、double専用のハードウェアがないため、フロートサイズのチャンクを2つ渡すことで処理され、スループットが低下し、レイテンシが2倍になります。その他(x86 FPUなど)では、両方のタイプが同じ内部形式の80ビット浮動小数点(x86の場合)に変換されるため、パフォーマンスは同じです。さらに、floatとdoubleの両方に適切なハードウェアサポートがありますが、floatのビット数が少ないため、floatを少し速くすることができ、通常、double操作に比べてレイテンシを少し減らします。

免責事項:上記のタイミングと特性はすべてメモリから取得されたものです。私はそれを調べていないので、間違っているかもしれません。 ;)

異なる整数型の場合、答えはCPUアーキテクチャによって大きく異なります。 x86アーキテクチャは、その複雑な歴史により、8、16、32(および今日64)の両方のビット操作をネイティブにサポートする必要があり、一般に、それらはすべて同等に高速です(基本的に同じハードウェアを使用し、ゼロ必要に応じて上位ビットを出力します)。

ただし、他のCPUでは、intよりも小さいデータ型はロード/ストアのコストが高くなる場合があります(メモリへのバイトの書き込みは、32ビットのWord全体をロードしてから行う必要がある場合があります。ビットマスキングを行ってレジスタ内の1バイトを更新し、Word全体を書き戻します。同様に、intより大きいデータ型の場合、一部のCPUは操作を2つに分割し、下半分と上半分を別々にロード/保存/計算する必要があります。

しかし、x86では、答えはほとんど問題ではないということです。歴史的な理由により、CPUは、すべてのデータタイプに対して非常に強力なサポートを提供する必要があります。気付く唯一の違いは、浮動小数点演算の方がレイテンシーが大きいことです(ただし、スループットが似ているため、少なくともコードを正しく記述している場合はslower自体ではありません)

11
jalf

整数の昇格規則については誰も言及していないと思います。標準C/C++では、intより小さい型では操作を実行できません。現在のプラットフォームでcharまたはshortがintよりも小さい場合、それらは暗黙的にintに昇格されます(これはバグの主な原因です)。この暗黙の昇格を行うにはコンパイラーが必要であり、標準に違反することなく回避する方法はありません。

整数の昇格とは、intよりも小さい整数型では言語での操作(加算、ビット単位、論理など)が発生しないことを意味します。したがって、char/short/intの操作は、前者の操作が後者に昇格されるため、一般に同等に高速です。

そして、整数プロモーションに加えて、「通常の算術変換」があります。つまり、Cは両方のオペランドを同じ型にし、異なる場合は一方を2つのうちの大きい方に変換しようとします。

ただし、CPUは8、16、32などのレベルでさまざまなロード/ストア操作を実行できます。 8および16ビットアーキテクチャでは、これは多くの場合、整数の昇格にもかかわらず8および16ビットタイプが高速であることを意味します。 32ビットCPUでは、実際にはすべてのデータを32ビットチャンクにきちんと整列させたいため、小さい型がslowerであることを意味する場合があります。 32ビットコンパイラは通常、速度を最適化し、指定されたよりも大きなスペースに小さな整数型を割り当てます。

一般に、小さい整数型の方が大きい整数型よりもスペースが少ないため、RAMサイズに最適化する場合は、これらを優先することをお勧めします。

7
Lundin

上記の最初の答えは素晴らしく、その小さなブロックを次の複製にコピーしました(これが最初に終わった場所です)。

「char」と「small int」は「int」より遅いですか?

さまざまな整数サイズでの割り当て、初期化、およびいくつかの算術演算をプロファイルする次のコードを提供したいと思います。

#include <iostream>

#include <windows.h>

using std::cout; using std::cin; using std::endl;

LARGE_INTEGER StartingTime, EndingTime, ElapsedMicroseconds;
LARGE_INTEGER Frequency;

void inline showElapsed(const char activity [])
{
    QueryPerformanceCounter(&EndingTime);
    ElapsedMicroseconds.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart;
    ElapsedMicroseconds.QuadPart *= 1000000;
    ElapsedMicroseconds.QuadPart /= Frequency.QuadPart;
    cout << activity << " took: " << ElapsedMicroseconds.QuadPart << "us" << endl;
}

int main()
{
    cout << "Hallo!" << endl << endl;

    QueryPerformanceFrequency(&Frequency);

    const int32_t count = 1100100;
    char activity[200];

    //-----------------------------------------------------------------------------------------//
    sprintf_s(activity, "Initialise & Set %d 8 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    int8_t *data8 = new int8_t[count];
    for (int i = 0; i < count; i++)
    {
        data8[i] = i;
    }
    showElapsed(activity);

    sprintf_s(activity, "Add 5 to %d 8 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    for (int i = 0; i < count; i++)
    {
        data8[i] = i + 5;
    }
    showElapsed(activity);
    cout << endl;
    //-----------------------------------------------------------------------------------------//

    //-----------------------------------------------------------------------------------------//
    sprintf_s(activity, "Initialise & Set %d 16 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    int16_t *data16 = new int16_t[count];
    for (int i = 0; i < count; i++)
    {
        data16[i] = i;
    }
    showElapsed(activity);

    sprintf_s(activity, "Add 5 to %d 16 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    for (int i = 0; i < count; i++)
    {
        data16[i] = i + 5;
    }
    showElapsed(activity);
    cout << endl;
    //-----------------------------------------------------------------------------------------//

    //-----------------------------------------------------------------------------------------//    
    sprintf_s(activity, "Initialise & Set %d 32 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    int32_t *data32 = new int32_t[count];
    for (int i = 0; i < count; i++)
    {
        data32[i] = i;
    }
    showElapsed(activity);

    sprintf_s(activity, "Add 5 to %d 32 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    for (int i = 0; i < count; i++)
    {
        data32[i] = i + 5;
    }
    showElapsed(activity);
    cout << endl;
    //-----------------------------------------------------------------------------------------//

    //-----------------------------------------------------------------------------------------//
    sprintf_s(activity, "Initialise & Set %d 64 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    int64_t *data64 = new int64_t[count];
    for (int i = 0; i < count; i++)
    {
        data64[i] = i;
    }
    showElapsed(activity);

    sprintf_s(activity, "Add 5 to %d 64 bit integers", count);
    QueryPerformanceCounter(&StartingTime);

    for (int i = 0; i < count; i++)
    {
        data64[i] = i + 5;
    }
    showElapsed(activity);
    cout << endl;
    //-----------------------------------------------------------------------------------------//

    getchar();
}


/*
My results on i7 4790k:

Initialise & Set 1100100 8 bit integers took: 444us
Add 5 to 1100100 8 bit integers took: 358us

Initialise & Set 1100100 16 bit integers took: 666us
Add 5 to 1100100 16 bit integers took: 359us

Initialise & Set 1100100 32 bit integers took: 870us
Add 5 to 1100100 32 bit integers took: 276us

Initialise & Set 1100100 64 bit integers took: 2201us
Add 5 to 1100100 64 bit integers took: 659us
*/

I7 4790k上のMSVCでの私の結果:

1100100の8ビット整数の初期化と設定:444us
5から1100100に8ビット整数を追加:358us

1100100 16ビット整数の初期化と設定:666us
5から1100100に16ビット整数を追加:359us

1100100 32ビット整数の初期化と設定:870us
5を1100100 32ビット整数に追加するのにかかった:276us

1100100 64ビット整数の初期化と設定:2201us
5を1100100に追加する64ビット整数がかかった:659us

3
Researcher

整数演算と浮動小数点演算の間にパフォーマンスの違いはありますか?

はい。ただし、これは非常に多くのプラットフォームとCPU固有です。プラットフォームが異なれば、異なる速度で異なる算術演算を実行できます。

そうは言っても、問題の回答はもう少し具体的でした。 pow()は、double値で機能する汎用ルーチンです。整数値を供給することで、非整数の指数を処理するために必要なすべての作業を実行しています。直接乗算を使用すると、多くの複雑さが回避され、速度が重要になります。これは実際には(それほど)さまざまなタイプの問題ではなく、指数を持つパウ関数を作成するために必要な大量の複雑なコードをバイパスするという問題です。

2
Reed Copsey

プロセッサとプラットフォームの構成に依存します。

浮動小数点コプロセッサーを備えたプラットフォームは、値をコプロセッサーとの間で転送する必要があるため、整数演算よりも遅くなる場合があります。

浮動小数点処理がプロセッサのコア内にある場合、実行時間は無視できる場合があります。

浮動小数点計算がソフトウェアによってエミュレートされる場合、積分演算は高速になります。

疑わしい場合は、プロファイルします。

最適化する前に、プログラミングが正しく機能するようにします。

1
Thomas Matthews

浮動小数点演算と整数演算には確かに違いがあります。 CPUの特定のハードウェアとマイクロ命令に応じて、異なるパフォーマンスや精度が得られます。正確な説明に適したGoogleの用語(どちらも正確にはわかりません):

FPU x87 MMX SSE

整数のサイズに関しては、プラットフォーム/アーキテクチャのWordサイズ(またはその2倍)を使用するのが最適です。x86ではint32_t、x86_64ではint64_tになります。 SOmeプロセッサには、これらの値のいくつかを一度に処理する組み込みの命令(SSE(浮動小数点)やMMXなど)があり、並列の加算または乗算を高速化します。

0
rubenvb

一般に、整数演算は浮動小数点演算よりも高速です。これは、整数演算がより単純な計算を伴うためです。ただし、ほとんどの操作では、1ダース未満のクロックについて説明しています。ミリ、マイクロ、ナノ、またはティックではありません。クロック。現代のコアで毎秒2〜3億回発生するもの。また、486には多くのコアが浮動小数点演算装置またはFPUのセットを持っているため、浮動小数点演算を効率的に、多くの場合CPUと並列に実行するように配線されています。

これらの結果、技術的には遅くなりますが、浮動小数点計算は依然として非常に高速であるため、差を計ろうとすると、実際に計算を実行するよりも、タイミングメカニズムおよびスレッドスケジューリングに固有のエラーが多くなります。可能な場合はintを使用しますが、不可能な場合は理解し、相対的な計算速度についてはあまり気にしません。

0
KeithS

いいえ、そうでもありません。もちろん、これはCPUとコンパイラに依存しますが、パフォーマンスの違いは通常、ごくわずかです。

0
Puppy