2012-09-28 10 views
50

Performans izleme için tsc'yi kullanmaya çalıştığımızı ve talimatların yeniden sıralanmasını önlemek istediğimizi varsayalım.rdtscp, rdtsc: memory ve cpuid/rdtsc arasındaki fark nedir?

1:rdtscp bir Serileştirme çağrıdır

Bu

bizim seçeneklerdir. Rdtscp'ye çağrı etrafında yeniden sıralamayı engeller.

__asm__ __volatile__("rdtscp; "   // serializing read of tsc 
        "shl $32,%%rdx; " // shift higher 32 bits stored in rdx up 
        "or %%rdx,%%rax" // and or onto rax 
        : "=a"(tsc)  // output to tsc variable 
        : 
        : "%rcx", "%rdx"); // rcx and rdx are clobbered 

Ancak rdtscp yeni işlemci üzerinde kullanılabilir. Bu durumda rdtsc kullanmamız gerekiyor. Ancak rdtsc seri hale getirilmiyor, dolayısıyla tek başına kullanmak CPU'nun yeniden sıralanmasını engelleyemez.

2:

yüzden yeniden sıralama önlemek için bu iki seçenekten birini kullanabilirsiniz Bu da cpuid ve rdtsc için bir çağrıdır. cpuid seri hale getirme çağrısıdır.

volatile int dont_remove __attribute__((unused)); // volatile to stop optimizing 
unsigned tmp; 
__cpuid(0, tmp, tmp, tmp, tmp);     // cpuid is a serialising call 
dont_remove = tmp;        // prevent optimizing out cpuid 

__asm__ __volatile__("rdtsc; "   // read of tsc 
        "shl $32,%%rdx; " // shift higher 32 bits stored in rdx up 
        "or %%rdx,%%rax" // and or onto rax 
        : "=a"(tsc)  // output to tsc 
        : 
        : "%rcx", "%rdx"); // rcx and rdx are clobbered 

3: Bu yeniden sıralama önler clobber listesinde memory ile rdtsc çağrısı olduğunu

__asm__ __volatile__("rdtsc; "   // read of tsc 
        "shl $32,%%rdx; " // shift higher 32 bits stored in rdx up 
        "or %%rdx,%%rax" // and or onto rax 
        : "=a"(tsc)  // output to tsc 
        : 
        : "%rcx", "%rdx", "memory"); // rcx and rdx are clobbered 
                // memory to prevent reordering 

aşağıdaki gibi 3 seçenek için My anlayış:

Yapımı __volatile__ numaralı çağrı, eniyileyicinin asm'ı kaldırmasını veya asm'ın sonuçlarına (veya girdilerin değiştirilmesine) gerek duyabilecek herhangi bir yönerge üzerinde hareket etmesini engeller. Ancak ilgisiz operasyonlara göre hareket edebilir. Yani __volatile__ yeterli değil.

Derleyici belleğinin yakalandığını söyle: : "memory"). "memory" clobber, GCC'nin, bellek içeriği ile ilgili olarak asm'da aynı kalan tüm varsayımları yapamayacağı anlamına gelir ve bu nedenle, etrafında yeniden sıralama yapmayacaktır.

Yani benim sorular şunlardır:

  • 1: __volatile__ ve "memory" doğru benim anlayış var mı?
  • 2: İkinci iki çağrı da aynı şeyi yapıyor mu?
  • 3: "memory"'u kullanmak, başka bir serileştirme komutunu kullanmaktan çok daha basit görünüyor. Neden 3. seçenekte 3. seçeneği kullanan kimse var?
  • bir yorumda belirtildiği gibi
+9

Sen sen kullanarak önlemek, hangi 'volatile' ve' memory' ve (aka sipariş execution_ ait _out) işlemcisi tarafından çalıştırılan komutların yeniden sıralama kullanarak önleyebilirsiniz derleyici tarafından oluşturulan talimatların yeniden sıralama şaşırtmak gibi görünüyor ' cpuid'. – hirschhornsalz

+0

@hirschhornsalz ama "bellekteki" hafızaya alma işlemi, işlemcinin talimatları yeniden sıralamasını engelliyor mu? Bellek hafıza çiti gibi davranmıyor mu? –

+0

ya da belki de clobber listesindeki 'hafıza 'sadece gcc'ye gönderilir ve sonuçta ortaya çıkan makine kodu bunu işlemciye göstermez mi? –

cevap

35

, bir derleyici bariyer ve işlemci bariyer arasında bir fark vardır. Asm deyiminde volatile ve memory derleyici bariyeri olarak işlev görür, ancak işlemci yönergeleri yeniden düzenlemek için hala ücretsizdir.

İşlemci bariyeri, açıkça belirtilmesi gereken özel talimatlardır, örn. rdtscp, cpuid, hafıza kilidi talimatları (mfence, lfence, ...) vb.Olarak bir kenara

