最近、いくつかの新しいサーバーを購入しましたが、memcpyのパフォーマンスが低下しています。 memcpyのパフォーマンスは、ラップトップに比べてサーバー上で3倍遅くなります。
サーバーの仕様
編集:私はまた、わずかに高いスペックを持つ別のサーバーでテストしており、上記のサーバーと同じ結果が見られます
サーバー2仕様
ラップトップ仕様
オペレーティングシステム
$ cat /etc/redhat-release
Scientific Linux release 6.5 (Carbon)
$ uname -a
Linux r113 2.6.32-431.1.2.el6.x86_64 #1 SMP Thu Dec 12 13:59:19 CST 2013 x86_64 x86_64 x86_64 GNU/Linux
コンパイラー(すべてのシステムで)
$ gcc --version
gcc (GCC) 4.6.1
また、@ stefanからの提案に基づいてgcc 4.8.2でテストしました。コンパイラ間にパフォーマンスの違いはありませんでした。
テストコード以下のテストコードは、実稼働コードで見られる問題を再現するための定型テストです。このベンチマークは非常に単純ですが、問題を悪用して特定することができました。このコードは、2つの1GBバッファーとそれらの間にmemcpyを作成し、memcpy呼び出しのタイミングを計ります。次を使用して、コマンドラインで代替バッファサイズを指定できます。/big_memcpy_test [SIZE_BYTES]
#include <chrono>
#include <cstring>
#include <iostream>
#include <cstdint>
class Timer
{
public:
Timer()
: mStart(),
mStop()
{
update();
}
void update()
{
mStart = std::chrono::high_resolution_clock::now();
mStop = mStart;
}
double elapsedMs()
{
mStop = std::chrono::high_resolution_clock::now();
std::chrono::milliseconds elapsed_ms =
std::chrono::duration_cast<std::chrono::milliseconds>(mStop - mStart);
return elapsed_ms.count();
}
private:
std::chrono::high_resolution_clock::time_point mStart;
std::chrono::high_resolution_clock::time_point mStop;
};
std::string formatBytes(std::uint64_t bytes)
{
static const int num_suffix = 5;
static const char* suffix[num_suffix] = { "B", "KB", "MB", "GB", "TB" };
double dbl_s_byte = bytes;
int i = 0;
for (; (int)(bytes / 1024.) > 0 && i < num_suffix;
++i, bytes /= 1024.)
{
dbl_s_byte = bytes / 1024.0;
}
const int buf_len = 64;
char buf[buf_len];
// use snprintf so there is no buffer overrun
int res = snprintf(buf, buf_len,"%0.2f%s", dbl_s_byte, suffix[i]);
// snprintf returns number of characters that would have been written if n had
// been sufficiently large, not counting the terminating null character.
// if an encoding error occurs, a negative number is returned.
if (res >= 0)
{
return std::string(buf);
}
return std::string();
}
void doMemmove(void* pDest, const void* pSource, std::size_t sizeBytes)
{
memmove(pDest, pSource, sizeBytes);
}
int main(int argc, char* argv[])
{
std::uint64_t SIZE_BYTES = 1073741824; // 1GB
if (argc > 1)
{
SIZE_BYTES = std::stoull(argv[1]);
std::cout << "Using buffer size from command line: " << formatBytes(SIZE_BYTES)
<< std::endl;
}
else
{
std::cout << "To specify a custom buffer size: big_memcpy_test [SIZE_BYTES] \n"
<< "Using built in buffer size: " << formatBytes(SIZE_BYTES)
<< std::endl;
}
// big array to use for testing
char* p_big_array = NULL;
/////////////
// malloc
{
Timer timer;
p_big_array = (char*)malloc(SIZE_BYTES * sizeof(char));
if (p_big_array == NULL)
{
std::cerr << "ERROR: malloc of " << SIZE_BYTES << " returned NULL!"
<< std::endl;
return 1;
}
std::cout << "malloc for " << formatBytes(SIZE_BYTES) << " took "
<< timer.elapsedMs() << "ms"
<< std::endl;
}
/////////////
// memset
{
Timer timer;
// set all data in p_big_array to 0
memset(p_big_array, 0xF, SIZE_BYTES * sizeof(char));
double elapsed_ms = timer.elapsedMs();
std::cout << "memset for " << formatBytes(SIZE_BYTES) << " took "
<< elapsed_ms << "ms "
<< "(" << formatBytes(SIZE_BYTES / (elapsed_ms / 1.0e3)) << " bytes/sec)"
<< std::endl;
}
/////////////
// memcpy
{
char* p_dest_array = (char*)malloc(SIZE_BYTES);
if (p_dest_array == NULL)
{
std::cerr << "ERROR: malloc of " << SIZE_BYTES << " for memcpy test"
<< " returned NULL!"
<< std::endl;
return 1;
}
memset(p_dest_array, 0xF, SIZE_BYTES * sizeof(char));
// time only the memcpy FROM p_big_array TO p_dest_array
Timer timer;
memcpy(p_dest_array, p_big_array, SIZE_BYTES * sizeof(char));
double elapsed_ms = timer.elapsedMs();
std::cout << "memcpy for " << formatBytes(SIZE_BYTES) << " took "
<< elapsed_ms << "ms "
<< "(" << formatBytes(SIZE_BYTES / (elapsed_ms / 1.0e3)) << " bytes/sec)"
<< std::endl;
// cleanup p_dest_array
free(p_dest_array);
p_dest_array = NULL;
}
/////////////
// memmove
{
char* p_dest_array = (char*)malloc(SIZE_BYTES);
if (p_dest_array == NULL)
{
std::cerr << "ERROR: malloc of " << SIZE_BYTES << " for memmove test"
<< " returned NULL!"
<< std::endl;
return 1;
}
memset(p_dest_array, 0xF, SIZE_BYTES * sizeof(char));
// time only the memmove FROM p_big_array TO p_dest_array
Timer timer;
// memmove(p_dest_array, p_big_array, SIZE_BYTES * sizeof(char));
doMemmove(p_dest_array, p_big_array, SIZE_BYTES * sizeof(char));
double elapsed_ms = timer.elapsedMs();
std::cout << "memmove for " << formatBytes(SIZE_BYTES) << " took "
<< elapsed_ms << "ms "
<< "(" << formatBytes(SIZE_BYTES / (elapsed_ms / 1.0e3)) << " bytes/sec)"
<< std::endl;
// cleanup p_dest_array
free(p_dest_array);
p_dest_array = NULL;
}
// cleanup
free(p_big_array);
p_big_array = NULL;
return 0;
}
ビルドするCMakeファイル
project(big_memcpy_test)
cmake_minimum_required(VERSION 2.4.0)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
# create verbose makefiles that show each command line as it is issued
set( CMAKE_VERBOSE_MAKEFILE ON CACHE BOOL "Verbose" FORCE )
# release mode
set( CMAKE_BUILD_TYPE Release )
# grab in CXXFLAGS environment variable and append C++11 and -Wall options
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall -march=native -mtune=native" )
message( INFO "CMAKE_CXX_FLAGS = ${CMAKE_CXX_FLAGS}" )
# sources to build
set(big_memcpy_test_SRCS
main.cpp
)
# create an executable file named "big_memcpy_test" from
# the source files in the variable "big_memcpy_test_SRCS".
add_executable(big_memcpy_test ${big_memcpy_test_SRCS})
テスト結果
Buffer Size: 1GB | malloc (ms) | memset (ms) | memcpy (ms) | NUMA nodes (numactl --hardware)
---------------------------------------------------------------------------------------------
Laptop 1 | 0 | 127 | 113 | 1
Laptop 2 | 0 | 180 | 120 | 1
Server 1 | 0 | 306 | 301 | 2
Server 2 | 0 | 352 | 325 | 2
ご覧のとおり、サーバー上のmemcpysおよびmemsetsは、ラップトップ上のmemcpysおよびmemsetsよりもはるかに低速です。
さまざまなバッファサイズ
100MBから5GBまでのバッファを試してみましたが、すべて同様の結果が得られました(ラップトップよりもサーバーが遅い)
NUMAアフィニティ
NUMAでパフォーマンスの問題がある人について読んだので、numactlを使用してCPUとメモリのアフィニティを設定しようとしましたが、結果は同じままでした。
サーバーNUMAハードウェア
$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23
node 0 size: 65501 MB
node 0 free: 62608 MB
node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31
node 1 size: 65536 MB
node 1 free: 63837 MB
node distances:
node 0 1
0: 10 21
1: 21 10
ラップトップNUMAハードウェア
$ numactl --hardware
available: 1 nodes (0)
node 0 cpus: 0 1 2 3 4 5 6 7
node 0 size: 16018 MB
node 0 free: 6622 MB
node distances:
node 0
0: 10
NUMAアフィニティの設定
$ numactl --cpunodebind=0 --membind=0 ./big_memcpy_test
これを解決する助けは大歓迎です。
編集:GCCオプション
コメントに基づいて、さまざまなGCCオプションでコンパイルしようとしました:
-marchおよび-mtuneをネイティブに設定してコンパイルする
g++ -std=c++0x -Wall -march=native -mtune=native -O3 -DNDEBUG -o big_memcpy_test main.cpp
結果:まったく同じパフォーマンス(改善なし)
-O3の代わりに-O2でコンパイルする
g++ -std=c++0x -Wall -march=native -mtune=native -O2 -DNDEBUG -o big_memcpy_test main.cpp
結果:まったく同じパフォーマンス(改善なし)
編集:NULLページ(@SteveCox)を避けるために、0ではなく0xFを書き込むようにmemsetを変更しました(@ -SteveCox)
0以外の値(この場合は0xFを使用)でmemsettingを行っても改善はありません。
編集:キャッシュベンチの結果
私のテストプログラムが単純すぎることを排除するために、実際のベンチマークプログラムLLCacheBenchをダウンロードしました( http://icl.cs.utk.edu/projects/llcbench/cachebench.html )
アーキテクチャの問題を回避するために、各マシンに個別にベンチマークを作成しました。以下は私の結果です。
非常に大きな違いは、大きなバッファサイズでのパフォーマンスであることに注意してください。テストされた最後のサイズ(16777216)は、ラップトップで18849.29 MB /秒、サーバーで6710.40で実行されました。これはパフォーマンスの約3倍の違いです。また、サーバーのパフォーマンスの低下は、ラップトップの場合よりもはるかに急峻であることに気付くことができます。
編集:memmove()はサーバー上のmemcpy()より2倍高速です
いくつかの実験に基づいて、テストケースでmemcpy()の代わりにmemmove()を使用してみましたが、サーバーで2倍の改善が見られました。ラップトップのMemmove()はmemcpy()よりも低速ですが、奇妙なことに、サーバーのmemmove()と同じ速度で十分に実行されます。これは疑問を招きます、なぜmemcpyはとても遅いのですか?
Memcpyとともにmemmoveをテストするようにコードを更新しました。 memmove()を関数内にラップする必要がありました。インラインGCCで最適化して、memcpy()とまったく同じように実行した場合(場所が重複していないことを知っているため、gccはmemcpyに最適化したと仮定します)。
更新された結果
Buffer Size: 1GB | malloc (ms) | memset (ms) | memcpy (ms) | memmove() | NUMA nodes (numactl --hardware)
---------------------------------------------------------------------------------------------------------
Laptop 1 | 0 | 127 | 113 | 161 | 1
Laptop 2 | 0 | 180 | 120 | 160 | 1
Server 1 | 0 | 306 | 301 | 159 | 2
Server 2 | 0 | 352 | 325 | 159 | 2
編集:Naive Memcpy
@Salgarからの提案に基づいて、私は自分の素朴なmemcpy関数を実装してテストしました。
素朴なMemcpyソース
void naiveMemcpy(void* pDest, const void* pSource, std::size_t sizeBytes)
{
char* p_dest = (char*)pDest;
const char* p_source = (const char*)pSource;
for (std::size_t i = 0; i < sizeBytes; ++i)
{
*p_dest++ = *p_source++;
}
}
Memcpy()と比較した単純なMemcpyの結果
Buffer Size: 1GB | memcpy (ms) | memmove(ms) | naiveMemcpy()
------------------------------------------------------------
Laptop 1 | 113 | 161 | 160
Server 1 | 301 | 159 | 159
Server 2 | 325 | 159 | 159
編集:アセンブリ出力
シンプルなmemcpyソース
#include <cstring>
#include <cstdlib>
int main(int argc, char* argv[])
{
size_t SIZE_BYTES = 1073741824; // 1GB
char* p_big_array = (char*)malloc(SIZE_BYTES * sizeof(char));
char* p_dest_array = (char*)malloc(SIZE_BYTES * sizeof(char));
memset(p_big_array, 0xA, SIZE_BYTES * sizeof(char));
memset(p_dest_array, 0xF, SIZE_BYTES * sizeof(char));
memcpy(p_dest_array, p_big_array, SIZE_BYTES * sizeof(char));
free(p_dest_array);
free(p_big_array);
return 0;
}
アセンブリ出力:これは、サーバーとラップトップの両方でまったく同じです。スペースを節約し、両方を貼り付けません。
.file "main_memcpy.cpp"
.section .text.startup,"ax",@progbits
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB25:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movl $1073741824, %edi
pushq %rbx
.cfi_def_cfa_offset 24
.cfi_offset 3, -24
subq $8, %rsp
.cfi_def_cfa_offset 32
call malloc
movl $1073741824, %edi
movq %rax, %rbx
call malloc
movl $1073741824, %edx
movq %rax, %rbp
movl $10, %esi
movq %rbx, %rdi
call memset
movl $1073741824, %edx
movl $15, %esi
movq %rbp, %rdi
call memset
movl $1073741824, %edx
movq %rbx, %rsi
movq %rbp, %rdi
call memcpy
movq %rbp, %rdi
call free
movq %rbx, %rdi
call free
addq $8, %rsp
.cfi_def_cfa_offset 24
xorl %eax, %eax
popq %rbx
.cfi_def_cfa_offset 16
popq %rbp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE25:
.size main, .-main
.ident "GCC: (GNU) 4.6.1"
.section .note.GNU-stack,"",@progbits
PROGRESS !!!! asmlib
@tbensonの提案に基づいて、memcpyの asmlib バージョンで実行してみました。私の結果は当初貧弱でしたが、SetMemcpyCacheLimit()を1GB(バッファのサイズ)に変更した後、私は素朴なforループと同等の速度で実行していました!
悪いニュースは、memmoveのasmlibバージョンがglibcバージョンよりも遅いことです。現在は300msのマークで実行されています(glibcバージョンのmemcpyと同等)。奇妙なことは、ラップトップでSetMemcpyCacheLimit()を大きな数にするとパフォーマンスが低下することです...
以下の結果では、SetCacheでマークされた行のSetMemcpyCacheLimitは1073741824に設定されています。SetCacheのない結果は、SetMemcpyCacheLimit()を呼び出しません。
Asmlibの関数を使用した結果:
Buffer Size: 1GB | memcpy (ms) | memmove(ms) | naiveMemcpy()
------------------------------------------------------------
Laptop | 136 | 132 | 161
Laptop SetCache | 182 | 137 | 161
Server 1 | 305 | 302 | 164
Server 1 SetCache | 162 | 303 | 164
Server 2 | 300 | 299 | 166
Server 2 SetCache | 166 | 301 | 166
キャッシュの問題に傾いていますが、これは何が原因ですか?
[私はこれをコメントにしたいが、そうするのに十分な評判を持っていない。]
同様のシステムを使用しており、同様の結果が表示されますが、いくつかのデータポイントを追加できます。
memcpy
の方向を逆にすると(つまり、_*p_dest-- = *p_src--
_に変換すると)、順方向よりもパフォーマンスが大幅に低下する可能性があります(私にとっては〜637 ms)。 glibc 2.12でmemcpy()
に変更があり、オーバーラップするバッファーでmemcpy
を呼び出すためのいくつかのバグが公開されました( http://lwn.net/Articles/414467/ )そしてこの問題は、逆方向に動作するmemcpy
のバージョンに切り替えることによって引き起こされたと考えています。したがって、逆方向コピーと順方向コピーでは、memcpy()
/memmove()
格差を説明できます。memcpy()
実装は、大きなバッファー(つまり、最終レベルのキャッシュよりも大きい)の非一時ストア(キャッシュされない)に切り替わります。 Agner Fogのmemcpyのバージョン( http://www.agner.org/optimize/#asmlib )をテストしたところ、glibc
のバージョンとほぼ同じ速度であることがわかりました。ただし、asmlib
には、非一時ストアが使用されるしきい値を設定できる関数(SetMemcpyCacheLimit
)があります。私の場合、非一時的なストアがパフォーマンスを倍増するのを避けるために、その制限を8GiB(または1 GiBバッファー)よりも大きく)に設定しました(176msまでの時間)。順方向の素朴なパフォーマンスなので、恒星ではありません。memcpy
(104ms)で9.6 GB/sと表示されます。 HaswellシステムのRAMはDDR3-1600です(他のシステムと同じ)。[〜#〜] updates [〜#〜]
/proc/cpuinfo
_に基づいて、コアは3 GHzでクロックされました。ただし、これによりメモリパフォーマンスが約10%低下しました。これは私には正常に見えます。
2つのCPUを備えた8x16GB ECCメモリスティックの管理は、2x2GBを備えた単一のCPUよりもはるかに困難な作業です。 16GBスティックは、両面メモリ+バッファ+ ECC(マザーボードレベルで無効になっている場合もあります)を持っている可能性があります。すべてのデータパスをRAMより長くします。 RAMを使用し、他のCPUで何もしなくても、常にメモリアクセスはほとんどありません。このデータを切り替えるには、さらに時間が必要です。RAMをグラフィックカードと共有するPCで失われるパフォーマンスを見てください。
それでもあなたのサーバーは本当に強力なデータポンプです。実際のソフトウェアで1GBの複製が頻繁に発生するかどうかはわかりませんが、128GBはどのハードドライブよりもはるかに高速で、最高のSSDであっても、サーバーを活用できる場所であると確信しています。 3GBで同じテストを実行すると、ラップトップが起動します。
これは、コモディティハードウェアに基づいたアーキテクチャが大規模なサーバーよりもはるかに効率的である可能性のある完璧な例のように見えます。これらの大きなサーバーに費やしたお金で、何台のコンシューマPCを購入できますか?
非常に詳細な質問をありがとうございます。
編集:(この答えを書くのに時間がかかりすぎて、グラフ部分を見逃してしまいました。)
問題はデータの保存場所にあると思います。これを比較してください:
これにより、メモリコントローラーが互いに遠く離れたメモリブロックを処理する方法がわかります。あなたのデータはメモリの異なるゾーンに置かれ、あるゾーンと他のゾーンと通信するためにデータパスのある時点で切り替え操作が必要だと思います(両面メモリにはこのような問題があります)。
また、スレッドが1つのCPUにバインドされていることを確認していますか?
編集2:
メモリにはいくつかの種類の「ゾーン」区切り文字があります。 NUMAは1つですが、それだけではありません。たとえば、両面スティックには、どちらかのアドレスを指定するフラグが必要です。グラフ上で、ラップトップでも(NUMAがない場合でも)大量のメモリでパフォーマンスがどのように低下するかを確認してください。私にはこれはわかりませんが、memcpyはハードウェア機能を使用してRAM(DMAの一種)をコピーする可能性があり、このチップのキャッシュはCPUよりも少なくなければなりません。
IvyBridgeベースのラップトップのCPUのいくつかの改善が、SandyBridgeベースのサーバーを超えるこの利益に貢献する可能性があります。
Page-crossing Prefetch -ラップトップCPUは、現在のページの最後に到達するたびに次のリニアページを先読みして、毎回厄介なTLBミスを防ぎます。これを軽減するには、2M/1Gページ用のサーバーコードを作成してください。
キャッシュ置換スキームも改善されたようです(興味深いリバースエンジニアリングを参照してください ここ )。実際にこのCPUが動的挿入ポリシーを使用する場合、コピーされたデータがLast-Level-Cache(サイズのためにとにかく効果的に使用できない)をスラッシングしようとするのを簡単に防ぎ、他の便利なキャッシングのためのスペースを節約しますコード、スタック、ページテーブルデータなど)。これをテストするために、ストリーミングのロード/ストア(movntdq
または同様のもの、gccビルトインを使用することもできます)を使用して、単純な実装の再構築を試みることができます。この可能性は、大きなデータセットサイズの突然の低下を説明する場合があります。
文字列コピーでもいくつかの改善が行われたと思います( here )。アセンブリコードがどのように見えるかによって、ここに適用される場合と適用されない場合があります。 Dhrystone でベンチマークを試して、固有の違いがあるかどうかをテストできます。これは、memcpyとmemmoveの違いを説明する場合もあります。
IvyBridgeベースのサーバーまたはSandy-Bridgeラップトップを手に入れることができれば、これらすべてを一緒にテストするのが最も簡単です。
Linuxでnsecタイマーを使用するようにベンチマークを変更し、すべてのメモリが同じプロセッサごとに同様のバリエーションがあることを発見しました。実行中のすべてのRHEL6。数値は複数の実行で一貫しています。
Sandy Bridge E5-2648L v2 @ 1.90GHz, HT enabled, L2/L3 256K/20M, 16 GB ECC
malloc for 1073741824 took 47us
memset for 1073741824 took 643841us
memcpy for 1073741824 took 486591us
Westmere E5645 @2.40 GHz, HT not enabled, dual 6-core, L2/L3 256K/12M, 12 GB ECC
malloc for 1073741824 took 54us
memset for 1073741824 took 789656us
memcpy for 1073741824 took 339707us
Jasper Forest C5549 @ 2.53GHz, HT enabled, dual quad-core, L2 256K/8M, 12 GB ECC
malloc for 1073741824 took 126us
memset for 1073741824 took 280107us
memcpy for 1073741824 took 272370us
インラインCコード-O3の結果を次に示します
Sandy Bridge E5-2648L v2 @ 1.90GHz, HT enabled, 256K/20M, 16 GB
malloc for 1 GB took 46 us
memset for 1 GB took 478722 us
memcpy for 1 GB took 262547 us
Westmere E5645 @2.40 GHz, HT not enabled, dual 6-core, 256K/12M, 12 GB
malloc for 1 GB took 53 us
memset for 1 GB took 681733 us
memcpy for 1 GB took 258147 us
Jasper Forest C5549 @ 2.53GHz, HT enabled, dual quad-core, 256K/8M, 12 GB
malloc for 1 GB took 67 us
memset for 1 GB took 254544 us
memcpy for 1 GB took 255658 us
また、インラインmemcpyに一度に8バイトを実行させることも試みました。これらのIntelプロセッサでは、目立った違いはありませんでした。キャッシュは、すべてのバイト操作をメモリ操作の最小数にマージします。私は、gccライブラリのコードがあまりにも巧妙にしようとしているのではないかと疑っています。
質問はすでに回答されました 上記 、しかし、いずれにせよ、AVXを使用した実装があります。
#define ALIGN(ptr, align) (((ptr) + (align) - 1) & ~((align) - 1))
void *memcpy_avx(void *dest, const void *src, size_t n)
{
char * d = static_cast<char*>(dest);
const char * s = static_cast<const char*>(src);
/* fall back to memcpy() if misaligned */
if ((reinterpret_cast<uintptr_t>(d) & 31) != (reinterpret_cast<uintptr_t>(s) & 31))
return memcpy(d, s, n);
if (reinterpret_cast<uintptr_t>(d) & 31) {
uintptr_t header_bytes = 32 - (reinterpret_cast<uintptr_t>(d) & 31);
assert(header_bytes < 32);
memcpy(d, s, min(header_bytes, n));
d = reinterpret_cast<char *>(ALIGN(reinterpret_cast<uintptr_t>(d), 32));
s = reinterpret_cast<char *>(ALIGN(reinterpret_cast<uintptr_t>(s), 32));
n -= min(header_bytes, n);
}
for (; n >= 64; s += 64, d += 64, n -= 64) {
__m256i *dest_cacheline = (__m256i *)d;
__m256i *src_cacheline = (__m256i *)s;
__m256i temp1 = _mm256_stream_load_si256(src_cacheline + 0);
__m256i temp2 = _mm256_stream_load_si256(src_cacheline + 1);
_mm256_stream_si256(dest_cacheline + 0, temp1);
_mm256_stream_si256(dest_cacheline + 1, temp2);
}
if (n > 0)
memcpy(d, s, n);
return dest;
}
サーバー1の仕様
- CPU:2x Intel Xeon E5-2680 @ 2.70 Ghz
サーバー2の仕様
- CPU:2 x Intel Xeon E5-2650 v2 @ 2.6 Ghz
Intel ARKによると、 E5-265 と E5-268 の両方にAVX拡張があります。
ビルドするCMakeファイル
これは問題の一部です。 CMakeは、かなり貧弱なフラグを選択します。 make VERBOSE=1
を実行して確認できます。
-march=native
と-O3
の両方をCFLAGS
とCXXFLAGS
に追加する必要があります。劇的なパフォーマンスの向上が見られるでしょう。 AVX拡張機能を使用する必要があります。 -march=XXX
がなければ、最小限のi686またはx86_64マシンを効率的に取得できます。 -O3
がなければ、GCCのベクトル化に関与しません。
GCC 4.6がAVX(およびBMIなどの友人)に対応しているかどうかはわかりません。 GCCがmemcpyとmemsetをMMXユニットにアウトソーシングしているときにセグメンテーションフォールトを引き起こしていたアライメントバグを追跡しなければならなかったため、GCC 4.8または4.9が有効であることを知っています。 AVXおよびAVX2を使用すると、CPUは一度に16バイトおよび32バイトのデータブロックで動作できます。
GCCが整列したデータをMMXユニットに送信する機会がない場合、データが整列しているという事実が欠落している可能性があります。データが16バイトにアライメントされている場合、GCCにファットブロックでの操作を認識させるように伝えることができます。それについては、GCCの __builtin_assume_aligned
を参照してください。 ポインタ引数が常にダブルワードで整列されていることをGCCに伝える方法 のような質問も参照してください
void*
のため、これも少し疑わしいようです。ポインタに関する情報を破棄するようなものです。おそらく情報を保持する必要があります。
void doMemmove(void* pDest, const void* pSource, std::size_t sizeBytes)
{
memmove(pDest, pSource, sizeBytes);
}
次のようなものかもしれません:
template <typename T>
void doMemmove(T* pDest, const T* pSource, std::size_t count)
{
memmove(pDest, pSource, count*sizeof(T));
}
別の提案は、new
を使用し、malloc
の使用を停止することです。そのC++プログラムとGCCは、new
についてできないいくつかの仮定をmalloc
について行うことができます。前提条件の一部は、組み込みのGCCのオプションページで詳述されていると思います。
さらに別の提案は、ヒープを使用することです。典型的な最新のシステムでは常に16バイトに揃えられています。 GCCは、ヒープからのポインターが関係している場合(潜在的なvoid*
およびmalloc
の問題があるため)、MMXユニットにオフロードできることを認識する必要があります。
最後に、しばらくの間、Clangは-march=native
の使用時にネイティブCPU拡張を使用していませんでした。たとえば、 buntu Issue 1616723、Clang 3.4はSSE2のみをアドバタイズします 、 buntu Issue 1616723、Clang 3.5はSSE2のみをアドバタイズします 、および buntu Issue 1616723、Clang 3.6 SSE2 のみをアドバタイズします。