web-dev-qa-db-ja.com

32ビットシステムでint32_tの代わりにint64_tを使用すると、パフォーマンスにどのような影響がありますか?

現在、C++ライブラリは、時間値の保存にtime_tを使用しています。いくつかの場所で1秒未満の精度が必要になり始めているので、とにかく大きなデータ型が必要になります。また、いくつかの場所で2038年問題を回避することは有用かもしれません。したがって、すべての場所でtime_t値を置き換えるために、基になるint64_t値を持つ単一のTimeクラスに完全に切り替えることを考えています。

今、32ビットオペレーティングシステムまたは32ビットCPUでこのコードを実行するとき、このような変更のパフォーマンスへの影響について疑問に思っています。 IIUCコンパイラは、32ビットレジスタを使用して64ビット演算を実行するコードを生成します。しかし、これが遅すぎる場合、時間値を処理するために、より差別化された方法を使用する必要があり、ソフトウェアの保守がより難しくなる可能性があります。

興味のあること:

  • これらの操作のパフォーマンスに影響する要因は何ですか?おそらくコンパイラとコンパイラのバージョン。しかし、オペレーティングシステムまたはCPUのメーカー/モデルもこれに影響しますか?通常の32ビットシステムは、最新のCPUの64ビットレジスタを使用しますか?
  • 32ビットでエミュレートすると、どの操作が特に遅くなりますか?それとも、スローダウンがほとんどないのはどれですか?
  • 32ビットシステムでint64_t/uint64_tを使用するための既存のベンチマーク結果はありますか?
  • このパフォーマンスへの影響に関する経験はありますか?

Linux Core 2.6(RHEL5、RHEL6)上のg ++​​ 4.1および4.4、Intel Core 2システムに主に興味があります。しかし、他のシステム(Sparc Solaris + Solaris CC、Windows + MSVCなど)の状況について知っておくといいでしょう。

51
oliver

これらの操作のパフォーマンスに影響する要因は何ですか?おそらくコンパイラとコンパイラのバージョン。しかし、オペレーティングシステムまたはCPUのメーカー/モデルもこれに影響しますか?

主にプロセッサアーキテクチャ(およびモデル-このセクションでプロセッサアーキテクチャについて言及しているモデルをお読みください)。コンパイラーはある程度の影響を与える可能性がありますが、ほとんどのコンパイラーはこれに非常に優れているため、プロセッサーのアーキテクチャーはコンパイラーよりも大きな影響力を持ちます。

オペレーティングシステムはまったく影響を与えません(「OSを変更する場合は、コンパイラの動作を変更する別のタイプのコンパイラを使用する必要がある」以外の場合がありますが、おそらく小さな効果です)。

通常の32ビットシステムは、最新のCPUの64ビットレジスタを使用しますか?

これは不可能です。システムが32ビットモードの場合、32ビットシステムとして機能します。システムの実際の「真の32ビットシステム」である場合と同様に、レジスタの余分な32ビットは完全に見えません。 。

32ビットでエミュレートすると、どの操作が特に遅くなりますか?それとも、スローダウンがほとんどないのはどれですか?

足し算と引き算は、2つの操作を順番に行う必要があるため、さらに悪くなります。2番目の操作では、最初の操作を完了する必要があります。

入力パラメーターが実際に64ビットの場合、複製はさらに悪化します。たとえば、2 ^ 35 * 83は2 ^ 31 * 2 ^ 31よりも悪化します。これは、プロセッサが32 x 32ビットの乗算を64ビットの結果に非常にうまく生成できるためです(5〜10クロックサイクル)。しかし、64 x 64ビットの乗算にはかなりの余分なコードが必要なので、時間がかかります。

除算は乗算と同様の問題です。ただし、ここでは片側で64ビット入力を取得し、32ビット値で除算して32ビット値を取得してもかまいません。これがいつ機能するかを予測するのは難しいため、64ビットの除算はほとんど常に遅いです。

また、データには2倍のキャッシュスペースが必要であり、結果に影響する可能性があります。また、同様の結果として、一般的な割り当てとデータの受け渡しには、操作するデータが2倍あるため、最小で2倍の時間がかかります。

また、コンパイラはより多くのレジスタを使用する必要があります。

32ビットシステムでint64_t/uint64_tを使用するための既存のベンチマーク結果はありますか?