, rdtsc yaygındır önce bariyer olarak cpuid kullanırken, aynı zamanda sanal makine platformları genellikle tuzak beri bir performans açısından çok kötü olabilir ve CPU ortak bir dizi empoze etmek amacıyla cpuid talimat taklit Bir kümedeki birden fazla makinede bulunan özellikler (canlı geçişin çalışmasını sağlamak için). Böylece bellek çit talimatlarından birini kullanmak daha iyidir.

Linux çekirdeği AMD platformlarında mfence;rdtsc ve Intel lfence;rdtsc kullanır. Üzerinde mfence;rdtsc eserler bunlar arasında ayrım ile rahatsız etmek istemiyorsanız hem de mfencelfence daha güçlü bir engeldir yavaş olarak biraz olsa.

+5

'cpuid (aslında doğru ölçümler için ** ve **' rdtscp' + 'cpuid' hem' rdtsc' + 'cpuid' gerektiğini göstermektedir); rdtsc, bellek çitleriyle ilgili değil, talimat akışını serileştirmekle ilgili. Genellikle, yeniden sıralama tamponları/rezervasyon istasyonlarında "eski" talimatların kalmadığından emin olmak için karşılaştırma amacıyla kullanılır. "Cpuid" in yürütme süresi (ki bu oldukça uzun,> 200 çevrimi hatırlıyorum) daha sonra çıkarılacak. Eğer sonuç daha "kesin" ise, bu yol benim için pek açık değil, ben de denedim ve deneyimlemedim ve farklılıklar, hiç bir şey olmadığında, tek kullanıcı modunda bile, ölçümün doğal hatası kadar az görünüyor. – hirschhornsalz

+0

Emin değilim, ama muhtemelen çekirdekte bu şekilde kullanılan çit yönergesi hiç de yararlı değil ^^ – hirschhornsalz

+4

@hirschhornsalz: Git işlem günlüklerine göre, AMD ve Intel şu anda m/lence'nin rdtsc'yi şu anda serileştireceğini doğruladı kullanılabilir CPU'lar. Andi Kleen'in tam olarak ne söylendiğine dair daha fazla bilgi verebileceğini, eğer ilgilenir ve ona sorarsa, sanırım. – janneb

5

bunu aşağıda gösterildiği sever kullanabilirsiniz:

Yukarıdaki kodda
asm volatile (
"CPUID\n\t"/*serialize*/ 
"RDTSC\n\t"/*read the clock*/ 
"mov %%edx, %0\n\t" 
"mov %%eax, %1\n\t": "=r" (cycles_high), "=r" 
(cycles_low):: "%rax", "%rbx", "%rcx", "%rdx"); 
/* 
Call the function to benchmark 
*/ 
asm volatile (
"RDTSCP\n\t"/*read the clock*/ 
"mov %%edx, %0\n\t" 
"mov %%eax, %1\n\t" 
"CPUID\n\t": "=r" (cycles_high1), "=r" 
(cycles_low1):: "%rax", "%rbx", "%rcx", "%rdx"); 

, ilk CPUID çağrı RDTSC talimatı altında ve üstünde talimatların dışı bir yürütme önlemek için bir bariyer uygular. Bu yöntemle, gerçek zamanlı kayıtlar

arasındaki bir CPUID komutunu çağırmaktan kaçınırız. İlk RDTSC sonra zaman damgası yazıcısını okur ve değer belleğinde saklanır. Sonra ölçmek istediğimiz kod yürütülür. RDTSCP komutu, zaman damgası kaydını ikinci kez okur ve ölçmek istediğimiz tüm kodların yürütülmesinin tamamlanmasını garanti eder. Daha sonra gelen iki “mov” komutu edx ve eax kayıtlarını hafızaya kaydeder. Son olarak, bir CPUID çağrısı, bir engelin tekrar uygulanmasını garanti eder, böylece daha sonra gelen herhangi bir komutun, CPUID'den önce yürütülmesi imkansızdır.

+12

Merhaba, bu yanıtı Gabriele Paolinis adlı beyaz kağıttan "Intel® IA-32 ve IA-64 Komut Seti Mimarileri Üzerindeki Kod Yürütme Süreleri Nasıl Kıyaslama" (siz bir satır sonu kaçırdınız) adlı belgeden kopyaladığınız anlaşılıyor. Yazarın kredisini vermeden başka birinin işini kullanıyorsun. Neden bir ilişkilendirme eklemiyorsunuz? –

+0

Evet, aslında, bu başa çıkıyor. Ayrıca, başlangıç ​​saatini okuyan iki oyuncunun da gerekli olup olmadığını merak ediyorum: http://stackoverflow.com/questions/38994549/is-intels-timestamp-reading-asm-code-example-using-two-more-registers -ehan-are –

+0

Iki değişken yüksek ve düşük sahip olmak için belirli bir neden var mı? – ExOfDe

İlgili konular