9

私の CPU の仕様によると、メモリへの帯域幅は 5.336GB/s になるはずです。これをテストするために、大きな配列で memset (または memcpy) を実行し、タイミングを報告する簡単なプログラムを作成しました。memset で 3.8GB/s、memcpy で 1.9GB/s を示しています。 http://en.wikipedia.org/wiki/Intel_Core_(microarchitecture)によると、私の Q9400 は 5.336MB/s になるはずです。どうしたの?

memset または memcpy を割り当てループに置き換えてみました。私は、メモリの配置について学ぶためにグーグルで検索しました。さまざまなコンパイラ フラグを試しました。私はこれに恥ずかしい時間を費やしました。ご協力いただきありがとうございます。

libc-dev バージョン 2.15-0ubuntu10.5 およびカーネル 3.8.0-37-generic で Ubuntu 12.04 を使用しています

コード:

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

#define numBytes ((long)(1024*1024*1024))
#define numTransfers ((long)(8))

int main(int argc,char**argv){
    if(argc!=3){
        printf("Usage: %s BLOCK_SIZE_IN_BYTES NUMBER_OF_BLOCKS_TO_TRANSFER\n",argv[0]);
        return -1;
    }
    char*__restrict__ source=(char*)malloc(numBytes);
    char*__restrict__ dest=(char*)malloc(numBytes);
    struct timespec start,end;
    long totalTimeMs;
    int i;

    clock_gettime(CLOCK_MONOTONIC_RAW,&start);
    for(i=0;i<numTransfers;++i)
        memset(source,0,numBytes);
    clock_gettime(CLOCK_MONOTONIC_RAW,&end);
    totalTimeMs=(end.tv_nsec-start.tv_nsec)*.000001+1000*(end.tv_sec-start.tv_sec);
    printf("memset %ld bytes %ld times (%.2fGB total) in %ldms (%.3fGB/s). ",numBytes,numTransfers,numBytes/1024.0/1024/1024*numTransfers,totalTimeMs,numBytes/1024.0/1024/1024*1000*numTransfers/totalTimeMs);

    clock_gettime(CLOCK_MONOTONIC_RAW,&start);
    for(i=0;i<numTransfers;++i)
        memcpy( dest, source, numBytes);
    clock_gettime(CLOCK_MONOTONIC_RAW,&end);
    totalTimeMs=(end.tv_nsec-start.tv_nsec)*.000001+1000*(end.tv_sec-start.tv_sec);
    printf("memcpy %ld bytes %ld times (%.2fGB total) in %ldms (%.3fGB/s).\n",numBytes,numTransfers,numBytes/1024.0/1024/1024*numTransfers,totalTimeMs,numBytes/1024.0/1024/1024*1000*numTransfers/totalTimeMs);

    free(source);
    free(dest);

    return EXIT_SUCCESS;
}

コンパイル コマンド:

gcc -O3 -DNDEBUG -o memcpyStackOverflowNoParameters.c.o -c memcpyStackOverflowNoParameters.c
gcc -O3 -DNDEBUG memcpyStackOverflowNoParameters.c.o -o memcpy -rdynamic -lrt 

サンプル出力:

