Montaj ne zaman C'den daha hızlıdır?


475

Montajcıyı tanımak için belirtilen nedenlerden biri, bazen, bu kodu daha yüksek seviyeli bir dilde, özellikle C olarak yazmaktan daha performanslı olacak kod yazmak için kullanılabilmesidir. Bununla birlikte, birçok kez, tamamen yanlış olmasa da, montajcının aslında daha fazla performans kodu üretmek için kullanılabileceği durumların hem son derece nadir olduğunu hem de montaj konusunda uzman bilgisi ve deneyim gerektirdiğini birçok kez duyduğumu duydum .

Bu soru, montaj talimatlarının makineye özgü ve taşınabilir olmayacağı veya montajcının diğer yönlerinden herhangi birine bile girmez. Elbette bunun dışında montajı bilmek için birçok iyi neden var, ancak bu, montajcıya karşı daha üst düzey dillere yönelik genişletilmiş bir söylem değil, örnek ve verileri isteyen belirli bir soru olması anlamına geliyor.

Modern derleyici kullanarak montajın iyi yazılmış C kodundan daha hızlı olacağı bazı özel örnekler verebilir misiniz ve bu iddiayı profil kanıtlarıyla destekleyebilir misiniz? Bu vakaların var olduğundan oldukça eminim, ancak bu davaların ne kadar ezoterik olduğunu tam olarak bilmek istiyorum, çünkü bazı çekişmelerin bir noktası gibi görünüyor.


17
aslında derlenmiş kod üzerinde geliştirmek oldukça önemsizdir. Montaj dili ve C hakkında sağlam bir bilgiye sahip olan herkes, üretilen kodu inceleyerek bunu görebilir. Herhangi bir kolay olan, derlenmiş sürümde tek kullanımlık kayıtlarınız bittiğinde düştüğünüz ilk performans uçurumudur. Derleyici, büyük bir proje için ortalama olarak bir insandan çok daha iyisini yapacaktır, ancak iyi boyutlandırılmış bir projede derlenmiş koddaki performans sorunlarını bulmak zor değildir.
old_timer

14
Aslında, kısa cevap şudur: Assembler her zaman C hızına eşit veya daha hızlıdır. Bunun nedeni, C olmadan montaj yapabilmenizdir, ancak montaj olmadan C'ye sahip olamazsınız (eski formda, günler "makine kodu" olarak adlandırılır). Bununla birlikte, uzun cevap şudur: C Derleyiciler genellikle düşünmediğiniz şeyleri optimize etmek ve "düşünmek" için oldukça iyidir, bu yüzden gerçekten becerilerinize bağlıdır, ancak normalde C derleyicisini yenebilirsiniz; hala sadece düşünemeyen ve fikir edemeyen bir yazılım. Ayrıca, makro kullanıyorsanız ve sabırlıysanız, taşınabilir birleştirici de yazabilirsiniz.

11
Bu soruya verilen cevapların "görüşe dayalı" olması gerektiğine kesinlikle katılmıyorum - oldukça objektif olabilirler - bu, her birinin güçlü noktalara sahip olacağı ve geri çekileceği favori evcil hayvan dillerinin performansını karşılaştırmaya çalışmak gibi bir şey değildir. Bu, derleyicilerin bizi ne kadar uzağa götürebileceğini ve hangi noktadan devralmanın daha iyi olduğunu anlamak meselesidir.
jsbueno

21
Kariyerimin başlarında bir yazılım şirketinde çok fazla C ve anabilgisayar montajcısı yazıyordum. Akranlarımdan biri "montajcı safçı" dediğim şeydi (her şey montajcı olmalı), bu yüzden bahse girerim, C'de montajcıya yazabileceğinden daha hızlı koşan belirli bir rutin yazabilirim. Kazandım. Ama üstesinden gelmek için, kazandıktan sonra, ona ikinci bir bahis istediğimi söyledim - montajcıda, önceki bahiste onu yenen C programından daha hızlı bir şey yazabileceğimi söyledim. Bunu da kazandım, çoğunun programcının beceri ve yeteneğine her şeyden daha fazla geldiğini kanıtlayın.
Valerie R

3
Beyninizde bir -O3bayrak yoksa, muhtemelen C derleyicisine optimizasyon bırakmaktan daha iyisiniz :-)
paxdiablo

Yanıtlar:


272

İşte gerçek bir dünya örneği: Sabit nokta eski derleyicilerde çoğalır.

Bunlar sadece kayan nokta olmayan cihazlarda kullanışlı değildir, tahmin edilebilir bir hata ile size 32 bit hassasiyet verdiklerinden hassaslık konusunda parlarlar (şamandıra sadece 23 bit vardır ve hassas kaybı tahmin etmek daha zordur). mesela, muntazam yakın göreli hassasiyet ( ) yerine, tüm aralık boyunca muntazam mutlak hassasiyet ( ).float


Modern derleyiciler bu sabit nokta örneğini güzelce optimize eder, bu nedenle derleyiciye özgü koda ihtiyaç duyan daha modern örnekler için bkz.


C'nin tam çarpma işleci yoktur (N bit girişlerinden 2N bit sonucu). C olarak ifade etmenin olağan yolu, girdileri daha geniş bir türe dökmek ve derleyicinin girişlerin üst bitlerinin ilginç olmadığını fark etmesini ummaktır:

// on a 32-bit machine, int can hold 32-bit fixed-point integers.
int inline FixedPointMul (int a, int b)
{
  long long a_long = a; // cast to 64 bit.

  long long product = a_long * b; // perform multiplication

  return (int) (product >> 16);  // shift by the fixed point bias
}

Bu kodla ilgili sorun, doğrudan C dilinde ifade edilemeyen bir şey yapmamızdır. İki 32 bit sayıyı çarpmak ve orta bit 32 biti döndürdüğümüz 64 bit sonucu elde etmek istiyoruz. Bununla birlikte, C'de bu çarpma mevcut değildir. Yapabileceğiniz tek şey tamsayıları 64 bit'e yükseltmek ve 64 * 64 = 64 çarpımı yapmaktır.

x86 (ve ARM, MIPS ve diğerleri) çarpmayı tek bir komutla yapabilir. Bazı derleyiciler bu gerçeği yoksaymak ve çarpma yapmak için bir çalışma zamanı kitaplığı işlevini çağıran kod üretmek için kullanılır. 16'ya geçiş genellikle bir kütüphane rutini tarafından da yapılır (ayrıca x86 bu değişiklikleri yapabilir).

Bu yüzden sadece bir çarpma için bir veya iki kütüphane çağrısı kaldı. Bunun ciddi sonuçları var. Vardiya yavaşlamakla kalmaz, kayıtlar işlev çağrıları boyunca korunmalıdır ve satır içi ve kod çözme işlemlerine de yardımcı olmaz.

Aynı kodu (satır içi) birleştiricide yeniden yazarsanız, önemli bir hız artışı elde edebilirsiniz.

Buna ek olarak: ASM kullanmak sorunu çözmenin en iyi yolu değildir. Çoğu derleyici, C cinsinden ifade edemiyorsanız, bazı derleyici talimatlarını içsel formda kullanmanıza izin verir.

İçsel özellikleri kullanarak, işlevi C-derleyicisinin olup biteni anlama şansına sahip olacak şekilde yeniden yazabilirsiniz. Bu, kodun satır içine alınmasına, kayıt tahsisine izin verir, ortak alt ifade eliminasyonu ve sabit yayılım da yapılabilir. Elle yazılmış montajcı kodu üzerinde bu şekilde büyük bir performans artışı elde edersiniz .

Referans için: VS.NET derleyicisi için sabit noktalı mul için sonuç:

int inline FixedPointMul (int a, int b)
{
    return (int) __ll_rshift(__emul(a,b),16);
}

Sabit nokta bölümlerinin performans farkı daha da büyüktür. Birkaç asm satırı yazarak bölüm ağır sabit nokta kodu için faktör 10'a kadar iyileştirmeler yaptım.


Visual C ++ 2013 kullanarak her iki yol için aynı derleme kodu verir.

2007'den gelen gcc4.1 de saf C versiyonunu güzel bir şekilde optimize ediyor. (Godbolt derleyici gezgininde gcc'nin daha önceki herhangi bir sürümü yüklü değildir, ancak muhtemelen eski GCC sürümleri bile bunu intrinsics olmadan yapabilir.)

Godbolt derleyici gezgininde x86 (32 bit) için kaynak + asm ve ARM'ye bakın . (Ne yazık ki basit saf C sürümünden kötü kod üretecek kadar eski derleyici yok.)


Modern CPU'lar C operatörleri yoktur şeyler yapabilirsiniz hiç gibi popcntveya bit tarama ilk veya son ayarlanan biraz bulmak için . (POSIX'in bir ffs()işlevi vardır, ancak anlambilimi x86 bsf/ ile eşleşmez bsr. Bkz. Https://en.wikipedia.org/wiki/Find_first_set ).

