Derleyicinin bazı makine için birleştirici (ve sonuçta makine kodu) üretmesi beklenir ve genellikle C ++ bu makineye sempatik olmaya çalışır.
Alttaki makineye sempatik olmak kabaca anlamına gelir: makinenin hızlı bir şekilde gerçekleştirebileceği işlemlere verimli bir şekilde eşlenecek C ++ kodunun yazılmasını kolaylaştırır. Bu nedenle, donanım platformumuzda hızlı ve "doğal" veri türlerine ve işlemlerine erişim sağlamak istiyoruz.
Somut olarak, belirli bir makine mimarisini düşünün. Şu anki Intel x86 ailesini ele alalım.
Intel® 64 ve IA-32 Mimarlar Yazılım Geliştirici Kılavuzu cilt 1 ( bağlantı ), bölüm 3.4.1 şöyle diyor:
32 bit genel amaçlı kayıt EAX, EBX, ECX, EDX, ESI, EDI, EBP ve ESP, aşağıdaki öğeleri tutmak için sağlanmıştır:
• Mantıksal ve aritmetik işlemler için işlenenler
• Adres hesaplamaları için işlenenler
• Bellek İşaretçileri
Bu nedenle, derleyicinin basit C ++ tamsayı aritmetiğini derlediğinde bu EAX, EBX vb. Kayıtlarını kullanmasını istiyoruz. Bu, bir ilan ettiğimde int
, bu kayıtlarla uyumlu bir şey olması gerektiği anlamına gelir , böylece onları verimli bir şekilde kullanabilirim.
Kayıtlar her zaman aynı boyuttadır (burada, 32 bit), bu yüzden benim int
değişkenlerim de her zaman 32 bit olacaktır. Aynı düzeni (little-endian) kullanacağım, böylece bir kayda bir değişken değeri her yüklediğimde veya bir değişkeni bir kayıtta sakladığımda bir dönüşüm yapmak zorunda kalmam.
Godbolt'u kullanarak derleyicinin bazı önemsiz kodlar için tam olarak ne yaptığını görebiliriz:
int square(int num) {
return num * num;
}
(GCC 8.1 ve -fomit-frame-pointer -O3
basitlik için) aşağıdakileri derler :
square(int):
imul edi, edi
mov eax, edi
ret
bu şu anlama gelir:
int num
parametresi tam olarak boyutu olduğu anlamına kayıt EDI geçirilen ve Intel yerli kayıt için bekliyoruz düzeni edildi. İşlevin hiçbir şeyi dönüştürmesi gerekmez
- çarpma
imul
çok hızlı olan tek bir komuttur ( )
- sonucun geri döndürülmesi sadece başka bir kayıt defterine kopyalanması meselesidir (arayan sonuç EAX'a konulmasını bekler)
Düzenleme: Biz bir yerli olmayan düzeni kullanarak fark göstermek için ilgili bir karşılaştırma ekleyebilirsiniz. En basit durum değerleri yerel genişlik dışında bir şeyde saklamaktır.
Godbolt'u tekrar kullanarak , basit bir yerel çarpmayı karşılaştırabiliriz
unsigned mult (unsigned x, unsigned y)
{
return x*y;
}
mult(unsigned int, unsigned int):
mov eax, edi
imul eax, esi
ret
standart olmayan bir genişlik için eşdeğer kodla
struct pair {
unsigned x : 31;
unsigned y : 31;
};
unsigned mult (pair p)
{
return p.x*p.y;
}
mult(pair):
mov eax, edi
shr rdi, 32
and eax, 2147483647
and edi, 2147483647
imul eax, edi
ret
Tüm ekstra talimatlar, giriş biçimini (iki adet 31 bit işaretsiz tam sayı) işlemcinin doğal olarak işleyebileceği biçime dönüştürmeyle ilgilidir. Sonucu tekrar 31 bit değerine depolamak istersek, bunu yapmak için bir veya iki talimat daha olurdu.
Bu ekstra karmaşıklık, yalnızca yer tasarrufu çok önemli olduğunda bununla uğraşacağınız anlamına gelir. Bu durumda , daha basit bir kod oluşturacak olan yerel unsigned
veya uint32_t
türü kullanmaya kıyasla yalnızca iki bit kaydediyoruz.
Dinamik boyutlar hakkında bir not:
Yukarıdaki örnek, değişken genişlik yerine sabit genişlikli değerlerdir, ancak genişlik (ve hizalama) artık yerel kayıtlarla eşleşmemektedir.
X86 platformu, ana 32-bit'e ek olarak 8-bit ve 16-bit de dahil olmak üzere çeşitli yerel boyutlara sahiptir (64 bit modundan ve basitlik için çeşitli diğer şeylerden bahsediyorum).
Bu tip (char, int8_t, uint8_t, int16_t vs) , aynı zamanda , doğrudan mimarisi ile desteklenen - kısmen eski 8086/286 / 386 / vs için geriye dönük uyumluluk. vb talimat setleri.
Kesinlikle en küçük doğal sabit boyutu seçmenin tipin iyi bir uygulama olabileceği kesinlikle doğrudur - hala hızlıdırlar, tek talimatlar yüklenir ve saklanır, yine de tam hızlı doğal aritmetik elde edersiniz ve hatta performansı artırabilirsiniz. önbellek isabetlerini azaltır.
Bu, değişken uzunluklu kodlamadan çok farklı - bunlardan bazılarıyla çalıştım ve korkunçlar. Her yük tek bir komut yerine bir döngü haline gelir. Her mağaza da bir döngü. Her yapı değişken uzunluktadır, bu nedenle dizileri doğal olarak kullanamazsınız.
Verimlilik hakkında bir not
Sonraki yorumlarda, depolama boyutuyla ilgili olarak anlayabildiğim kadarıyla "verimli" kelimesini kullanıyorsunuz. Bazen depolama boyutunu en aza indirmeyi seçeriz - dosyalara çok sayıda değer kaydederken veya bunları ağ üzerinden gönderirken önemli olabilir. Takas, onlarla herhangi bir şey yapmak için bu değerleri kayıtlara yüklememiz gerektiğidir ve dönüşümü gerçekleştirmek ücretsiz değildir.
Verimliliği tartıştığımızda, neyi optimize ettiğimizi ve takasların ne olduğunu bilmemiz gerekir. Yerel olmayan depolama türlerini kullanmak, alan için işlem hızını değiştirmenin bir yoludur ve bazen mantıklıdır. Değişken uzunlukta depolama kullanarak (en azından aritmetik tipler için), daha az yer kazanmak için daha fazla işlem hızı (ve kod karmaşıklığı ve geliştirici süresi) kullanır.
Bunun için ödediğiniz hız cezası, yalnızca bant genişliğini veya uzun süreli depolamayı kesinlikle en aza indirmeniz gerektiğinde faydalıdır ve bu durumlarda genellikle basit ve doğal bir biçim kullanmak daha kolaydır - ve daha sonra genel amaçlı bir sistemle sıkıştırın (zip, gzip, bzip2, xy ya da her neyse).
tl; Dr.
Her platformun bir mimarisi vardır, ancak verileri temsil etmek için esasen sınırsız sayıda farklı yolla gelebilirsiniz. Herhangi bir dil için sınırsız sayıda yerleşik veri türü sağlamak makul değildir. Bu nedenle, C ++, platformun doğal, doğal veri türleri kümesine örtülü erişim sağlar ve diğer (yerel olmayan) gösterimleri kendiniz kodlamanıza olanak tanır.
unsinged
değer, 1 bayt ile temsil edilebilir255
. 2) Değer değiştikçe, bir değişkenin en uygun depolama boyutunu hesaplama ve depolama alanını daraltma / genişletme yükünü göz önünde bulundurun.