75

これは少しばかげた質問のように見えるかもしれませんが、他のトピックでアレクサンドルCの回答を見ると、組み込みタイプとのパフォーマンスの違いがあるかどうかを知りたいと思います。

charvs shortvsvs.vs int.。float _ double_

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

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

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

4

9 に答える 9

126

浮動小数点と整数:

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

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

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

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

異なるサイズの整数型:

通常、CPUは、ネイティブワードサイズの整数での動作が最も高速です(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(コンピューターで使用されるものなど)では高速になります。これは、ユーザーがそれらの費用を喜んで支払うためです。トースターに追加の$3を支払って、高速FPを乗算することはおそらくないでしょう。ただし、安価なCPUはこれらの命令を無視します。

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

需要が少なく、複雑さが少ない操作は、誰かがわざわざ考えれば高速になり、それ以外の場合は存在しません。

参考文献:

  • Agner Fogは、低レベルのパフォーマンスの詳細について多くの議論がある素晴らしいWebサイトを維持しています(そしてそれをバックアップするための非常に科学的なデータ収集方法論を持っています)。
  • インテル®64およびIA-32アーキテクチャー最適化リファレンス・マニュアル(PDFダウンロード・リンクはページの途中にあります)は、特定のアーキテクチャー・ファミリーに焦点を当てていますが、これらの問題の多くもカバーしています。
于 2011-02-21T18:17:49.497 に答える
15

絶対。

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

ただし、整数型と浮動小数点型の処理は大きく異なるため、ほとんどの場合、次のようになります。

  • 単純な操作の場合、整数型は高速です。たとえば、整数の加算には1サイクルのレイテンシしかなく、整数の乗算は通常2〜4サイクル、IIRCです。
  • 浮動小数点タイプは、パフォーマンスがはるかに遅くなりました。ただし、今日のCPUでは、スループットが優れており、各浮動小数点ユニットは通常、サイクルごとに操作をリタイアできるため、整数操作の場合と同じ(または同様の)スループットになります。ただし、待ち時間は一般的に悪化します。浮動小数点の加算には、多くの場合、約4サイクルのレイテンシがあります(intの場合は1)。
  • 一部の複雑な操作では、状況が異なるか、逆になります。たとえば、FPでの除算は、整数よりもレイテンシが短い場合があります。これは、どちらの場合も操作の実装が複雑であるためですが、FP値ではより一般的に役立つため、その場合の最適化により多くの労力(およびトランジスタ)が費やされる可能性があります。

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

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

整数タイプが異なれば、答えはCPUアーキテクチャによって大きく異なります。x86アーキテクチャは、その長い複雑な歴史のために、8、16、32(および現在は64)ビット演算の両方をネイティブにサポートする必要があり、一般に、すべて同じように高速です(基本的に同じハードウェアを使用し、ゼロだけです)必要に応じて上位ビットを削除します)。

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

しかし、x86では、答えはほとんど問題ではないということです。歴史的な理由から、CPUはすべてのデータ型をかなり堅牢にサポートする必要があります。したがって、気付く可能性のある唯一の違いは、浮動小数点演算のレイテンシーが大きいことです(ただし、スループットは同様であるため、少なくともコードを正しく記述していれば、それ自体は遅くなりません)。

于 2011-02-21T18:29:50.240 に答える
10

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

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

また、整数拡張に加えて、「通常の算術変換」があります。これは、Cが両方のオペランドを同じタイプにし、異なる場合は一方を2つのうち大きい方に変換することを意味します。

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

もちろん、一般的に小さい整数タイプは大きい整数タイプよりもスペースを取りません。したがって、RAMサイズを最適化する場合は、それらを優先します。

于 2011-02-21T21:10:08.577 に答える
8

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

「char」と「smallint」は「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での私の結果:

初期化と設定11001008ビット整数がかかりました:444us
5を1100100に追加します8ビット整数がかかりました:358us

初期化と設定110010016ビット整数がかかりました:666us
5を1100100に追加します16ビット整数がかかりました:359us

初期化と設定110010032ビット整数がかかりました:870us
5を1100100に追加します32ビット整数がかかりました:276us

初期化と設定110010064ビット整数の取得:2201us
5を1100100に追加64ビット整数の取得:659us

于 2016-05-03T01:02:21.040 に答える
2

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

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

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

于 2011-02-21T18:05:15.337 に答える
1

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

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

于 2011-02-21T18:16:14.587 に答える
1

プロセッサとプラットフォームの構成によって異なります。

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

浮動小数点処理がプロセッサのコア内にある場合、実行時間はごくわずかである可能性があります。

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

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

最適化する前に、プログラミングを正しく動作させ、堅牢にします。

于 2011-02-21T18:19:48.583 に答える
0

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

于 2011-02-21T18:09:51.280 に答える
0

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

FPU x87 MMX SSE

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

于 2011-02-21T18:12:25.390 に答える