Bazı derleyiciler bazen bir tamsayıdaki set bitlerinin sayısını sayan bir döngüyü tanıyabilir ve bunu bir popcntkomut için derleyebilir (derleme zamanında etkinleştirildiyse), ancak __builtin_popcntGNU C'de veya yalnızca SSE4.2 ile hedefleme donanımı: _mm_popcnt_u32from<immintrin.h> .

Veya C ++ ile, a atayın std::bitset<32>ve kullanın .count(). (Bu dil portably her zaman doğru bir şey derlemek olacak şekilde, standart kütüphanesinde yoluyla popcount optimize edilmiş bir uygulama ortaya çıkarmak için bir yol bulmuş bir durumdur ve ne olursa olsun hedef desteklerin yararlanabilir.) Ayrıca bkz https : //en.wikipedia.org/wiki/Hamming_weight#Language_support .

Benzer şekilde, onu içeren bazı C uygulamalarında (endian dönüşümü için 32 bitlik bayt takas) ntohlderleyebilir bswap.


İçsel veya elle yazılmış asm için bir başka önemli alan, SIMD talimatları ile manuel vektörleştirmedir. Derleyiciler, gibi basit döngülerle kötü değildir dst[i] += src[i] * 10.0;, ancak işler daha karmaşık hale geldiğinde genellikle kötü yapar veya hiç otomatik vektörleştirmez. Örneğin, SIMD kullanarak atoi nasıl uygulanır? derleyici tarafından skaler koddan otomatik olarak oluşturulur.


6
{X = c% d; y = c / d;}, derleyiciler bunu tek bir div veya idiv yapacak kadar zeki mi?
Jens Björnhager

4
Aslında, iyi bir derleyici ilk fonksiyondan en uygun kodu üretecektir. Kaynak kodun kendinden veya satır içi montajdan kesinlikle yarar görmemesi, yapılacak en iyi şey değildir.
slacker

65
Merhaba Slacker, sanırım daha önce zaman açısından kritik kod üzerinde çalışmak zorunda kalmadınız ... satır içi montaj * büyük bir fark yaratabilir. Ayrıca derleyici için bir içsel C'deki normal aritmetik ile aynıdır. İçseldeki nokta budur. Onlar dezavantajları ile uğraşmak zorunda kalmadan bir mimari özelliği kullanmanızı sağlar.
Nils Pipenbrinck

6
@slacker Aslında, buradaki kod oldukça okunabilir: satır içi kod, yöntem imzasını okurken hemen anlaşılamayan tek bir işlem yapar. Belirsiz bir talimat kullanıldığında kodun okunabilirliği yalnızca yavaşça kaybedildi. Burada önemli olan, sadece bir tane açıkça tanımlanabilir işlem yapan bir yöntemimiz var ve bu atomik fonksiyonları okunabilir kod üretmenin en iyi yolu bu. Bu arada, / * (a * b) >> 16 * / gibi küçük bir yorum hemen açıklanamaz.
Dereckson

5
Adil olmak gerekirse, bu örnek kötü bir örnek, en azından bugün. C derleyicileri, dil doğrudan sunmasa bile 32x32 -> 64 çarpımını uzun zamandır yapabiliyorlar: 32 bit argümanları 64 bit'e dönüştürdüğünüzde ve daha sonra çarptığınızda, tam bir 64-bit çarpma yapın, ancak 32x32 -> 64 iyi olur. Kontrol ettim ve mevcut sürümlerindeki tüm clang, gcc ve MSVC bu doğru olsun . Bu yeni değil - derleyici çıktısına baktığımı ve bunu on yıl önce fark ettiğimi hatırlıyorum.
BeeOnRope

143

Yıllar önce birisine C'de programlamayı öğretiyordum. Egzersiz bir grafiği 90 derece döndürmekti. Tamamlanması birkaç dakika süren bir çözümle geri geldi, çünkü çarpanları ve bölmeleri kullanıyor.

Ona bit kaydırmalarını kullanarak sorunu nasıl yeniden ayarlayacağımı gösterdim ve işlem süresi, sahip olmadığı optimize edici olmayan derleyicide yaklaşık 30 saniyeye düştü.

Ben sadece bir optimize edici derleyici vardı ve aynı kod <5 saniye içinde grafik döndürülmüş. Derleyicinin ürettiği montaj koduna baktım ve gördüklerimden oraya karar verdim ve daha sonra yazmacı montaj günlerimin bittiğini gördüm.


3
Evet, tek bitlik tek renkli bir sistemdi, özellikle Atari ST'nin tek renkli görüntü bloklarıydı.
lilburne

16
Optimize edici derleyici orijinal programı veya sürümünüzü derledi mi?
Thorbjørn Ravn Andersen

Hangi işlemcide? 8086'da, 8x8 döndürme için en uygun kodun, add di,di / adc al,al / add di,di / adc ah,ahsekiz 8 bitlik kayıtların tümü için SI, tekrar vb. Kullanarak 16 bit veri ile DI'yi yüklemesini beklerim , daha sonra 8 yazıcının hepsini tekrar yapar ve ardından tüm prosedürü üç tekrar eder. ve sonunda ax / bx / cx / dx ile dört kelime kaydedin. Bir montajcı buna yaklaşamaz.
supercat

1
Bir derleyicinin 8x8 döndürme için en uygun kodun bir veya iki faktörüne girebileceği herhangi bir platformu gerçekten düşünemiyorum.
supercat

65

Derleyici kayan nokta kodunu her gördüğünde, eski bir kötü derleyici kullanıyorsanız elle yazılmış bir sürüm daha hızlı olacaktır. ( 2019 güncellemesi: Bu, modern derleyiciler için genel olarak doğru değildir.Özellikle x87 dışında bir şey derlerken; derleyiciler, skaler matematik için SSE2 veya AVX veya x87'lerin aksine düz FP kayıt setine sahip x86 olmayan herhangi bir zamanla daha kolay zaman geçirir kayıt yığını.)

Birincil neden, derleyicinin sağlam optimizasyon yapamamasıdır. Konu hakkında bir tartışma için MSDN'deki bu makaleye bakın . Derleme sürümünün C sürümünün (VS2K5 ile derlenmiş) iki katı hız olduğu bir örnek:

#include "stdafx.h"
#include <windows.h>

float KahanSum(const float *data, int n)
{
   float sum = 0.0f, C = 0.0f, Y, T;

   for (int i = 0 ; i < n ; ++i) {
      Y = *data++ - C;
      T = sum + Y;
      C = T - sum - Y;
      sum = T;
   }

   return sum;
}

float AsmSum(const float *data, int n)
{
  float result = 0.0f;

  _asm
  {
    mov esi,data
    mov ecx,n
    fldz
    fldz
l1:
    fsubr [esi]
    add esi,4
    fld st(0)
    fadd st(0),st(2)
    fld st(0)
    fsub st(0),st(3)
    fsub st(0),st(2)
    fstp st(2)
    fstp st(2)
    loop l1
    fstp result
    fstp result
  }

  return result;
}

int main (int, char **)
{
  int count = 1000000;

  float *source = new float [count];

  for (int i = 0 ; i < count ; ++i) {
    source [i] = static_cast <float> (rand ()) / static_cast <float> (RAND_MAX);
  }

  LARGE_INTEGER start, mid, end;

  float sum1 = 0.0f, sum2 = 0.0f;

  QueryPerformanceCounter (&start);

  sum1 = KahanSum (source, count);

  QueryPerformanceCounter (&mid);

  sum2 = AsmSum (source, count);

  QueryPerformanceCounter (&end);

  cout << "  C code: " << sum1 << " in " << (mid.QuadPart - start.QuadPart) << endl;
  cout << "asm code: " << sum2 << " in " << (end.QuadPart - mid.QuadPart) << endl;

  return 0;
}

Ve bilgisayarımdan bazı sayılar varsayılan sürüm derlemesi çalıştırıyor * :

  C code: 500137 in 103884668
asm code: 500137 in 52129147

İlginin dışında, bir dec / jnz ile döngüyü değiştirdim ve zamanlamalarda hiçbir fark yaratmadı - bazen daha hızlı, bazen daha yavaş. Bellek sınırlı yönü diğer optimizasyonları cüce sanırım. (Editörün notu: FP gecikmesi darboğazı ekstra maliyetini gizlemek için yeterlidir loop. Tek / çift elemanlar için iki Kahan toplamı yapmak ve sonunda bunları eklemek belki 2 kat hızlandırabilir. )

Hata! Kodun biraz farklı bir sürümünü çalıştırıyordum ve sayıları yanlış yoldan çıktı (yani C daha hızlıydı!). Sonuçlar düzeltildi ve güncellendi.


20
Veya GCC'de, derleyiciyi kayan nokta optimizasyonu (sonsuzluk veya NaN'lerle hiçbir şey yapmayacağınıza söz verdiğiniz sürece) bayrağını kullanarak çözebilirsiniz -ffast-math. -OfastŞu anda eşdeğer bir optimizasyon düzeyine sahiptirler -O3 -ffast-math, ancak gelecekte köşe vakalarında (IEEE NaN'lere dayanan kod gibi) yanlış kod oluşturmaya neden olabilecek daha fazla optimizasyon içerebilir.
David Stone

2
Evet, şamandıralar değişmeli değildir, derleyici yazdıklarınızı, temelde @DavidStone'un söylediklerini TAMAMEN yapmalıdır.
Alec Teal

2
SSE matematiğini denediniz mi? Performans, MS'in x86_64'te x87'yi tamamen terk etmesinin ve x86'da 80-bit uzunluğunda iki katını bırakmasının nedenlerinden biriydi
phuclv

4
@Praxeolitic: FP add değişmeli ( a+b == b+a), ancak ilişkisel değil (işlemlerin yeniden sıralanması , bu nedenle ara maddelerin yuvarlanması farklıdır). re: bu kod: Ben düşünmeden x87 ve bir looptalimat hızlı asm çok harika bir gösteri olduğunu sanmıyorum . loopGörünüşe göre aslında FP gecikmesi nedeniyle bir darboğaz değil. FP operasyonlarını boru hattına alıp almadığından emin değilim; x87 insanların okuması zordur. fstp resultsSondaki iki insns net bir şekilde optimum değildir. Yığındaki ekstra sonucu patlatmak mağaza dışında daha iyi olur. Gibi fstp st(0)IIRC.
Peter Cordes

2
@PeterCordes: Ekleme değişmeli yapmanın ilginç bir sonucu, 0 + x ve x + 0'ın birbirine eşit olmasına rağmen her ikisinin de x'e eşdeğer olmamasıdır.
supercat

58

Belirli bir örnek veya profil oluşturucu kanıtı vermeden, derleyiciden daha fazlasını bildiğinizde derleyiciden daha iyi bir derleyici yazabilirsiniz.

Genel durumda, modern bir C derleyicisi söz konusu kodun nasıl optimize edileceği hakkında çok daha fazla şey bilir: işlemci boru hattının nasıl çalıştığını bilir, talimatları bir insanın yapabileceğinden daha hızlı yeniden sıralamaya çalışabilir ve benzeri - temelde aynı bir bilgisayar boardgames, vb. için en iyi insan oyuncu kadar iyi veya daha iyi olması, çünkü sadece sorun alanı içinde çoğu insandan daha hızlı arama yapabilirsiniz. Teorik olarak belirli bir durumda bilgisayar kadar iyi performans gösterebilmenize rağmen, kesinlikle aynı hızda yapamazsınız, bu da birkaç durumdan daha fazla olanaksız kılar (yani, yazmaya çalıştığınızda derleyici kesinlikle daha iyi performans gösterecektir) birkaç rutinden daha fazla).