おそらく、しかし、私は何も知りません。また、たとえ存在する場合でも、操作の組み合わせは操作の速度にとって非常に重要であるため、それはあなたにとって多少意味があるだけです。

パフォーマンスがアプリケーションの重要な部分である場合は、コード(またはその代表的な部分)のベンチマークを行います。同じ状況で、コードがまったく異なる量のコードである場合、ベンチマークXの結果が5%、25%、または103%遅くなるかどうかは問題ではありません。

このパフォーマンスへの影響に関する経験はありますか?

64ビットアーキテクチャ用に64ビット整数を使用するコードを再コンパイルしましたが、パフォーマンスが大幅に向上しました。一部のコードでは25%も向上しました。

OSを同じOSの64ビットバージョンに変更すると、おそらく役立つでしょうか?

編集:

私はこれらの種類の違いが何であるかを見つけるのが好きなので、少しのコードを書き、いくつかの原始的なテンプレートを使用しました(まだそのビットを学びます-テンプレートは正確に私の最もホットなトピックではありません、私は言わなければなりません-私に与えてくださいbitfiddlingとポインター演算、そして私は(通常)それを正しくします...)

ここに私が書いたコードがあり、いくつかの一般的な機能を再現しようとしています:

#include <iostream>
#include <cstdint>
#include <ctime>

using namespace std;

static __inline__ uint64_t rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (uint64_t)lo)|( ((uint64_t)hi)<<32 );
}

template<typename T>
static T add_numbers(const T *v, const int size)
{
    T sum = 0;
    for(int i = 0; i < size; i++)
    sum += v[i];
    return sum;
}


template<typename T, const int size>
static T add_matrix(const T v[size][size])
{
    T sum[size] = {};
    for(int i = 0; i < size; i++)
    {
    for(int j = 0; j < size; j++)
        sum[i] += v[i][j];
    }
    T tsum=0;
    for(int i = 0; i < size; i++)
    tsum += sum[i];
    return tsum;
}



template<typename T>
static T add_mul_numbers(const T *v, const T mul, const int size)
{
    T sum = 0;
    for(int i = 0; i < size; i++)
    sum += v[i] * mul;
    return sum;
}

template<typename T>
static T add_div_numbers(const T *v, const T mul, const int size)
{
    T sum = 0;
    for(int i = 0; i < size; i++)
    sum += v[i] / mul;
    return sum;
}


template<typename T> 
void fill_array(T *v, const int size)
{
    for(int i = 0; i < size; i++)
    v[i] = i;
}

template<typename T, const int size> 
void fill_array(T v[size][size])
{
    for(int i = 0; i < size; i++)
    for(int j = 0; j < size; j++)
        v[i][j] = i + size * j;
}




uint32_t bench_add_numbers(const uint32_t v[], const int size)
{
    uint32_t res = add_numbers(v, size);
    return res;
}

uint64_t bench_add_numbers(const uint64_t v[], const int size)
{
    uint64_t res = add_numbers(v, size);
    return res;
}

uint32_t bench_add_mul_numbers(const uint32_t v[], const int size)
{
    const uint32_t c = 7;
    uint32_t res = add_mul_numbers(v, c, size);
    return res;
}

uint64_t bench_add_mul_numbers(const uint64_t v[], const int size)
{
    const uint64_t c = 7;
    uint64_t res = add_mul_numbers(v, c, size);
    return res;
}

uint32_t bench_add_div_numbers(const uint32_t v[], const int size)
{
    const uint32_t c = 7;
    uint32_t res = add_div_numbers(v, c, size);
    return res;
}

uint64_t bench_add_div_numbers(const uint64_t v[], const int size)
{
    const uint64_t c = 7;
    uint64_t res = add_div_numbers(v, c, size);
    return res;
}


template<const int size>
uint32_t bench_matrix(const uint32_t v[size][size])
{
    uint32_t res = add_matrix(v);
    return res;
}
template<const int size>
uint64_t bench_matrix(const uint64_t v[size][size])
{
    uint64_t res = add_matrix(v);
    return res;
}


template<typename T>
void runbench(T (*func)(const T *v, const int size), const char *name, T *v, const int size)
{
    fill_array(v, size);

    uint64_t long t = rdtsc();
    T res = func(v, size);
    t = rdtsc() - t;
    cout << "result = " << res << endl;
    cout << name << " time in clocks " << dec << t  << endl;
}

