2013-10-18 10 views
19

Neden C++ dosyasındaki bir işlevin konumu performansını etkiliyor? Aşağıda verilen örnekte, farklı ve tutarlı performans profillerine sahip iki özdeş fonksiyona sahibiz. Bunu araştırmak ve performansın neden bu kadar farklı olduğunu belirlemek nasıl gerçekleşir?Neden C++ dosyasındaki bir işlevin konumu performansını etkiliyor

Örnek, iki fonksiyona sahip olduğumuz için oldukça basittir: a ve b. Her biri sıkı bir döngüde birçok kez çalıştırılır ve optimize edilir (-O3 -march=corei7-avx) ve zamanlanır.

#include <cstdint> 
#include <iostream> 
#include <numeric> 

#include <boost/timer/timer.hpp> 

bool array[] = {true, false, true, false, false, true}; 

uint32_t __attribute__((noinline)) a() { 
    asm(""); 
    return std::accumulate(std::begin(array), std::end(array), 0); 
} 

uint32_t __attribute__((noinline)) b() { 
    asm(""); 
    return std::accumulate(std::begin(array), std::end(array), 0); 
} 

const size_t WARM_ITERS = 1ull << 10; 
const size_t MAX_ITERS = 1ull << 30; 

void test(const char* name, uint32_t (*fn)()) 
{ 
    std::cout << name << ": "; 
    for (size_t i = 0; i < WARM_ITERS; i++) { 
     fn(); 
     asm(""); 
    } 
    boost::timer::auto_cpu_timer t; 
    for (size_t i = 0; i < MAX_ITERS; i++) { 
     fn(); 
     asm(""); 
    } 
} 

int main(int argc, char **argv) 
{ 
    test("a", a); 
    test("b", b); 
    return 0; 
} 

Bazı önemli özellikleri:

  • Fonksiyon a ve b aynı İşte kodudur. Aynı birikme işlemini gerçekleştirir ve aynı montaj talimatlarını derler.
  • Her test yinelemesinin, zamanlamanın, önbelleklerin ısınmasıyla ilgili sorunları gidermeye ve ortadan kaldırmaya başlamadan önce ısınma süresi vardır. Hareketsiz yavaştır iki testleri ters olursa

    [[email protected]:~/code/mystery] make && ./mystery 
    g++-4.8 -c -g -O3 -Wall -Wno-unused-local-typedefs -std=c++11 -march=corei7-avx -I/usr/local/include/boost-1_54/ mystery.cpp -o mystery.o 
    g++-4.8 mystery.o -lboost_system-gcc48-1_54 -lboost_timer-gcc48-1_54 -o mystery 
    a: 7.412747s wall, 7.400000s user + 0.000000s system = 7.400000s CPU (99.8%) 
    b: 5.729706s wall, 5.740000s user + 0.000000s system = 5.740000s CPU (100.2%) 
    

    (yani sonra test(a)test(b) arayıp): Bu derlenmiş ve çalışma olduğunda

biz b anlamlı derecede daha yavaştır gösteren şu çıktıyı almak b daha: biz şimdi C++ dosya fonksiyonlarının yerini ters Eğer (a yukarıdaki b tanımını taşımak)

[[email protected]:~/code/mystery] make && ./mystery 
g++-4.8 -c -g -O3 -Wall -Wno-unused-local-typedefs -std=c++11 -march=corei7-avx -I/usr/local/include/boost-1_54/ mystery.cpp -o mystery.o 
g++-4.8 mystery.o -lboost_system-gcc48-1_54 -lboost_timer-gcc48-1_54 -o mystery 
b: 5.733968s wall, 5.730000s user + 0.000000s system = 5.730000s CPU (99.9%) 
a: 7.414538s wall, 7.410000s user + 0.000000s system = 7.410000s CPU (99.9%) 

sonuç ters ve bir b daha hızlı hale gelir!

[[email protected]:~/code/mystery] make && ./mystery 
g++-4.8 -c -g -O3 -Wall -Wno-unused-local-typedefs -std=c++11 -march=corei7-avx -I/usr/local/include/boost-1_54/ mystery.cpp -o mystery.o 
g++-4.8 mystery.o -lboost_system-gcc48-1_54 -lboost_timer-gcc48-1_54 -o mystery 
a: 5.729604s wall, 5.720000s user + 0.000000s system = 5.720000s CPU (99.8%) 
b: 7.411549s wall, 7.420000s user + 0.000000s system = 7.420000s CPU (100.1%) 