Öte yandan, derleyicinin çok fazla bilgiye sahip olmadığı durumlar vardır - öncelikle derleyicinin bilgisi olmayan farklı harici donanım formlarıyla çalışırken söyleyebilirim. Birincil örnek muhtemelen, montajcının bir insanın söz konusu donanımı yakından tanımasıyla birleştiği cihaz sürücüleri, C derleyicisinin yapabileceğinden daha iyi sonuçlar verebilir.

Diğerleri, yukarıdaki paragrafta bahsettiğim özel amaçlı talimatlardan bahsettiler - derleyicinin herhangi bir bilgiye sahip olmadığı veya hiç bilgisi olmayan talimatlar, bir insanın daha hızlı kod yazmasını mümkün kıldı.


Genel olarak, bu ifade doğrudur. Derleyici DWIW için en iyisini yapar, ancak bazı durumlarda el kodlama montajcısı gerçek zamanlı performans bir zorunluluk olduğunda işi yapar.
spoulson

1
@Liedman: "talimatları bir insanın yapabileceğinden daha hızlı yeniden sıralamaya çalışabilir". OCaml hızlı olduğu için biliniyor ve şaşırtıcı bir şekilde yerel kod derleyicisi ocamloptx86'da komut programlamasını atlıyor ve bunun yerine CPU'ya bırakıyor çünkü çalışma zamanında daha etkili bir şekilde yeniden sıralayabiliyor.
Jon Harrop

1
Modern derleyiciler çok şey yapıyor ve elle yapmak çok uzun sürecek, ancak mükemmel bir yere yakın değiller. Gcc veya llvm hata izleyicilerini "cevapsız optimizasyon" hataları için arayın. Çok var. Ayrıca, asm'de yazarken, bir derleyicinin kanıtlaması zor olan "bu girdi negatif olamaz" gibi ön koşullardan daha kolay yararlanabilirsiniz.
Peter Cordes

48

İşimde montajı bilmem ve kullanmamın üç nedeni var. Önem sırasına göre:

  1. Hata ayıklama - Sık sık hata veya eksik belge içeren kütüphane kodu alıyorum. Montaj seviyesinde adım atarak ne yaptığını anlıyorum. Bunu haftada bir kez yapmak zorundayım. Ayrıca, gözlerimin C / C ++ / C #'daki deyimsel hatayı fark etmediği sorunları ayıklamak için bir araç olarak kullanıyorum. Meclise bakmak bunu aşıyor.

  2. Optimizasyon - derleyici optimizasyonda oldukça iyidir, ancak çoğundan daha farklı bir basketbol sahasında oynuyorum. Genellikle şöyle görünen kodla başlayan görüntü işleme kodunu yazarım:

    for (int y=0; y < imageHeight; y++) {
        for (int x=0; x < imageWidth; x++) {
           // do something
        }
    }

    "bir şey yapın kısmı" tipik olarak birkaç milyon kez (yani 3 ile 30 arasında) gerçekleşir. Bu "bir şey yap" aşamasındaki döngüleri kazıyarak, performans kazançları büyük ölçüde artar. Ben genellikle orada başlamıyorum - Ben genellikle ilk çalışmak için kod yazarak, daha sonra doğal olarak daha iyi (daha iyi algoritma, döngü daha az yük vb) C yeniden refactor için elimden geleni yapacağım. Neler olup bittiğini görmek için genellikle montaj okumam ve nadiren yazmam gerekir. Bunu belki iki ya da üç ayda bir yapıyorum.

  3. bir şey yapmak bana izin vermeyecek. Bunlar - işlemci mimarisini ve özel işlemci özelliklerini almak, CPU'da olmayan bayraklara erişmek (adam, gerçekten C'nin taşıma bayrağına erişmesini diliyorum), vb. Bunu belki yılda bir veya iki yıl yapıyorum.


Döngülerini döşemedin mi? :-)
Jon Harrop

1
@plinth: "kazıma çevrimleri" ne demek?
lang2

@ lang2: bu, iç döngüde mümkün olduğunca fazla gereksiz zamandan kurtulmak anlamına gelir - derleyicinin çekmeyi başaramadığı herhangi bir şey, bu da bir döngü yapmak için bir döngüden çarpımı kaldırmak için cebir kullanmayı içerebilir. iç, vb
kaide

1
Verilerin üzerinden yalnızca bir geçiş yapıyorsanız, döngü döşemesi gerekli görünmemektedir.
James

@ JamesM.Lay: Her öğeye yalnızca bir kez dokunursanız, daha iyi bir geçiş sırası size uzamsal yerellik verebilir. (örneğin, önbellek satırı başına bir öğe kullanarak bir matrisin sütunlarını aşağı döngü yapmak yerine dokunduğunuz bir önbellek satırının tüm baytlarını kullanın.)
Peter Cordes

42

Sadece bazı özel amaçlı talimat setlerini kullanırken derleyici desteklemez.

Birden fazla boru hattı ve tahmini dallanma ile modern bir CPU'nun hesaplama gücünü en üst düzeye çıkarmak için, montaj programını a) bir insanın yazması neredeyse imkansız b) daha da imkansız hale getirecek şekilde yapılandırmanız gerekir.

Ayrıca, daha iyi algoritmalar, veri yapıları ve bellek yönetimi, montajda yapabileceğiniz mikro optimizasyonlardan en azından daha büyük bir performans sırası sağlayacaktır.


4
+1, son cümle bu tartışmaya dahil olmasa da, montajcının ancak algoritma vb. Tüm olası iyileştirmeler gerçekleştirildikten sonra devreye girdiğini varsayabiliriz.
mghie

18
@Matt: Elle yazılmış ASM, berbat satıcı derleyici desteğine sahip EE'nin bazı küçük işlemcilerinde genellikle çok daha iyi.
Zan Lynx

5
"Sadece bazı özel amaçlı talimat setleri kullanıldığında" ?? Muhtemelen daha önce hiç el ile optimize edilmiş bir asm kodu yazmadınız. Üzerinde çalıştığınız mimarinin orta düzeyde samimi bir bilgisi, derleyicinizden daha iyi bir kod (boyut ve hız) üretmeniz için iyi bir şans sağlar. Açıkçası, mghie'nin yorumladığı gibi, her zaman probleminiz için gelebileceğiniz en iyi algoları kodlamaya başlarsınız. Çok iyi derleyiciler için bile, C kodunuzu derleyiciyi en iyi derlenmiş koda götürecek şekilde yazmanız gerekir. Aksi takdirde, oluşturulan kod en iyi düzeyde olacaktır.
ysap