template<typename T, const int size>
void runbench2(T (*func)(const T v[size][size]), const char *name, T v[size][size])
{
    fill_array(v);

    uint64_t long t = rdtsc();
    T res = func(v);
    t = rdtsc() - t;
    cout << "result = " << res << endl;
    cout << name << " time in clocks " << dec << t  << endl;
}


int main()
{
    // spin up CPU to full speed...
    time_t t = time(NULL);
    while(t == time(NULL)) ;

    const int vsize=10000;

    uint32_t v32[vsize];
    uint64_t v64[vsize];

    uint32_t m32[100][100];
    uint64_t m64[100][100];


    runbench(bench_add_numbers, "Add 32", v32, vsize);
    runbench(bench_add_numbers, "Add 64", v64, vsize);

    runbench(bench_add_mul_numbers, "Add Mul 32", v32, vsize);
    runbench(bench_add_mul_numbers, "Add Mul 64", v64, vsize);

    runbench(bench_add_div_numbers, "Add Div 32", v32, vsize);
    runbench(bench_add_div_numbers, "Add Div 64", v64, vsize);

    runbench2(bench_matrix, "Matrix 32", m32);
    runbench2(bench_matrix, "Matrix 64", m64);
}

コンパイル済み:

g++ -Wall -m32 -O3 -o 32vs64 32vs64.cpp -std=c++0x

結果は次のとおりです。注:以下の2016年の結果を参照-これらの結果は、SSE 64ビットモードの命令の違いにより、わずかに楽観的ですが、 SSE 32ビットモードでの使用。

result = 49995000
Add 32 time in clocks 20784
result = 49995000
Add 64 time in clocks 30358
result = 349965000
Add Mul 32 time in clocks 30182
result = 349965000
Add Mul 64 time in clocks 79081
result = 7137858
Add Div 32 time in clocks 60167
result = 7137858
Add Div 64 time in clocks 457116
result = 49995000
Matrix 32 time in clocks 22831
result = 49995000
Matrix 64 time in clocks 23823

ご覧のとおり、加算と乗算はそれほど悪くはありません。分裂は本当に悪くなります。興味深いことに、マトリックスの追加はそれほど大きな違いではありません。

そして、64ビットで高速ですか?一部の人が尋ねるのを聞きます:同じコンパイラオプションを使用して、-m32-yuppの代わりに-m64だけで、はるかに高速です:

result = 49995000
Add 32 time in clocks 8366
result = 49995000
Add 64 time in clocks 16188
result = 349965000
Add Mul 32 time in clocks 15943
result = 349965000
Add Mul 64 time in clocks 35828
result = 7137858
Add Div 32 time in clocks 50176
result = 7137858
Add Div 64 time in clocks 50472
result = 49995000
Matrix 32 time in clocks 12294
result = 49995000
Matrix 64 time in clocks 14733

編集、2016年の更新:SSEの有無にかかわらず、コンパイラの32ビットモードと64ビットモードの4つのバリアント。

最近は、通常、clang ++を通常のコンパイラとして使用しています。 g ++でコンパイルしようとしました(ただし、マシンを更新したため、上記とは異なるバージョンになります-また、CPUも異なります)。 g ++がno-sseバージョンを64ビットでコンパイルできなかったため、その点がわかりませんでした。 (とにかくg ++は同様の結果を与える)

短い表として:

Test name      | no-sse 32 | no-sse 64 | sse 32 | sse 64 |
----------------------------------------------------------
Add uint32_t   |   20837   |   10221   |   3701 |   3017 |
----------------------------------------------------------
Add uint64_t   |   18633   |   11270   |   9328 |   9180 |
----------------------------------------------------------
Add Mul 32     |   26785   |   18342   |  11510 |  11562 |
----------------------------------------------------------
Add Mul 64     |   44701   |   17693   |  29213 |  16159 |
----------------------------------------------------------
Add Div 32     |   44570   |   47695   |  17713 |  17523 |
----------------------------------------------------------
Add Div 64     |  405258   |   52875   | 405150 |  47043 |
----------------------------------------------------------
Matrix 32      |   41470   |   15811   |  21542 |   8622 |
----------------------------------------------------------
Matrix 64      |   22184   |   15168   |  13757 |  12448 |

