Zavallı işlev, hedef'in Linux üzerinde Performans
Son zamanlarda bazı yeni sunucular satın aldık ve zavallı işlev, hedef'in performans yaşıyoruz. Memcpy performansı 3x yavaş bizim dizüstü bilgisayarlar ile karşılaştırıldığında sunucularda.
Server Özellikleri
- Kasa ve Anakart: SÃœPER MÄ°KRO 1027GR-SIRALANAN
- Ä°ÅžLEMCÄ°: Intel E5-2680 @ Xeon 2.70 Ghz 2x
- Bellek: 8x 16 GB DDR3 1600 MHz
Edit: biraz daha yüksek özellikleri olan bir sunucu üzerinde test ve yukarıdaki sunucusu olarak aynı sonuçları görüyorum
Server 2 Özellikleri
- Kasa ve Anakart: SÃœPER MÄ°KRO 10227GR-TRFT
- CPU: E5- @ 2.6 Ghz 2650 v2 . 2x, Intel tabanlı
- Bellek: 16 GB DDR3 8x ben 1866mhz
Laptop Özellikleri
- Kasa: Lenovo W530
- Ä°ÅžLEMCÄ°: 1 x Intel Core i7 i7- @ 3720QM 2.6 Ghz
- Bellek: 4x 4GB DDR3 1600 MHz
Ä°ÅŸletim Sistemi
$ 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
Derleyici (tüm sistemlerde)
$ gcc --version
gcc (GCC) 4.6.1
Ayrıca bir öneri dayalı gcc 4.8.2 ile test @stefan. Derleyiciler arasında performans farkı yok.
Test Kodu Test aşağıdaki kod üretim kodu olarak görüyorum sorun çoğaltmak için hazırlanmış bir testtir. Bu kriter basit ama bizim sorunumuz istismar tespit etmek mümkün olduğunu biliyorum. Kod aralarında iki 1GB tamponlar ve memcpys, işlev, hedef'in çağrı zamanlama oluşturur. Komut satırını kullanarak diğer arabellek boyutları belirtebilirsiniz: ./big_memcpy_test [SİZE_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;
}
Dosya Oluşturmak için 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})
Test Sonuçları
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
Gördüğünüz gibi bizim sunucularda memcpys ve memsets bizim dizüstü üzerinde memcpys ve memsets yavaştır.
Tampon farklı boyutlarda
Benzer sonuçlar (sunucular laptop daha yavaş) ile 5 GB tüm 100MB dan tamponlar denedim
BenzeÅŸme NUMA
CPU ve bellek yakınlık numactl kullanarak ama sonuç aynı kaldı ayarı denedim yani insanlar NUMA ile performans sorunları hakkında okudum.
Sunucu Donanım 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
Laptop Donanım 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 Yakınlık ayarı
$ numactl --cpunodebind=0 --membind=0 ./big_memcpy_test
Herhangi bir yardım bu hadiseyi büyük beğeni topluyor.
Edit: GCC Seçenekleri
Yoruma dayalı farklı seçenekler GCC ile derleme denedim:
-Mart ve yerli-mtune set ile derleniyor
g -std=c 0x -Wall -march=native -mtune=native -O3 -DNDEBUG -o big_memcpy_test main.cpp
Sonuç: aynı performans (hiçbir gelişme)
-O2 ile derleme yerine -O3
g -std=c 0x -Wall -march=native -mtune=native -O2 -DNDEBUG -o big_memcpy_test main.cpp
Sonuç: aynı performans (hiçbir gelişme)
Edit: BOŞ sayfa (@SteveCox) önlemek için 0 yerine 0xF yazmaya Değişti memset
Değeri 0 (0xF bu durumda kullanılır) dışında memsetting ne zaman bir düzelme yok.
Edit: Cachebench sonuçları
Benim test programı çok basit olduğunu ekarte etmek için gerçek bir kıyaslama programı LLCacheBench (http://icl.cs.utk.edu/projects/llcbench/cachebench.html) indirdim
Her makinede kriter ayrı ayrı mimari sorunları önlemek için yaptım. Aşağıda benim sonuçlar.
ÇOK büyük bir fark olduğunu daha büyük arabellek boyutları üzerinde performans. Son boyut (16777216) test 18849.29 yapılan sec, dizüstü bilgisayar ve 6710.40 sunucuda MB/. Bu performans hakkında 3x bir fark var. Ayrıca, sunucu performansı dropoff laptop daha çok dik olduğunu fark edebilirsiniz.
Edit: () hedef 2 kat daha HIZLI memcpy() sunucu üzerinde
Hedef kullanarak denedim bazı deneye dayalı() işlev, hedef'in yerine() testimi ve sunucu üzerinde 2x bir iyileşme var. () Hedef dizüstü bilgisayar daha yavaş çalışır memcpy() ama garip bir şekilde, hedef olarak aynı hızda çalışır yeterli() sunucu üzerinde. Bu soru yalvarır, işlev, hedef'in neden bu kadar yavaş?
Test için güncellenmiş Kod işlev, hedef'in birlikte hedef. Vardı kaydır hedef() içinde bir fonksiyon çünkü eğer ben sol satır GCC optimize edildi ve yapılan tam olarak aynı memcpy() (sanırım gcc optimize etmek için memcpy çünkü biliyordu yerleri yoktu üst üste).
Sonuçlar Güncellendi
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
Edit: Saf Ä°ÅŸlev, Hedef'in
Öneri @Salgar dayalı kendi saf memcpy fonksiyonu uygulanan ve test ettik.
Saf İşlev, Hedef'in Kaynağı
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 ;
}
}
Saf işlev, hedef'in Sonuçları işlev, hedef'in Göre()
Buffer Size: 1GB | memcpy (ms) | memmove(ms) | naiveMemcpy()
------------------------------------------------------------
Laptop 1 | 113 | 161 | 160
Server 1 | 301 | 159 | 159
Server 2 | 325 | 159 | 159
Edit: Derleme Çıktı
Basit bir işlev, hedef'in kaynağı
#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;
}
Derleme Çıktısı: Bu sunucu ve dizüstü hem de aynı. Yerden tasarruf ve ikisini yapıştırma ediyorum.
.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, íi
pushq %rbx
.cfi_def_cfa_offset 24
.cfi_offset 3, -24
subq $8, %rsp
.cfi_def_cfa_offset 32
call malloc
movl $1073741824, íi
movq %rax, %rbx
call malloc
movl $1073741824, íx
movq %rax, %rbp
movl $10, %esi
movq %rbx, %rdi
call memset
movl $1073741824, íx
movl $15, %esi
movq %rbp, %rdi
call memset
movl $1073741824, íx
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 êx, êx
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
!!!Ä°LERLEME! asmlib
Öneri @tbenson dayalı işlev, hedef'in asmlib sürümü ile çalıştırmayı denedim. Benim sonuçlar başlangıçta fakir ama SetMemcpyCacheLimit değiştirdikten sonra() döngü için saf! ile eşit hızda koşuyordum 1GB (arabellek boyutu)
Kötü haber hedef olan asmlib sürümünü abone olarak giriş sürümü, şimdi 300ms işareti çalışıyor (bu işlev, hedef'in abone olarak giriş sürümü ile aynı düzeyde) daha yavaş olmasıdır. Garip şey SetMemcpyCacheLimit zaman dizüstü() çok sayıda performans... ... canını yakıyor
Çizgiler SetCache ile işaretlenmiş aşağıda sonuçlar var 1073741824 için SetMemcpyCacheLimit ayarlayın. SetCache olmadan sonuçları SetMemcpyCacheLimit arama()
Sonuçlar asmlib fonksiyonları kullanarak:
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
Doğru cache sorunu yalın başlangıç, ama buna ne sebep olabilir?
CEVAP
[Bu bir yorum yapmak isterdim, ama bunu yapmak için yeterince ünü yok.]
Benzer bir sistemim var ve benzer sonuçlar, ama birkaç veri noktaları eklemek için:
- EÄŸer
memcpy
saf (yani 33 ** dönüştürme) yönü ters ise, o zaman çok daha kötü olabilir ileriye doğru daha performanslı (~637 benim için ms). Değişiklik oldumemcpy()
glibc 2.12 bu açıkta çeşitli böcek aramamemcpy
çakışan tamponlar (http://lwn.net/Articles/414467/) ve sanırım sorunu olmasından kaynaklandığını geçiş için bir sürümümemcpy
geriye doğru çalışır. Yani, geriye dönük karşı ileri kopyamemcpy()
/memmove()
farkı açıklayabilir. - Non-temporal mağazaları kullanmamak daha iyi gibi görünüyor. Birçok
memcpy()
uygulamaları-zamansal olmayan depolar önbelleğe olan) geçmek büyük tamponlar için (yani son seviyede daha büyük önbellek optimize edilmiş. Bu işlev, hedef'in Agner Fog'un sürüm (*48)* * * veglibc
sürümü olarak aynı hız yaklaşık buldum test ettim. Ancak,asmlib
olan yukarıdaki eşik ayarı izin veren bir işlev (SetMemcpyCacheLimit
) non-temporal depolar kullanılır. 8GiB (veya 1 GiB tampon daha sadece daha büyük) sınır dışı geçici depolar önlemek için ayarı iki katına benim davam (176ms zaman aşağı) performans. Tabii ki, o yıldız değil bu yüzden sadece ileri yönde saf performans tutuyor. - Bu sistemlerde BIOS dört farklı donanım prefetchers devre dışı (MLC Flama önceden getirici, Mekansal önceden getirici MLC, Flama önceden getirici DCU ve IP önceden getirici DCU)/etkin olmasını sağlar. Her devre dışı, ama çok iyi muhafaza performans parite ve düşük performans yapmakta ayarları birkaç için çalıştım.
- Çalışan ortalama güç sınırı (RAPL) devre dışı bırakma modu etkisi yoktur DRAM.
- Diğer Supermicro sistemleri Fedora 19 çalışan (abone olarak giriş 2.17) erişimi var. 19 ve " çok çekirdekli E5-2670 CPU Fedora X9DRG-HF Supermicro bir yönetim kurulu ile, yukarıdaki gibi benzer bir performans görüyorum. 9.6 bakın 19 Supermicro X10SLM-F tek soket Yönetim Kurulu Xeon E3-1275 v3 (Haswell) ve Fedora üzerinde çalışan GB
memcpy
(104ms)/s. Haswell sistem üzerindeki RAM DDR3-1600 (Diğer sistemler gibi).
GÃœNCELLEÅžTÄ°RMELERÄ°
- Maksimum Performans ve BIOS hiper iş parçacığı devre dışı CPU güç yönetimi kurdum.
/proc/cpuinfo
temel çekirdek 3 GHz saat hızına sahip sonra. Ancak garip bir şekilde bu hafıza performansı civarında azaldı. - memtest86 4.10 raporları 9091 MB/s bellek bant genişliği. Bu okuma karşılık gelir, yazma veya kopyalama bulamadım.
- STREAM benchmark raporlar 13422 MB/s kopyalama, ama onların sayısı bayt olarak hem okuma hem de yazılı, böylece karşılık gelen ~6.5 GB/s Eğer istediğimiz karşılaştırmak için yukarıdaki sonuçları.