memset 1073741824 bytes 8 times (8.00GB total) in 2214ms (3.880GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4466ms (1.923GB/s).
memset 1073741824 bytes 8 times (8.00GB total) in 2218ms (3.873GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4557ms (1.885GB/s).
memset 1073741824 bytes 8 times (8.00GB total) in 2222ms (3.866GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4433ms (1.938GB/s).
memset 1073741824 bytes 8 times (8.00GB total) in 2216ms (3.876GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4521ms (1.900GB/s).
memset 1073741824 bytes 8 times (8.00GB total) in 2217ms (3.875GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4520ms (1.900GB/s).
memset 1073741824 bytes 8 times (8.00GB total) in 2218ms (3.873GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4430ms (1.939GB/s).
memset 1073741824 bytes 8 times (8.00GB total) in 2226ms (3.859GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4444ms (1.933GB/s).
memset 1073741824 bytes 8 times (8.00GB total) in 2225ms (3.861GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4485ms (1.915GB/s).
memset 1073741824 bytes 8 times (8.00GB total) in 2620ms (3.279GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4855ms (1.769GB/s).
memset 1073741824 bytes 8 times (8.00GB total) in 2535ms (3.389GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4870ms (1.764GB/s).
memset 1073741824 bytes 8 times (8.00GB total) in 2423ms (3.545GB/s). memcpy 1073741824 bytes 8 times (8.00GB total) in 4905ms (1.751GB/s).

lshwによると私のハードウェア:

  product: OptiPlex 960 ()
  vendor: Winbond Electronics
  width: 64 bits
*-core
     description: Motherboard
     product: 0Y958C
     vendor: Winbond Electronics
   *-firmware
        description: BIOS
        capabilities: pci pnp apm upgrade shadowing escd cdboot bootselect edd int13floppytoshiba int13floppy720 int5printscreen int9keyboard int14serial int17printer acpi usb biosbootspecification netboot
   *-cpu
        product: Intel(R) Core(TM)2 Quad CPU    Q9400  @ 2.66GHz
        physical id: 400
        size: 2666MHz
        width: 64 bits
        clock: 1333MHz
        capabilities: x86-64 fpu fpu_exception wp vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx constant_tsc arch_perfmon pebs bts rep_good nopl aperfmperf pni dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cx16 xtpr pdcm sse4_1 xsave lahf_lm dtherm tpr_shadow vnmi flexpriority
        configuration: cores=4 enabledcores=4 threads=4
      *-cache:0
           description: L1 cache
           physical id: 700
           size: 256KiB
           capacity: 256KiB
           capabilities: internal write-back unified
      *-cache:1
           description: L2 cache
           physical id: 701
           size: 6MiB
           capacity: 6MiB
           capabilities: internal varies unified
   *-memory
        description: System Memory
        physical id: 1000
        slot: System board or motherboard
        size: 4GiB
      *-bank:0
           description: DIMM DDR2 Synchronous 667 MHz (1.5 ns)
           product: CT51264AA667.M16FC
           vendor: 7F7F7F7F7F9B0000
           slot: DIMM_1
           size: 4GiB
           width: 64 bits
           clock: 667MHz (1.5ns)
      *-bank:1
           description: DIMM DDR2 Synchronous 667 MHz (1.5 ns) [empty]
      *-bank:2
           description: DIMM DDR2 Synchronous 667 MHz (1.5 ns) [empty]
      *-bank:3
           description: DIMM DDR2 Synchronous 667 MHz (1.5 ns) [empty]
4

2 に答える 2

7

メモリアドレスは「仮想化」され、プログラムが使用するアドレスは実際のアドレスに変換されます。この変換により、プログラムが連続したメモリと見なすものを、その時点で便利な部分から割り当てることができます。すべての汎用 CPU がこれを行います。変換には、メモリ アクセスを必要とするテーブル ルックアップが必要です。CPU にはキャッシュがありますが、仮想アドレスが長く続くと、そのキャッシュである「TLB」(「変換ルックアサイド バッファー」) が簡単に使い果たされる可能性があります。したがって、CPU は 4KB (ユーザーが何をしているかを把握している Linux システムでは 2MB) ごとに、メモリ トラフィックを実際に送信する場所を探して失速します。それらの屋台にはかなりの時間がかかる場合があります。ベンチマークの 2 つのコピーを実行してみると、TLB ミスが一致しないことは合理的に思えます。

(編集: ええと、あなた#defineの s を

size_t numBytes=atoi(argv[1]);
size_t numTransfers=atoi(argv[2]);

本体に…)

編集:ちなみに、私のボックスでこのテストから見た(そしてコメントで報告された)帯域幅は、私のCPUの定格容量をはるかに下回っていたので、自分のシステムを調査する必要がありました. 私のボックスビルダーは、それらのスロットに本当にがらくたのメモリを入れていました. 私はずっと前に、それらを既知の優れたブランドに置き換えました。報告されたスループットは 2 倍以上になり、私のマシンのパフォーマンスは目に見えて改善されました。

于 2014-04-29T20:30:08.183 に答える
3

最後に、memcpy と memset が GCC で最適化されていないことを確認しました。これは2012 年にも当てはまりました。Agner Fog のOptimizing software in C++セクション 2.6 2.6「関数ライブラリの選択」および表 2.1 を参照してください。彼は、いくつかの異なるコンパイラと OS を比較しています。

GCC には、memcpy を実行するための関数が組み込まれています。どうやら、Glib の memcpy よりもさらに悪いようです。私が理解している限り、GCC 開発者と Glib 開発者は独立して作業しています。Glib からライブラリを取得するには、-fno-builtin. ただし、Glib の方が優れている (または少なくとも優れていた) とはいえ、まだ最適ではありません。最良の結果を得るには、Agner Fog のasmlibを使用します。彼は memcpy と memset および他の多くのアセンブリ内の一般的な関数を最適化し、他の最適化の中で SSE と AVX を利用しました。

于 2014-04-30T08:10:09.517 に答える