コンパイルオプションを使用した完全な結果。

$ clang++ -m32 -mno-sse 32vs64.cpp --std=c++11 -O2
$ ./a.out
result = 49995000
Add 32 time in clocks 20837
result = 49995000
Add 64 time in clocks 18633
result = 349965000
Add Mul 32 time in clocks 26785
result = 349965000
Add Mul 64 time in clocks 44701
result = 7137858
Add Div 32 time in clocks 44570
result = 7137858
Add Div 64 time in clocks 405258
result = 49995000
Matrix 32 time in clocks 41470
result = 49995000
Matrix 64 time in clocks 22184

$ clang++ -m32 -msse 32vs64.cpp --std=c++11 -O2
$ ./a.out
result = 49995000
Add 32 time in clocks 3701
result = 49995000
Add 64 time in clocks 9328
result = 349965000
Add Mul 32 time in clocks 11510
result = 349965000
Add Mul 64 time in clocks 29213
result = 7137858
Add Div 32 time in clocks 17713
result = 7137858
Add Div 64 time in clocks 405150
result = 49995000
Matrix 32 time in clocks 21542
result = 49995000
Matrix 64 time in clocks 13757


$ clang++ -m64 -msse 32vs64.cpp --std=c++11 -O2
$ ./a.out
result = 49995000
Add 32 time in clocks 3017
result = 49995000
Add 64 time in clocks 9180
result = 349965000
Add Mul 32 time in clocks 11562
result = 349965000
Add Mul 64 time in clocks 16159
result = 7137858
Add Div 32 time in clocks 17523
result = 7137858
Add Div 64 time in clocks 47043
result = 49995000
Matrix 32 time in clocks 8622
result = 49995000
Matrix 64 time in clocks 12448


$ clang++ -m64 -mno-sse 32vs64.cpp --std=c++11 -O2
$ ./a.out
result = 49995000
Add 32 time in clocks 10221
result = 49995000
Add 64 time in clocks 11270
result = 349965000
Add Mul 32 time in clocks 18342
result = 349965000
Add Mul 64 time in clocks 17693
result = 7137858
Add Div 32 time in clocks 47695
result = 7137858
Add Div 64 time in clocks 52875
result = 49995000
Matrix 32 time in clocks 15811
result = 49995000
Matrix 64 time in clocks 15168
47
Mats Petersson

あなたの質問はその環境ではかなり奇妙に聞こえます。 32ビットを使用するtime_tを使用します。追加情報が必要です。これはより多くのビットを意味します。したがって、int32よりも大きなものを使用する必要があります。パフォーマンスとは関係ありませんか?選択肢は、ちょうど40ビットを使用するか、int64を使用するかです。何百万ものインスタンスを保存する必要がある場合を除き、後者を選択するのが賢明です。

他の人が指摘したように、真のパフォーマンスを知る唯一の方法は、プロファイラーで測定することです(いくつかの大まかなサンプルでは、​​単純なクロックで行います)。そのまま測定してください。 time_tの使用をtypedefにグローバルに置き換えて64ビットに再定義し、実際のtime_tが予期されていたいくつかのインスタンスにパッチを当てることは難しくありません。

現在のtime_tインスタンスが少なくとも数メガのメモリを占有しない限り、私の賭けは「測定できない差」になります。現在のIntelライクなプラットフォームでは、コアはほとんどの時間を外部メモリがキャッシュに入るのを待っています。単一のキャッシュミスは、数百サイクルにわたってストールします。命令の1ティックの差を計算できない理由。実際のパフォーマンスは、現在の構造がキャッシュラインにちょうど収まり、大きなラインには2つ必要になるなどの理由で低下する可能性があります。また、現在のパフォーマンスを測定したことがない場合は、構造体の一部のメンバーのアライメントまたは交換順序を追加するだけで、一部の機能の極端な高速化を実現できることがわかります。または、デフォルトのレイアウトを使用する代わりに、pack(1)構造を...

2
Balog Pal

加算/減算は基本的にそれぞれ2サイクルになり、乗算と除算は実際のCPUに依存します。一般的なパフォーマンスへの影響はかなり低くなります。

Intel Core 2はEM64Tをサポートしていることに注意してください。

0
dom0