2
@ysap - gerçek dünya kullanımındaki gerçek bilgisayarlarda (küçük güçsüz gömülü yongalar değil), "en uygun" kod daha hızlı olmayacaktır, çünkü herhangi bir büyük veri kümesi için performansınız bellek erişimi ve sayfa hataları ile sınırlı olacaktır ( ve büyük bir veri kümeniz yoksa, bu her iki şekilde de hızlı olacaktır ve bunu optimize etmenin bir anlamı yoktur) - o günlerde çoğunlukla C # (hatta c) de çalışıyorum ve sıkıştırma bellek yöneticisinden performans kazançları- çöp toplama, sıkıştırma ve JIT derleme yükünü tartar.
Nir

4
Bir yapabileceği derleyiciler (özellikle. JIT) belirten +1 iyi , insanlardan daha işi eğer onlar üzerinde çalıştırılır donanım için optimize edilmiştir.
Sebastian

38

C, 8-bit, 16-bit, 32-bit, 64-bit verilerin düşük seviyeli manipülasyonuna "yakın" olsa da, C tarafından desteklenmeyen ve bazı montaj talimatlarında zarif bir şekilde gerçekleştirilebilen birkaç matematik işlemi vardır. kümeleri:

  1. Sabit noktalı çarpma: İki 16 bitlik sayının çarpımı 32 bitlik bir sayıdır. Ancak C'deki kurallar, iki 16 bitlik sayının çarpımının 16 bitlik bir sayı olduğunu ve iki 32 bitlik sayının çarpımının 32 bitlik bir sayı olduğunu söylüyor - her iki durumda da alt yarısı. İsterseniz üst çarpma çarpma bir 16x16 veya 32x32 yarısını, sen derleyici ile oyun oyunları var. Genel yöntem, gerekenden daha büyük bir bit genişliğine döküm yapmak, çarpmak, aşağı kaydırmak ve geri atmaktır:

    int16_t x, y;
    // int16_t is a typedef for "short"
    // set x and y to something
    int16_t prod = (int16_t)(((int32_t)x*y)>>16);`

    Bu durumda derleyici, sadece 16x16'nın üst yarısını elde etmeye çalıştığınızı ve makinenin doğal 16x16multiply ile doğru şeyi yapmaya çalıştığınızı bilecek kadar akıllı olabilir. Veya aptalca olabilir ve 32x32 çarpımını yapmak için bir kütüphane çağrısı gerektirebilir, çünkü bu sadece 16 bit ürüne ihtiyacınız vardır - ancak C standardı size kendinizi ifade etmenin hiçbir yolunu vermez.

  2. Bazı bit kaydırma işlemleri (döndürme / taşıma):

    // 256-bit array shifted right in its entirety:
    uint8_t x[32];
    for (int i = 32; --i > 0; )
    {
       x[i] = (x[i] >> 1) | (x[i-1] << 7);
    }
    x[0] >>= 1;

    Bu C'de çok yetersiz değil, ama yine de, derleyici ne yaptığınızı anlayacak kadar akıllı değilse, çok sayıda "gereksiz" iş yapacak. Birçok montaj talimatı seti taşıma kaydındaki sonuçla birlikte sola / sağa döndürmenize veya kaydırmanıza izin verir, böylece 34 talimatta yukarıdakileri gerçekleştirebilirsiniz: dizinin başına bir işaretçi yükleyin, taşımayı temizleyin ve 32 8- gerçekleştirin işaretçide otomatik artış kullanarak, bit sağa kaydırır.

    Başka bir örnek olarak, montajda zarif bir şekilde gerçekleştirilen doğrusal geri bildirim kaydırma kayıtları (LFSR) vardır: Bir parça N bit (8, 16, 32, 64, 128, vb.) Alın, her şeyi 1 sağa kaydırın (yukarıya bakın) algoritması), sonuçta elde edilen taşıma 1 ise, polinomu temsil eden bir bit deseninde XOR olur.

Bunu söyledikten sonra, ciddi performans kısıtlamaları olmadıkça bu tekniklere başvurmam. Diğerlerinin söylediği gibi, montajı belgelemek / hata ayıklamak / test etmek / korumak C kodundan daha zordur: performans kazancı bazı ciddi maliyetlerle gelir.

edit: 3. Overflow algılama montaj mümkündür (gerçekten C yapamaz), bu bazı algoritmaları çok daha kolay hale getirir.


23

Kısa cevap? Ara sıra.

Teknik olarak her soyutlamanın bir maliyeti vardır ve programlama dili CPU'nun nasıl çalıştığı için bir soyutlamadır. Ancak C çok yakın. Yıllar önce UNIX hesabımda oturum açtığımda ve aşağıdaki servet mesajını aldığımda (bu tür şeyler popüler olduğunda) kahkaha attığımı hatırlıyorum:

C Programlama Dili - Montaj dilinin esnekliğini montaj dilinin gücü ile birleştiren bir dildir.

Komik çünkü doğru: C portatif montaj dili gibidir.

Derleme dilinin sadece yazdığınız halde çalıştığını belirtmek gerekir. Bununla birlikte, C ile oluşturduğu montaj dili arasında bir derleyici vardır ve bu son derece önemlidir çünkü C kodunuzun ne kadar hızlı olduğunu, derleyicinizin ne kadar iyi olduğu ile ilgisi vardır.

Gcc sahneye geldiğinde, onu bu kadar popüler yapan şeylerden biri, çoğu ticari UNIX lezzeti ile gönderilen C derleyicilerinden çok daha iyi olmasıydı. Sadece ANSI C (bu K&R C çöplerinden hiçbiri) değil, daha sağlam ve tipik olarak daha iyi (daha hızlı) kod üretti. Her zaman değil, sık sık.

Tüm bunları size söylüyorum çünkü C ve montajcının hızı hakkında battaniye kuralı yok çünkü C için nesnel bir standart yok.

Benzer şekilde, birleştirici hangi işlemciyi çalıştırdığınıza, sistem spesifikasyonunuza, hangi komut setini kullandığınıza ve benzerlerine bağlı olarak çok değişir. Tarihsel olarak iki CPU mimarisi ailesi vardı: CISC ve RISC. CISC'deki en büyük oyuncu Intel x86 mimarisi (ve talimat seti) idi ve hala da öyle. RISC, UNIX dünyasına egemen oldu (MIPS6000, Alpha, Sparc vb.). CISC, kalpler ve zihinler için savaşı kazandı.

Her neyse, genç bir geliştiriciyken popüler bilgelik, elle yazılmış x86'nın C'den çok daha hızlı olabileceğiydi, çünkü mimarinin çalışma şekli, bunu yapan bir insandan yararlanan bir karmaşıklığa sahipti. Öte yandan RISC derleyiciler için tasarlanmış görünüyordu bu yüzden kimse (biliyordum) Sparc montajcı yazdı. Eminim böyle insanlar vardı ama şüphesiz ikisi de delirmiş ve kurumsallaşmıştır.

Komut setleri, aynı işlemci ailesinde bile önemli bir noktadır. Bazı Intel işlemcilerin SSE'den SSE4'e kadar uzantıları vardır. AMD'nin kendi SIMD talimatları vardı. C gibi bir programlama dilinin yararı, birinin kütüphanesini yazabilmesiydi, böylece hangi işlemci üzerinde çalıştığınız için optimize edildi. Montajcıda zor bir işti.

Montajcıda hiçbir derleyicinin yapamayacağı optimizasyonlar vardır ve iyi yazılmış bir montajcı algoritması C eşdeğerinden daha hızlı veya daha hızlı olacaktır. Daha büyük soru şu: buna değer mi?

Sonuçta montajcı zamanının bir ürünü olmasına rağmen ve CPU döngülerinin pahalı olduğu bir zamanda daha popülerdi. Günümüzde (Intel Atom) üretimi 5-10 $ 'a mal olan bir işlemci herkesin isteyebileceği hemen hemen her şeyi yapabilir. Bugünlerde montajcı yazmanın tek gerçek nedeni, bir işletim sisteminin bazı bölümleri (Linux çekirdeğinin büyük çoğunluğu C olarak yazılsa bile), aygıt sürücüleri, muhtemelen gömülü aygıtlar (C oraya hakim olma eğiliminde olsa da) ve benzeri). Ya da sadece tekmeler için (bu biraz mazoşist).


Acorn makinelerinde (90'ların başlarında) ARM montajcısını tercih edilen dil olarak kullanan birçok kişi vardı. IIRC, küçük riskli talimat setinin daha kolay ve eğlenceli hale getirdiğini söylediler. Ama sanırım C derleyicisi Acorn için geç bir varıştı ve C ++ derleyicisi hiç bitmedi.
Andrew M

3
“... çünkü C için öznel bir standart yok.” Amaç demek istiyorsun .
Thomas

@AndrewM: Evet, yaklaşık 10 yıl boyunca BASIC ve ARM derleyicisine karışık dilde uygulamalar yazdım. O zamanlar C'yi öğrendim ama çok kullanışlı değildi çünkü montajcı ve daha yavaş olduğu kadar hantaldı. Norcroft bazı harika optimizasyonlar yaptı, ancak koşullu talimat setinin günün derleyicileri için bir sorun olduğunu düşünüyorum.
Jon Harrop

1
@AndrewM: aslında ARM aslında geriye doğru yapılan bir çeşit RISC. Diğer RISC ISA'ları bir derleyicinin kullanacağı şeyden başlayarak tasarlanmıştır. ARM ISA, CPU'nun sağladığı şeyden başlayarak tasarlanmış gibi görünüyor (namlu kaydırma, durum bayrakları → bunları her talimatta gösterelim).
ninjalj

16

Artık uygulayamayacağınız ancak inek zevkiniz için kullanılabilecek bir kullanım örneği: Amiga'da CPU ve grafik / ses yongaları belirli bir RAM alanına (özel olan ilk 2MB RAM) erişmek için savaşacaklardı. Bu nedenle, yalnızca 2MB RAM'iniz (veya daha az) olduğunda, karmaşık grafiklerin yanı sıra ses çalmak CPU'nun performansını öldürür.

Montajcıda, kodunuzu akıllı bir şekilde serpiştirebilirsiniz, CPU yalnızca grafik / ses yongaları dahili olarak meşgulken (yani veri yolu serbestken) RAM'e erişmeye çalışacaktır. Bu nedenle, talimatlarınızı yeniden sıralayarak, CPU önbelleğinin akıllıca kullanımı, veriyolu zamanlaması, daha yüksek seviyeli bir dil kullanarak mümkün olmayan bazı efektler elde edebilirsiniz, çünkü her komutu zamanlamanız, hatta NOP'ları buraya yerleştirmeniz ve radar birbirlerinin fişleri.

Bu, CPU'nun NOP (İşlem Yok - hiçbir şey yapma) talimatının aslında tüm uygulamanızı daha hızlı çalıştırabilmesinin başka bir nedenidir.

[EDIT] Elbette, teknik belirli bir donanım kurulumuna bağlıdır. Birçok Amiga oyununun daha hızlı CPU'larla başa çıkamamasının ana nedeni buydu: Talimatların zamanlaması kapalıydı.


Amiga'nın yonga setine bağlı olarak daha fazla 512 kB ila 2 MB gibi 16 MB yonga RAM'i yoktu. Ayrıca, tarif ettiğiniz teknikler nedeniyle birçok Amiga oyunu daha hızlı CPU'larla çalışmadı.
bk1e

1
@ bk1e - Amiga çok çeşitli bilgisayar modelleri üretti, Amiga 500 512K koçla birlikte sevk edildi ve benim durumumda 1Meg'e uzatıldı. 128Meg Ram
David Waters

@ bk1e: Düzeltilmiş duruyorum. Hafızam beni başarısız edebilir, ancak çip RAM ilk 24 bit adres alanı (yani 16 MB) ile sınırlı değil miydi? Ve Fast bunun üzerinde mi haritalandı?
Aaron Digulla

@Aaron Digulla: Wikipedia, çip / hızlı / yavaş RAM arasındaki ayrımlar hakkında daha fazla bilgi
içeriyor

@ bk1e: Benim hatam. 68k CPU'nun sadece 24 adres hattı vardı, bu yüzden kafamda 16MB vardı.
Aaron Digulla

15

Cevap olmayan birinci nokta.
İçinde hiç programlamasanız bile, en az bir montajcı komut setini bilmeyi yararlı buluyorum. Bu, programcıların daha fazlasını bilmek ve dolayısıyla daha iyi olmak için hiç bitmeyen arayışının bir parçasıdır. Ayrıca, çerçevelere adım atarken, kaynak kodunuz yok ve en azından neler olduğu hakkında kabaca bir fikriniz var. Ayrıca JavaByteCode ve .Net IL'yi de birleştiriciye benzediğinden anlamanıza yardımcı olur.

Küçük bir kod veya çok fazla zamanınız olduğunda soruyu cevaplamak için. Düşük talaş karmaşıklığı ve bu talaşları hedefleyen derleyicilerdeki zayıf rekabetin dengeyi insanlar lehine çevirebildiği gömülü yongalarda kullanım için en yararlıdır. Ayrıca kısıtlı cihazlar için, derleyiciye yapması talimatını zorlaştıracak şekilde kod boyutu / bellek boyutu / performansı üzerinden işlem yaparsınız. Örneğin, bu kullanıcı eyleminin sık sık çağrılmadığını biliyorum, bu yüzden küçük kod boyutu ve düşük performansa sahip olacağım, ancak benzer görünen bu diğer işlev her saniye kullanılır, böylece daha büyük bir kod boyutu ve daha hızlı performans elde edeceğim. Bu, yetenekli bir montaj programcısının kullanabileceği bir çeşit ticarettir.

Ayrıca, C derlemesini kodlayabileceğiniz ve üretilen Meclisi inceleyebileceğiniz, daha sonra size C kodunu değiştirebileceğiniz veya düzenleyebileceğiniz ve montaj olarak koruyabileceğiniz bir çok orta yol eklemek isterim.

Arkadaşım mikro kontrolörler üzerinde çalışıyor, şu anda küçük elektrik motorlarını kontrol etmek için çipler. Düşük seviye c ve Meclisin bir arada çalışmaktadır. Bir keresinde bana işte iyi bir gün geçirdiğini ve ana döngüyü 48 talimattan 43'e düşürdüğünü söyledi. Ayrıca kod 256k yongasını doldurmak için büyüdü ve iş yeni bir özellik istiyor gibi seçeneklerle karşı karşıya, değil mi?

  1. Mevcut bir özelliği kaldırma
  2. Belki de performans maliyetiyle mevcut özelliklerin bir kısmının veya tamamının boyutunu azaltın.
  3. Daha yüksek maliyet, daha yüksek güç tüketimi ve daha büyük form faktörü ile daha büyük bir çipe geçmeyi savunun.

Daha önce bir portföy veya dil, platform, uygulama türlerine sahip bir ticari geliştirici olarak eklemek istiyorum. Bu konuda edindiğim bilgileri her zaman takdir ettim. Ve bazen hata ayıkladı.

"Montajcıyı neden öğrenmeliyim" sorusunu çok daha fazla cevapladığımı biliyorum ama bunun ne zaman daha hızlı olduğu konusunda daha önemli bir soru olduğunu düşünüyorum.

Şimdi bir kez daha deneyelim Montaj hakkında düşünmelisin

  • düşük seviyeli işletim sistemi işlevi üzerinde çalışma
  • Bir derleyici üzerinde çalışıyor.
  • Son derece sınırlı bir yonga, gömülü sistem vb.

Hangisinin daha hızlı / daha küçük / daha iyi olduğunu görmek için derlemenizi oluşturulan derleyiciyle karşılaştırmayı unutmayın.

David.


4
Küçük yongalardaki gömülü uygulamaları dikkate almak için +1. Burada çok fazla yazılım mühendisi ya gömülü olarak düşünmüyor ya da bunun akıllı bir telefon (32 bit, MB RAM, MB flaş) anlamına geldiğini düşünüyor.
Martin

1
Zamana gömülü uygulamalar harika bir örnektir! Donanım hakkında sınırlı bilgiye sahip oldukları için derleyicilerin tam olarak yararlanamayacağı (ve bazen de kullanmadığı) garip talimatlar (avr sbive hatta gerçekten basit olanlar bile cbi) vardır.
felixphew

15

Kimsenin bunu söylemediğine şaşırdım. strlen()Fonksiyon düzeneği ile yazılmış ise çok daha hızlıdır! C dilinde yapabileceğiniz en iyi şey

int c;
for(c = 0; str[c] != '\0'; c++) {}

montaj sırasında önemli ölçüde hızlandırabilirsiniz:

mov esi, offset string
mov edi, esi
xor ecx, ecx

lp:
mov ax, byte ptr [esi]
cmp al, cl
je  end_1
cmp ah, cl
je end_2
mov bx, byte ptr [esi + 2]
cmp bl, cl
je end_3
cmp bh, cl
je end_4
add esi, 4
jmp lp

end_4:
inc esi

end_3:
inc esi

end_2:
inc esi

end_1:
inc esi

mov ecx, esi
sub ecx, edi

uzunluk ecx cinsindendir. Bu, 4 karakteri karşılaştırır, böylece 4 kat daha hızlıdır. Ve üst düzey eax ve ebx kelimesini kullanmayı düşünün , önceki C rutininden 8 kat daha hızlı olacak !


3
Bu strchr.nfshost.com/optimized_strlen_function ile nasıl karşılaştırılır ?
ninjalj

@ninjalj: onlar aynı şey :) ben C bu şekilde yapılabilir düşündüm vermedi Biraz geliştirilebilir düşünüyorum
BlackBear

C kodundaki her karşılaştırmadan önce hala bitsel bir AND işlemi vardır. Derleyicinin bunu yüksek ve düşük bayt karşılaştırmalarına indirgeyecek kadar akıllı olması mümkündür, ancak bunun üzerine para bahis oynamam. Aslında (word & 0xFEFEFEFF) & (~word + 0x80808080), sözcükteki tüm baytların sıfır olmadığı durumlarda sıfır olan özelliğe dayanan daha hızlı bir döngü algoritması vardır .
user2310967

@MichaWiedenmann true, baltadaki iki karakteri karşılaştırdıktan sonra bx yüklemem gerekiyor. Teşekkür ederim
BlackBear

14

SIMD komutlarını kullanan matris işlemleri, derleyici tarafından üretilen koddan daha hızlıdır.


Bazı derleyiciler (VectorC, doğru hatırlıyorsam) SIMD kodu üretir, bu yüzden bu bile muhtemelen montaj kodunu kullanmak için bir argüman değildir.
OregonGhost

Derleyiciler SSE farkında kod yaratır, böylece argüman doğru değildir
vartec

5
Bu durumların çoğu için montaj yerine SSE intrisiklerini kullanabilirsiniz. Bu, kodunuzu daha taşınabilir hale getirecektir (gcc visual c ++, 64bit, 32bit vb.) Ve kayıt tahsisi yapmanız gerekmez.
Laserallan

1
Tabii ki, ama soru C yerine derleme nerede kullanmalıyım sormadı. C derleyicisi daha iyi bir kod üretmediği zaman söyledi. Doğrudan SSE çağrıları veya satır içi montaj kullanmayan bir C kaynağı olduğunu varsaydım.
Mehrdad Afshari

9
Mehrdad haklı. SSE'yi doğru yapmak derleyici için oldukça zordur ve hatta çoğu derleyici tarafından kullanılmayan bariz (insanlar için) durumlarda bile.
Konrad Rudolph

13

Belirli örnekleri veremem çünkü çok uzun yıllar önceydi, ancak elle yazılmış montajcının herhangi bir derleyiciyi gerçekleştirebileceği birçok durum vardı. Bunun sebepleri:

  • Sözleşmeleri çağırmaktan, kayıtlara argümanlar aktarmaktan sapabilirsiniz.

  • Kayıtların nasıl kullanılacağını dikkatle düşünebilir ve değişkenleri bellekte saklamaktan kaçınabilirsiniz.

  • Atlama tabloları gibi şeyler için, dizini sınırlamak-kontrol etmek zorunda kalabilirsiniz.

Temel olarak, derleyiciler optimize etmek için oldukça iyi bir iş yaparlar ve bu neredeyse her zaman "yeterince iyidir", ancak her bir döngü için çok pahalıya ödediğiniz bazı durumlarda (grafik oluşturma gibi), kısayolları alabilirsiniz çünkü kodu biliyorsunuz , derleyici güvenli tarafta olması gerektiği için yapamadı.

Aslında, bir çizgi çizme veya çokgen doldurma rutini gibi bir rutinin, yığın üzerinde küçük bir makine kodu bloğu oluşturduğu ve sürekli karar vermekten kaçınmak için orada yürüttüğü bazı grafik oluşturma kodlarını duydum. çizgi stili, genişlik, desen vb.

Yani, bir derleyicinin yapmasını istediğim şey benim için iyi bir montaj kodu oluşturmak, ancak çok zeki olmamak ve çoğunlukla bunu yapmak. Aslında, Fortran hakkında nefret ettiğim şeylerden biri, kodu "optimize etme" çabasıyla kodlamayı karıştırmaktır, genellikle önemli bir amacı yoktur.

Genellikle, uygulamalarda performans sorunları olduğunda, savurgan tasarımdan kaynaklanır. Bu günlerde, genel uygulama hayatının bir inç içinde ayarlanmamışsa, hala yeterince hızlı değildi ve tüm zamanını sıkı iç döngülerde geçirmedikçe, montajcıyı asla performans için önermem.

Eklendi: Montaj dilinde yazılmış birçok uygulama gördüm ve C, Pascal, Fortran gibi bir dile göre ana hız avantajı, programcının montajcıda kodlama yaparken çok daha dikkatli olmasıydı. Dilden bağımsız olarak günde yaklaşık 100 satır kod yazacak ve derleyici dilinde 3 veya 400 talimata eşit olacak.


8
+1: "Kongre çağırmaktan sapabilirsiniz". C / C ++ derleyicileri birden fazla değer döndürme emmeye meyillidir. Arayan yığının bir yapı için bitişik bir blok tahsis ettiği ve arayanın doldurması için kendisine bir referans ilettiği sret formunu kullanırlar. Kayıtlarda birden fazla değer döndürmek birkaç kat daha hızlıdır.
Jon Harrop

1
@Jon: C / C ++ derleyicileri, işlev satır içine alındığında bunu iyi yapar (satır içi olmayan işlevler ABI'ye uymak zorundadır, bu C ve C ++ ile bir sınırlama değil bağlantı modelidir)
Ben Voigt

@BenVoigt: İşte bir karşı örnek flyingfrogblog.blogspot.co.uk/2012/04/…
Jon Harrop

2
Orada herhangi bir fonksiyon çağrısı gelmez görüyorum.
Ben Voigt

13

Deneyimlerimden birkaç örnek:

  • C'den erişilemeyen talimatlara erişim Örneğin birçok mimari (x86-64, IA-64, DEC Alpha ve 64 bit MIPS veya PowerPC gibi), 64 bitlik 64 bitlik bir çarpımı destekleyerek 128 bitlik bir sonuç üretir. GCC kısa süre önce bu tür talimatlara erişim sağlayan bir uzantı ekledi, ancak bu montajdan önce. Ve bu talimata erişim, RSA gibi bir şey uygularken 64 bit CPU'larda büyük bir fark yaratabilir - bazen performansta 4 iyileşme faktörü kadar.

  • CPU'ya özel bayraklara erişim. Beni çok ısırmış olan, taşıma bayrağıdır; çok hassasiyetli bir ekleme yaparken, CPU taşıma bitine erişiminiz yoksa, bunun yerine, taşan olup olmadığını görmek için sonucu karşılaştırmanız gerekir, bu da uzuv başına 3-5 daha fazla talimat alır; ve daha da kötüsü, modern süperskalar işlemcilerde performansı öldüren veri erişimi açısından oldukça seri. Arka arkaya binlerce tamsayı işlerken, addc'yi kullanmak büyük bir kazançtır (taşıma bitinde çekişme ile süperskalar sorunlar da vardır, ancak modern CPU'lar bununla oldukça iyi ilgilenir).

  • SIMD. Otovektörleştirici derleyiciler bile sadece nispeten basit durumlar yapabilir, bu nedenle iyi bir SIMD performansı istiyorsanız, maalesef kodu doğrudan yazmak genellikle gereklidir. Tabii ki montaj yerine intrinsics kullanabilirsiniz, ancak intrinsics seviyesine girdikten sonra temelde derleme yazıyorsunuz, sadece derleyiciyi bir kayıt ayırıcı ve (nominal olarak) talimat zamanlayıcı olarak kullanıyorsunuz. (SIMD için intrinsics kullanmaya eğilimliyim çünkü derleyici fonksiyon prologlarını ve benim için ne üretemez, böylece aynı kodu Linux, OS X ve Windows'da işlev çağırma kuralları gibi ABI sorunları ile uğraşmak zorunda kalmadan kullanabilirim, ancak diğer SSE intrinsikleri gerçekten çok hoş değil - Altivec olanlar daha iyi görünüyor, ancak onlarla fazla deneyimim yok).bitslicing AES veya SIMD hata düzeltmesi - algoritmaları analiz edebilen ve böyle bir kod oluşturabilen bir derleyici hayal edilebilir, ancak böyle bir akıllı derleyicinin mevcut olandan en az 30 yıl uzakta olduğu gibi geliyor bana.

Öte yandan, çok çekirdekli makineler ve dağıtılmış sistemler, en büyük performans kazançlarının çoğunu diğer yöne kaydırdı - iç döngülerinizi montajda yazarken ekstra% 20 veya birden fazla çekirdek üzerinde çalıştırarak% 300 veya% 10000 onları bir makine kümesinde çalıştırıyorum. Ve elbette, yüksek seviye optimizasyonların (vadeli işlemler, not, vb. Gibi şeyler) ML veya Scala gibi daha yüksek bir dilde C veya asm'dan daha kolay yapılması ve genellikle çok daha büyük bir performans kazanımı sağlayabilir. Yani, her zaman olduğu gibi, yapılacak ödünleşmeler vardır.


2
@Dennis bu yüzden yazdım 'Elbette montaj yerine intrinsikleri kullanabilirsiniz, ancak intrinsics seviyesine girdikten sonra temel olarak montajı yazıyorsunuz, sadece derleyiciyi bir kayıt ayırıcısı ve (nominal olarak) talimat zamanlayıcısı olarak kullanıyorsunuz.'
Jack Lloyd

Ayrıca, gerçek tabanlı SIMD kodu , montajcıda yazılan koddan daha az okunabilir olma eğilimindedir : Çoğu SIMD kodu, vektörlerdeki verilerin örtülü olarak yeniden yorumlanmasına dayanır, bu da derleyici içsellerinin sağladığı veri türleriyle ilgili bir PITA'dır.
cmaster

10

Bir görüntü milyonlarca pikselden oluşabileceğinden, görüntülerle oynarken olduğu gibi sıkı döngüler. Oturmak ve sınırlı sayıda işlemci kaydından en iyi şekilde nasıl faydalanabileceğinizi bulmak fark yaratabilir. İşte gerçek hayattan bir örnek:

http://danbystrom.se/2008/12/22/optimizing-away-ii/

Daha sonra işlemciler genellikle bir derleyicinin rahatsız edemeyeceği kadar özel bazı ezoterik talimatlara sahiptir, ancak bazen bir montajcı programcısı bunları iyi kullanabilir. Örneğin XLAT talimatını ele alalım. Bir döngüde tablo aramaları yapmanız gerekiyorsa ve tablo 256 bayt ile sınırlıysa gerçekten harika !

Güncelleme: Oh, genel olarak döngülerden bahsettiğimizde en önemli olanı düşünmeye gel: derleyici genellikle ortak durum olacak kaç iterasyon hakkında hiçbir ipucu yok! Yalnızca programcı bir döngünün BİRÇOK kez yineleneceğini ve bu nedenle döngüye bazı ekstra çalışmalarla hazırlanmanın yararlı olacağını veya kurulumun gerçekten yinelemeden daha uzun süreceği birkaç kez yinelenecekse beklenen.


3
Profile yönelik optimizasyon, derleyiciye bir döngünün ne sıklıkta kullanıldığı hakkında bilgi verir.
Zan Lynx

10

Düşündüğünüzden daha çok, C'nin sadece C standartları söylediği için Meclis kodlayıcısının bakış açısından gereksiz gibi görünen şeyleri yapması gerekir.

Örneğin tamsayı tanıtımı. Bir char değişkenini C olarak değiştirmek isterseniz, kodun aslında tek bir bit kaydırma yapması beklenir.

Ancak standartlar, derleyiciyi vardiyadan önce int'e kadar genişletmeye zorlar ve sonucu daha sonra hedef işlemcinin mimarisine bağlı olarak kodu karmaşıklaştıracak şekilde char'a keser.


Küçük mikrolar için kaliteli derleyiciler, yıllarca sonuçların hiçbir zaman anlamlı bir şekilde etkilenemeyeceği durumlarda değerlerin üst kısımlarını işlemekten kaçınabilmiştir. Tanıtım kuralları sorunlara neden olur, ancak çoğu zaman bir derleyicinin hangi köşe vakalarının alakalı olduğunu ve ilgili olmadığını bilmesinin mümkün olmadığı durumlarda.
Supercat

9

Derleyicinin ürettiği demontaja bakmadıysanız, iyi yazılmış C kodunuzun gerçekten hızlı olup olmadığını gerçekten bilmiyorsunuz. Birçok kez ona bakıp "iyi yazılmış" ın öznel olduğunu görürsünüz.

Bu nedenle, şimdiye kadarki en hızlı kodu almak için montajcıya yazmak gerekli değildir, ancak kesinlikle aynı nedenden dolayı montajcıyı bilmeye değer.


2
"Bu yüzden şimdiye kadar en hızlı kodu almak için montajcı yazmak gerekli değil" Eh, bir derleyici önemsiz değildi her durumda en uygun şeyi yapmak görmedim. Deneyimli bir insan hemen hemen her durumda derleyiciden daha iyisini yapabilir. Bu nedenle, "şimdiye kadarki en hızlı kodu" almak için montajcıya yazmak kesinlikle gereklidir.
cmaster - eski haline monica

@cmaster Benim deneyimime göre derleyici çıktı iyi, rastgele. Bazen gerçekten iyi ve optimal ve bazen "bu çöp nasıl yayılmış olabilir".
sharptooth

9

Tüm yanıtları okudum (30'dan fazla) ve basit bir neden bulamadım: Intel® 64 ve IA-32 Mimarlar Optimizasyon Referans Kılavuzunu okuyup uyguladıysanız, montajcı C'den daha hızlıdır , bu yüzden montajın nedeni daha yavaş olması, bu kadar yavaş bir derleme yazan kişilerin Optimizasyon Kılavuzunu okumamasıdır .

Intel 80286'nın eski güzel günlerinde, her bir talimat sabit sayıda CPU döngüsünde yürütüldü, ancak 1995'te piyasaya sürülen Pentium Pro, Karmaşık Boru Hatları: Sipariş Dışı Yürütme ve Kayıt Yeniden Adlandırma kullanarak süperskalar hale geldi. Bundan önce, 1993'te üretilen Pentium'da, U ve V boru hatları vardı: birbirlerine bağımlı olmadılarsa, bir saat döngüsünde iki basit talimat yürütebilecek çift boru hatları; ancak bu, Pentium Pro'da Sıra Dışı Yürütme ve Kayıt Yeniden Adlandırma'nın göründüğünü karşılaştırmak için hiçbir şey değildi ve bugünlerde neredeyse değişmeden kaldı.

Birkaç kelimeyle açıklamak için, en hızlı kod, talimatların önceki sonuçlara bağlı olmadığı yerdir, örneğin her zaman tüm kayıtları temizlemelisiniz (movzx ile) veya add rax, 1bunun yerine kullanmalı veya inc raxönceki bayrak durumuna vb.

Zamanın müsaade etmesi halinde, İnternette bol miktarda bilgi varsa, Sipariş Dışı Yürütme ve Kayıt Yeniden Adlandırma hakkında daha fazla bilgi edinebilirsiniz.

Şube tahmini, yük ve mağaza birimleri sayısı, mikro-operasyonları yürüten kapı sayısı, vb.Gibi diğer önemli konular da vardır, ancak dikkate alınması gereken en önemli şey, Sipariş Dışı Yürütme'dir.

Çoğu insan sadece Sipariş Dışı Yürütme hakkında farkında değildir, bu yüzden 80286 gibi montaj programlarını yazarlar, talimatlarının bağlamdan bağımsız olarak yürütülmesi için sabit bir zaman alacaktır; C derleyicileri, Sipariş Dışı Yürütme işleminin farkındadır ve kodu doğru şekilde oluşturur. Bu yüzden farkında olmayan insanların kodu daha yavaştır, ancak fark ederseniz, kodunuz daha hızlı olacaktır.


8

Bence montajcı daha hızlı olduğunda genel durum, akıllı bir montaj programcısı derleyicinin çıktısına bakıp "bu performans için kritik bir yol ve bunu daha verimli olmak için yazabilirim" der ve o kişi bu montajcıyı değiştirir veya yeniden yazar sıfırdan.


7

Her şey iş yükünüze bağlıdır.

Günlük işlemler için, C ve C ++ gayet iyi, ancak montajın performans göstermesini gerektiren belirli iş yükleri (video içeren herhangi bir dönüşüm (sıkıştırma, açma, görüntü efektleri, vb.) Var.

Ayrıca genellikle bu tür işlemler için ayarlanan CPU'ya özgü yonga seti uzantılarını (MME / MMX / SSE / her neyse) kullanmayı içerir.


6

Her kesmede 192 veya 256 bitte, her 50 mikrosaniyede bir gerçekleşmesi gereken bitlerin aktarılması işlemim var.

Sabit bir haritadan (donanım kısıtlamaları) oluşur. C kullanarak, yaklaşık 10 mikrosaniye sürdü. Bu haritanın belirli özelliklerini, belirli kayıt önbelleğe almayı ve bit yönelimli işlemleri kullanarak bunu Assembler'a çevirdiğimde; gerçekleştirmek 3,5 mikrosaniyeden daha az sürdü.




5

Basit cevap ... Montajı iyi bilen biri (aka onun yanında referansa sahiptir ve her küçük işlemci önbelleği ve boru hattı özelliğinden vb. Yararlanmaktadır) herhangi bir derleyiciden çok daha hızlı kod üretebileceği garanti edilmektedir .

Ancak bu günlerdeki fark tipik uygulamada önemli değil.


1
"Çok fazla zaman ve çaba harcanması" ve "bir bakım kabusu yaratması" demeyi unuttun. Bir meslektaşım OS kodunun performans açısından kritik bir bölümünü optimize etmeye çalışıyordu ve C'de montajdan çok daha fazla çalıştı, çünkü üst düzey değişikliklerin performans etkisini makul bir süre içinde araştırmasına izin verdi.
Haziran'da Artelius

Katılıyorum. Bazen zaman kazanmak ve hızlı bir şekilde geliştirmek için montaj kodu oluşturmak için makrolar ve komut dosyaları kullanırsınız. Bugünlerde çoğu montajcıda makro var; değilse, (oldukça basit RegEx) Perl komut dosyasını kullanarak (basit) bir makro ön işlemcisi yapabilirsiniz.

Bu. Tam. Alan uzmanlarını yenmek için derleyici henüz icat edilmedi.
cmaster - eski haline monica

4

PolyPascal'ın (Turbo Pascal'a kardeş) CP / M-86 sürümünün olasılıklarından biri, "ekrandan çıktıya karakterleri kullan-ekrana-kullan" tesisini, esasen bir makine dili rutini ile değiştirmekti. x ve y ile oraya dizge verildi.

Bu, ekranı eskisinden çok daha hızlı güncellemeye izin verdi!

İkili makine kodu (birkaç yüz bayt) gömmek için oda vardı ve orada da başka şeyler vardı, bu yüzden mümkün olduğunca sıkmak için gerekli oldu.

Ekran 80x25 olduğu için her iki koordinatın da her bir bayta sığabileceği, her ikisinin de iki baytlık bir kelimeye sığabileceği ortaya çıkıyor. Bu, tek bir ekleme her iki değeri aynı anda değiştirebildiğinden, daha az baytta gereken hesaplamaları yapmaya izin verdi.

Bildiğim kadarıyla, bir kayıtta birden fazla değeri birleştirebilecek, üzerinde SIMD talimatları yapabilen ve daha sonra tekrar ayırabilecek C derleyicileri yok (ve makine talimatlarının yine de daha kısa olacağını düşünmüyorum).


4

Montajın daha ünlü snippet'lerinden biri, Michael Abrash'in doku eşleme döngüsünden ( burada ayrıntılı olarak açıklanmıştır ):

add edx,[DeltaVFrac] ; add in dVFrac
sbb ebp,ebp ; store carry
mov [edi],al ; write pixel n
mov al,[esi] ; fetch pixel n+1
add ecx,ebx ; add in dUFrac
adc esi,[4*ebp + UVStepVCarry]; add in steps

Günümüzde çoğu derleyici, gelişmiş CPU'ya özgü talimatları gerçek olarak ifade eder, yani gerçek talimatlara göre derlenen işlevler. MS Visual C ++, MMX, SSE, SSE2, SSE3 ve SSE4 için intrinsikleri destekler, bu nedenle platforma özel talimatlardan yararlanmak için montaja düşme konusunda daha az endişelenmeniz gerekir. Visual C ++, uygun / ARCH ayarıyla hedeflediğiniz gerçek mimariden de yararlanabilir.


Daha da iyisi, bu SSE intrinsikleri Intel tarafından belirlenir, bu yüzden aslında oldukça taşınabilirler.
James

4

Doğru programcı göz önüne alındığında, Assembler programları her zaman C meslektaşlarından daha hızlı yapılabilir (en azından marjinal olarak). Assembler'ın en az bir talimatını alamadığınız bir C programı oluşturmak zor olurdu.


Bu biraz daha doğru olurdu: " Önemsiz bir C programı oluşturmak zor olurdu ..." Alternatif olarak, şöyle diyebilirsiniz: "Burada gerçek bir C programı bulmak zor olurdu ..." , derleyicilerin optimum çıktı ürettiği önemsiz döngüler vardır. Yine de, iyi bir cevap.
cmaster - eski haline monica


4

gcc yaygın olarak kullanılan bir derleyici haline gelmiştir. Optimizasyonları genel olarak iyi değil. Ortalama programcı yazma derleyicisinden çok daha iyi, ama gerçek performans için o kadar iyi değil. Ürettikleri kodda inanılmaz olan derleyiciler var. Genel bir cevap olarak, derleyicinin çıktısına gidip performans için montajcıyı ayarlayabileceğiniz ve / veya rutini sıfırdan yeniden yazabileceğiniz birçok yer olacaktır.


8
GCC son derece akıllı "platformdan bağımsız" optimizasyonlar yapar. Bununla birlikte, belirli talimat setlerini sonuna kadar kullanmak o kadar iyi değildir. Böyle taşınabilir bir derleyici için çok iyi bir iş çıkarır.
09:56

2
kabul. Taşınabilirliği, gelen diller ve çıkan hedefler inanılmaz. Bu taşınabilir olmak, bir dil veya hedefte gerçekten iyi olma yolunda ilerleyebilir. Dolayısıyla, bir insanın daha iyisini yapma fırsatları, belirli bir hedefte belirli bir optimizasyon için vardır.
old_timer

+1: GCC kesinlikle hızlı kod üretme konusunda rekabetçi değil ama taşınabilir olduğundan emin değilim. LLVM taşınabilir ve GCC'lerden 4 kat daha hızlı kod ürettiğini gördüm.
Jon Harrop

GCC'yi tercih ediyorum, çünkü uzun yıllardır kaya gibi sağlam, ayrıca modern bir taşınabilir derleyici çalıştırabilen hemen hemen her platform için mevcut. Ne yazık ki LLVM (Mac OS X / PPC) oluşturamadım, bu yüzden muhtemelen buna geçemeyeceğim. GCC ile ilgili iyi şeylerden biri, GCC'de oluşturulan bir kod yazarsanız, büyük olasılıkla standartlara yakın olursunuz ve hemen hemen her platform için oluşturulabileceğinden emin olacaksınız.

4

Longpoke, sadece bir sınırlama var: zaman. Koddaki her değişikliği optimize etmek ve kayıt ayırmak için zaman harcamak, birkaç sızıntıyı optimize etmek ve ne yapmak için harcamak için kaynaklara sahip değilseniz, derleyici her seferinde kazanacaktır. Kodda yaptığınız değişikliği yeniden derleyin ve ölçün. Gerekirse tekrarlayın.

Ayrıca, üst düzey tarafta çok şey yapabilirsiniz. Ayrıca, sonuçta ortaya çıkan derlemeyi denetlemek GÖSTERGEYE kodun bok olduğunu verebilir, ancak pratikte daha hızlı olacağını düşündüğünüzden daha hızlı çalışacaktır. Misal:

int y = veri [i]; // burada bir şeyler yapın .. call_function (y, ...);

Derleyici verileri okuyacak, yığmak (dökmek) için itecek ve daha sonra yığından okuyacak ve bağımsız değişken olarak geçecektir. Bok gibi mi geliyor? Aslında çok etkili gecikme telafisi olabilir ve daha hızlı çalışma süresi ile sonuçlanabilir.

// optimize edilmiş sürüm call_function (veri [i], ...); // sonuçta o kadar optimize edilmemiş ..

Optimize edilmiş versiyonun fikri, kayıt basıncını düşürmemiz ve dökülmekten kaçınmamızdı. Ama gerçekte, "boktan" versiyonu daha hızlıydı!

Montaj koduna bakmak, sadece talimatlara bakmak ve sonuçlandırmak: daha fazla talimat, daha yavaş, bir yanlış karar olacaktır.

Burada dikkat edilmesi gereken şey: birçok montaj uzmanı çok şey bildiklerini düşünüyor , ama çok az şey biliyor. Kurallar mimariden diğerine de değişir. Örneğin, her zaman en hızlı olan gümüş mermi x86 kodu yoktur. Bu günlerde başparmak kurallarına uymak daha iyidir:

  • bellek yavaş
  • önbellek hızlı
  • önbelleği daha iyi kullanmaya çalışın
  • ne sıklıkla özleyeceksin? gecikme tazminat stratejiniz var mı?
  • tek bir önbellek kaçırma için 10-100 ALU / FPU / SSE talimatı yürütebilirsiniz
  • uygulama mimarisi önemlidir ..
  • .. ama sorun mimaride olmadığında yardımcı olmuyor

Ayrıca, kötü düşünülmüş C / C ++ kodunu sihirli bir şekilde "teorik olarak optimum" koda dönüştüren derleyiciye çok fazla güvenmek arzuludur. Bu düşük seviyede "performans" ı önemsiyorsanız, kullandığınız derleyici ve takım zincirini bilmeniz gerekir.

C / C ++ 'da derleyiciler genellikle alt ifadeleri yeniden sıralamada çok iyi değildir, çünkü fonksiyonlar yeni başlayanlar için yan etkilere sahiptir. İşlevsel diller bu uyarıdan muzdarip değildir, ancak mevcut ekosisteme o kadar iyi uymamaktadır. Derleyici / bağlayıcı / kod üreticisi tarafından işlem sırasının değiştirilmesine izin veren rahat hassasiyet kurallarına izin veren derleyici seçenekleri vardır.

Bu konu biraz çıkmaz bir konu; çoğu için alakalı değil ve geri kalanı zaten ne yaptıklarını biliyorlar.

Her şey şuna bağlı: "ne yaptığınızı anlamak", ne yaptığınızı bilmekten biraz farklı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.