Temel olarak, C++ dosyasının en üstünde hangi işlev varsa daha yavaştır. sorulara

Bazı cevaplar olabilir:

  • derlenmiş kod a ve b her ikisi için aynıdır. Demontaj kontrol edildi. (İlgilenenler için: http://pastebin.com/2QziqRXR)
  • Kod, gb 4.8, gcc 4.8.1 ubuntu 13.04, ubuntu 13.10 ve ubuntu 12.04.03 kullanılarak derlenmiştir.
  • Intel Sandy Bridge i7-2600 ve Intel Xeon X5482 cpus üzerinde gözlemlenen etkiler.

Bu neden oluyor? Böyle bir şeyi araştırmak için hangi araçlar kullanılabilir?

+0

Farklı sayfalarda sona ermeleri ve bu da fazladan işlere yol açıyor mu? CPU zamanının Kullanıcı değil Sistem ölçümünde tuhaf olduğunu buluyorum. Bu, kullanıcı kodunun zaman almasının değil, aynı zamanda süreçler üzerinde bazı işletim sistemi seviyelerinin çalışması anlamına gelmez. –

+0

Karanlıkta tam bir çekim olarak, b oturumunun ilk kez çalıştırılan bir oturum sonucunda daha da ısınmasını öneririm ... (DÜZENLEME: oh, tersine çevirdiniz ...) –

+0

@DaveS İnanıyorum ki zaman tüm kullanıcı uzayında topraktır. döngü ısınma döngü (ölçüm önce) önbellek ve şube tahmini yeterince ısınma olmalıdır. – Shane

cevap

6

Bana bir önbellek takma sorunu gibi görünüyor.

Test durumu oldukça zeki ve zamanlamadan önce her şeyi önbelleğe doğru şekilde yükler.

valgrind --tool=cachegrind --I1=32768,8,64 --D1=32768,8,64 /tmp/so 
==11130== Cachegrind, a cache and branch-prediction profiler 
==11130== Copyright (C) 2002-2012, and GNU GPL'd, by Nicholas Nethercote et al. 
==11130== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info 
==11130== Command: /tmp/so 
==11130== 
--11130-- warning: L3 cache found, using its data for the LL simulation. 
a: 6.692648s wall, 6.670000s user + 0.000000s system = 6.670000s CPU (99.7%) 
b: 7.306552s wall, 7.280000s user + 0.000000s system = 7.280000s CPU (99.6%) 
==11130== 
==11130== I refs:  2,484,996,374 
==11130== I1 misses:   1,843 
==11130== LLi misses:   1,694 
==11130== I1 miss rate:   0.00% 
==11130== LLi miss rate:   0.00% 
==11130== 
==11130== D refs:  537,530,151 (470,253,428 rd + 67,276,723 wr) 
==11130== D1 misses:   14,477 ( 12,433 rd +  2,044 wr) 
==11130== LLd misses:   8,336 (  6,817 rd +  1,519 wr) 
==11130== D1 miss rate:   0.0% (  0.0%  +  0.0% ) 
==11130== LLd miss rate:   0.0% (  0.0%  +  0.0% ) 
==11130== 
==11130== LL refs:    16,320 ( 14,276 rd +  2,044 wr) 
==11130== LL misses:   10,030 (  8,511 rd +  1,519 wr) 
==11130== LL miss rate:   0.0% (  0.0%  +  0.0% ) 
: simüle olsa da, Valgrind en cachegrind aracının çıkışına bakarak bu doğrulanmış ettik ve böyle küçük bir test durumda beklediğiniz gibi, anlamlı önbellek vardır - her şey önbelleğinde uyuyor gibi görünüyor

Yaygın Intel CPU'larını eşleştirmek için 64 bayt önbellek satırı boyutuyla 32 k, 8 yollu bir ilişki önbelleği aldım ve art arda a ve b işlevi arasındaki aynı tutarsızlığı gördüm. gerçi aynı önbellek satırı boyutu ile bir 32k, 128 yolu ilişkisel önbellek, o fark ile hayali bir makinedeki tüm ama Running

uzağa gider:

8 yönlü önbelleğinde yana
valgrind --tool=cachegrind --I1=32768,128,64 --D1=32768,128,64 /tmp/so 
==11135== Cachegrind, a cache and branch-prediction profiler 
==11135== Copyright (C) 2002-2012, and GNU GPL'd, by Nicholas Nethercote et al. 
==11135== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info 
==11135== Command: /tmp/so 
==11135== 
--11135-- warning: L3 cache found, using its data for the LL simulation. 
a: 6.754838s wall, 6.730000s user + 0.010000s system = 6.740000s CPU (99.8%) 
b: 6.827246s wall, 6.800000s user + 0.000000s system = 6.800000s CPU (99.6%) 
==11135== 
==11135== I refs:  2,484,996,642 
==11135== I1 misses:   1,816 
==11135== LLi misses:   1,718 
==11135== I1 miss rate:   0.00% 
==11135== LLi miss rate:   0.00% 
==11135== 
==11135== D refs:  537,530,207 (470,253,470 rd + 67,276,737 wr) 
==11135== D1 misses:   14,297 ( 12,276 rd +  2,021 wr) 
==11135== LLd misses:   8,336 (  6,817 rd +  1,519 wr) 
==11135== D1 miss rate:   0.0% (  0.0%  +  0.0% ) 
==11135== LLd miss rate:   0.0% (  0.0%  +  0.0% ) 
==11135== 
==11135== LL refs:    16,113 ( 14,092 rd +  2,021 wr) 
==11135== LL misses:   10,054 (  8,535 rd +  1,519 wr) 
==11135== LL miss rate:   0.0% (  0.0%  +  0.0% ) 

, daha az boşluk vardır Potansiyel olarak takma işlevlerin gizlenebileceği yerlerde, daha fazla karma çarpışmaların adresleme eşdeğerini alırsınız. Farklı önbellek birleştirme özelliğine sahip olan makine ile, bu durumda, nesne dosyasının içine yerleştirilen yerlerle şansınızı yitirirsiniz ve bir önbellek özeti olmasa da, hangi önbellek hattını çözmek için herhangi bir iş yapmanız gerekmez Aslında ihtiyacın var.

Düzenleme: daha önbellek ilişkilendirilebilirlik tarih: http://en.wikipedia.org/wiki/CPU_cache#Associativity


Başka düzenleme: donanım olay perf aracıyla izlenmesi ile bu teyit ettik.

Bir komut satırı argümanının bulunup bulunmadığına bağlı olarak kaynağı yalnızca a() veya b() 'yi çağırmak üzere değiştirdim. Zamanlamalar orijinal test durumundakiyle aynıdır. O b gösteriliyor oysa

sudo perf record -e dTLB-loads,dTLB-load-misses,dTLB-stores,dTLB-store-misses,iTLB-loads,iTLB-load-misses /tmp/so foobar 
b: 4.854249s wall, 4.840000s user + 0.000000s system = 4.840000s CPU (99.7%) 
sudo perf report 

3K dTLB-loads 
87 dTLB-load-misses 
3K dTLB-stores 
19 dTLB-store-misses 
259 iTLB-loads 
93 iTLB-load-misses 

sudo perf record -e dTLB-loads,dTLB-load-misses,dTLB-stores,dTLB-store-misses,iTLB-loads,iTLB-load-misses /tmp/so 
a: 6.317755s wall, 6.300000s user + 0.000000s system = 6.300000s CPU (99.7%) 
sudo perf report 

4K dTLB-loads 
97 dTLB-load-misses 
4K dTLB-stores 
7 dTLB-store-misses 
479 iTLB-loads 
142 iTLB-load-misses    

az TLB eylem vardır ve bu yüzden önbellek tahliye olmak zorunda değildir. İkisi arasındaki işlevselliğin aynı olduğu göz önüne alındığında, sadece takma yoluyla açıklanabilir.

0

test'dan a ve b numaralı telefonu arıyorsunuz. Derleyicinin iki işlevinizi yeniden sıralamak için bir nedeni olmadığından a, b (orijinalinde) test'dan daha uzaktadır. Ayrıca, gerçek kod oluşturma işlemi C++ kaynağından göründüğünden biraz daha büyük olduğu için şablonlar da kullanıyorsunuz.

nedenle b için talimat hafıza test, a cache içine almak ve bu nedenle aşağı alt önbelleklerini veya CPU ana bellekten al uzun sürmüyor daha uzakta olmanın birlikte talimat önbelleğine alır oldukça mümkündür b olduğunu .

O sadece daha uzakta olduğu, çünkü uzun talimat gerçek kod aynı olsa bile b daha a çalışır yavaş, b daha a için döngülerini getirme mümkündür.

Bazı CPU mimarileri (arm cortex-A serisi gibi) önbellek kayıplarının sayısını belirleyen performans sayaçlarını destekler. perf gibi araçlar, uygun performans sayaçlarıyla çalışacak şekilde ayarlandığında bu verileri yakalayabilir.

İlgili konular