Bellek tasarrufu yapmak için değişkenler için daha küçük veri türleri kullanmak iyi bir uygulama mıdır?


32

C ++ dilini ilk defa öğrendiğimde int, float vb dışında dilde bu veri türlerinin daha küçük veya daha büyük sürümlerinin bulunduğunu öğrendim. Mesela x değişkenini çağırabilirim

int x;
or 
short int x;

Buradaki asıl fark, kısa int'nin 2 byte belleği alırken, int 4 byte'ı alır ve kısa int'nin değeri daha düşük olur, ancak bunu daha da küçük yapmak için çağırabiliriz:

int x;
short int x;
unsigned short int x;

bu daha da kısıtlayıcı.

Buradaki sorum, değişkeninizin programda aldığı değerlere göre ayrı veri türleri kullanmanın iyi bir uygulama olup olmadığıdır. Değişkenleri her zaman bu veri türlerine göre bildirmek iyi bir fikir midir?


3
Flyweight tasarım modelinin farkında mısın? "Diğer benzer nesnelerle mümkün olduğunca veri olarak paylaşımı ile en aza indirir bellek kullanımı bir nesnenin; basit bir tekrarlanan temsil bellek kabul edilemez bir miktarda kullanmak ne zaman kullanıma için bir yoldur ... çok sayıda nesneleri"
tatarcık

5
Standart paketleme / hizalama derleyici ayarlarıyla, değişkenler yine de 4 byte sınırına hizalanır, bu nedenle hiçbir fark olmayabilir.
nikie,

36
Klasik erken optimizasyon durumu.
Fularlı

1
@nikie - Bir x86 işlemcide 4 baytlık bir sınırla hizalanmış olabilirler, ancak bu genel olarak doğru değildir. MSP430 char'ı herhangi bir bayt adresine ve diğer her şeyi eşit baytlık bir adrese yerleştirir. AVR-32 ve ARM Cortex-M'nin aynı olduğunu düşünüyorum.
uɐɪ

3
Sorunuzun 2. kısmı, unsignedbir şekilde bir tamsayı eklemenin elbette yanlış olan daha az yer kapladığını gösterir. Aynı sayıda farklı temsil edilebilir değere sahip olacaktır (işaretin nasıl gösterildiğine bağlı olarak 1 veriniz veya veriniz) ancak sadece pozitif olarak değişmiştir.
underscore_d

Yanıtlar:


41

Çoğu zaman alan maliyeti ihmal edilebilir düzeydedir ve bunun için endişelenmemelisiniz, ancak bir tür bildirerek verdiğiniz ek bilgiler için endişelenmelisiniz. Örneğin, eğer:

unsigned int salary;

Başka bir geliştiriciye yararlı bir bilgi veriyorsunuz: maaş olumsuz olamaz.

Kısa, kısa, uzun arasındaki fark nadiren uygulamanızda alan sorunlarına neden olur. Yanlışlıkla bir sayının her zaman bazı veri türlerine uyacağı varsayımını yanlış yapma ihtimaliniz daha yüksektir. Numaralarınızın her zaman çok küçük olacağından% 100 emin değilseniz, int'yi kullanmak muhtemelen daha güvenlidir. O zaman bile, gözle görülür miktarda yer tasarrufu sağlamanız pek mümkün değildir.


5
Doğru, bugünlerde nadiren sorunlara yol açacak, ancak başka bir geliştiricinin kullanacağı bir kütüphane veya sınıf tasarlıyorsanız, bu başka bir konudur. Belki bu nesnelerin bir milyonu için depolamaya ihtiyaçları vardır, bu durumda fark büyüktür - sadece bu alan için 2 MB ile karşılaştırıldığında 4 MB.
dodgy_coder

30
unsignedBu durumda kullanmak kötü bir fikirdir: sadece maaş negatif olamaz, ancak iki maaş arasındaki fark da negatif olamaz. (Genel olarak, ufak tefek şeyler dışında imzasız kullanmak ve taşma konusunda davranış tanımlamak kötü bir fikirdir.)
zvrba

15
@zvrba: İki maaş arasındaki farkın kendisi maaş değildir ve bu nedenle imzalanan farklı bir türün kullanılması yasaldır.
JeremyP,

12
@JeremyP Evet, ancak C kullanıyorsanız (ve bunun da C ++ 'da doğru olduğu görülüyorsa), işaretsiz tamsayı çıkarma , negatif olmayan imzasız bir int ile sonuçlanır. İmzalı bir int atarsanız doğru değere dönüşebilir, ancak işlemin sonucu imzasız bir int olur. Daha fazla imzalı / imzasız hesaplama tuhaflığı için bu cevaba da bakın. Bu nedenle, gerçekten titremeler yapmadığınız sürece imzasız değişkenleri kullanmamanız gerekir.
Tacroy

5
@zvrba: Fark, parasal bir miktardır ancak maaş değildir. Şimdi, bir maaşın aynı zamanda parasal bir miktar olduğunu (çoğu insanın yapacağı girdiyi doğrulayarak pozitif sayılarla ve 0 ile sınırlandırılmış olduğunu) iddia edebilirsiniz, ancak iki maaş arasındaki farkın kendisi değildir.
JeremyP

29

OP, programlar yazdıkları sistem türü hakkında hiçbir şey söylemedi, ancak OP'nin C ++ 'dan bahsedildiğinden beri GB belleğe sahip tipik bir PC'yi düşündüğünü sanıyorum. Yorumlardan birinin dediği gibi, bu tür bir belleğe rağmen, bir dizi gibi bir milyon türden bir eşinize sahipseniz - değişkenin boyutu bir fark yaratabilir.

OP, PC'lerle sınırlamadığından, sorunun kapsamı dışında olmayan gömülü sistemler dünyasına girerseniz, veri türlerinin büyüklüğü çok önemlidir. Sadece 8K'lık program hafızası ve 368 byte RAM kelimesine sahip 8 bitlik bir mikrokontrolörde hızlıca bir proje bitirdim . Orada, açıkçası her bayt sayılır. Birisi asla ihtiyaç duyduğundan daha büyük bir değişken kullanmaz (hem alan açısından hem de kod boyutundan - 8 bit işlemciler 16 ve 32 bit verileri işlemek için birçok talimat kullanır). Neden bu kadar sınırlı kaynaklara sahip bir CPU kullanıyorsunuz? Büyük miktarlarda, çeyreklik maliyete mal olabilirler.

Şu anda 512K bayt flash ve 128K bayt RAM'e sahip 32 bit MIPS tabanlı bir mikrodenetleyici ile gömülü bir proje yapıyorum (ve miktarında yaklaşık 6 $ civarında). Bir PC'de olduğu gibi, "doğal" veri boyutu 32 bittir. Artık karakter veya şort yerine çoğu değişken için ints kullanmak, kod açısından daha verimli hale geliyor. Ancak bir kez daha, daha küçük veri türlerinin garanti edilip edilmediğine bakılmaksızın, herhangi bir dizi veya yapı türü göz önünde bulundurulmalıdır. Daha büyük sistemler için Derleyiciler farklı olarak, bir yapı içinde daha olası değişkenlerin olacaktır gömülü sistemde paketlenebilir. Herhangi bir "delikten" kaçınmak için her zaman önce 32 bit değişkenleri önce, sonra 16 bit, sonra da 8 bit koymaya çalışıyorum.


10
Gömülü sistemlere farklı kuralların uygulandığı için +1. C ++ 'dan bahsedilmesi, hedefin bir bilgisayar olduğu anlamına gelmez. Son projelerimden biri C ++ 'da 32k RAM ve 256K Flash işlemciye yazılmıştı.
03'te

13

Cevap sisteminize bağlıdır. Genel olarak, daha küçük tip kullanmanın avantajları ve dezavantajları:

Avantajları

  • Küçük tipler çoğu sistemde daha az bellek kullanır.
  • Küçük tipler bazı sistemlerde daha hızlı hesaplamalar verir. Özellikle birçok sistemde çift veya şamandıralar için geçerlidir. Ve daha küçük int türleri de 8 veya 16 bit işlemcilerde önemli ölçüde daha hızlı kod sağlar.

Dezavantajları

  • Pek çok CPU hizalama gereksinimine sahiptir. Bazı erişim verileri atanmamış olandan daha hızlı hizalı. Bazılarının erişebilmek için bile hizalanmış veriye sahip olması gerekir . Daha büyük tam sayı türleri, bir hizalanmış birime eşittir, bu nedenle büyük olasılıkla yanlış hizalanmamışlardır. Bu, derleyicinin daha küçük tam sayılarınızı daha büyük sayılara koymaya zorlanması anlamına gelir. Ve küçük tipler daha büyük bir yapının parçasıysa, hizalamayı düzeltmek için derleyicinin yapının herhangi bir yerine sessizce yerleştirdiği çeşitli doldurma baytlarını alabilirsiniz.
  • Tehlikeli örtük dönüşümler. C ve C ++, değişkenlerin daha büyük olanlara nasıl bir terfi edilmeksizin teşvik edildiğinin belirsiz, tehlikeli kurallarına sahiptir. "Tamsayılı promosyon kuralları" ve "normal aritmetik dönüşümler" olarak adlandırılan birbiriyle ilişkili iki örtük dönüştürme kuralı kümesi vardır. Burada onlar hakkında daha fazla bilgi edinin . Bu kurallar, C ve C ++ 'daki hataların en yaygın nedenlerinden biridir. Tüm program boyunca aynı tamsayı tipini kullanarak çok fazla problemi önleyebilirsiniz.

Tavsiyem bunu sevmektir:

system                             int types

small/low level embedded system    stdint.h with smaller types
32-bit embedded system             stdint.h, stick to int32_t and uint32_t.
32-bit desktop system              Only use (unsigned) int and long long.
64-bit system                      Only use (unsigned) int and long long.

Alternatif olarak, kullanabileceğiniz int_leastn_tveya int_fastn_tn sayısı 8, 16, 32 veya 64 olduğunu stdint.h gelen int_leastn_tbu en azından olmak istiyorum" tipi aracı n bayt ama umurumda yoksa derleyici ayırır onu olarak hizalamaya uyması için daha büyük bir tip ".

int_fastn_t "Bunun n bayt uzunluğunda olmasını istiyorum, ancak kodumun daha hızlı çalışmasını sağlayacaksa, derleyicinin belirtilenden daha büyük bir tür kullanması gerekir" anlamına gelir.

Genel olarak, çeşitli stdint.h tipleri düz olduğundan çok daha iyidir int, çünkü taşınabilirler. Amaç, intyalnızca taşınabilir yapmak için belirli bir genişlik vermemekti. Fakat gerçekte, taşımak zordur çünkü belirli bir sistemde ne kadar büyük olacağını asla bilemezsiniz.


Uyum ile ilgili nokta. Şu andaki projemde, 16-bit MSP430'da uint8_t'nin gereksiz kullanımı MCU'yu gizemli yollarla (büyük olasılıkla yanlış hizalanmış erişim bir yerde oldu, belki de GCC'nin suçu, belki de değil) çökertdi - tüm uint8_t'nin yerine “imzasız” çöktü. Ölümcül değilse en az verimsiz ise> 8 bit yaylarda 8 bit türlerin kullanılması: derleyici ek 've reg, 0xff' komutları oluşturur. Taşınabilirlik için 'int / unigned' komutunu kullanın ve derleyiciyi ekstra sınırlamalardan kurtarın.
alexei

11

Belirli bir işletim sisteminin nasıl çalıştığına bağlı olarak, genel olarak belleğin kullanılmaması için ayrılmayı beklersiniz, öyle ki bir bayt veya bir sözcük veya tahsis edilecek başka bir küçük veri türü için çağrıldığında, değer tüm kayıtların tamamını kaplar. kendi. Derleyiciniz veya tercümanınız bunu yorumlamak için nasıl çalışır, ancak başka bir şeydir, bu nedenle, örneğin C # 'da bir program derleyecekseniz, değer fiziksel olarak kendisi için bir kayıt defteri tutabilir; amaçlanan veri türünün sınırlarını aşacak bir değer depolamaya çalışın.

Performans açısından bilgili ve bu tür şeyler hakkında gerçekten bilgili iseniz, hedef kayıt büyüklüğüne en çok benzeyen veri türünü kullanmak daha hızlı olacaktır, ancak daha sonra değişkenlerle çalışmayı çok kolaylaştıran tüm bu güzel sözdizimsel şekerini kaçırırsınız. .

Bu size nasıl yardımcı olur? Ne tür bir durumun kodlandığına karar vermek gerçekten size bağlı. Yazdığım hemen hemen her program için, işleri optimize etmek ve sizin için en yararlı olan veri türünü kullanmak için derleyicinize güvenmeniz yeterli. Yüksek hassasiyete ihtiyacınız varsa, daha büyük kayar nokta veri türlerini kullanın. Yalnızca pozitif değerlerle çalışıyorsanız, muhtemelen işaretsiz bir tamsayı kullanabilirsiniz, ancak çoğunlukla int veri türünü kullanmak yeterlidir.

Bununla birlikte, bir iletişim protokolü yazmak ya da bir tür şifreleme algoritması gibi bazı çok katı veri gereksinimleriniz varsa, aralık-kontrol veri tiplerini kullanmak, özellikle de veri taşmaları / alt işleriyle ilgili sorunlardan kaçınmaya çalışıyorsanız çok kullanışlı olabilir. veya geçersiz veri değerleri.

Belirli veri türlerini kullanmak için kafamın üstünden düşünebilmemin tek nedeni, kodunuzla niyet iletişim kurmaya çalışıyor olmanızdır. Örneğin, bir eksiklik kullanıyorsanız, diğer geliştiricilere çok küçük bir değer aralığında pozitif ve negatif sayılara izin verdiğinizi söylüyorsunuz.


6

Scarridge'nin dediği gibi , bu bir

Klasik erken optimizasyon durumu .

Bellek kullanımı için optimize çalışılıyor olabilir performansın diğer alanları etkilediğini ve optimizasyon altın kurallar şunlardır:

Program Optimizasyonunun İlk Kuralı: Yapmayın .

İkinci Program Optimizasyonu Kuralı (yalnızca uzmanlar için!): Henüz yapma . "

- Michael A. Jackson

Şimdi optimizasyon zamanının olup olmadığını bilmek için kıyaslama ve test gerekir. Kodunuzun nerede yetersiz kaldığını bilmeniz gerekir, böylece optimizasyonlarınızı hedefleyebilirsiniz.

Olmadığını belirlemek amacıyla optimize kod versiyonu olan aslında daha iyi herhangi bir zamanda naif uygulanması yerine, onları yan yana aynı verilerle kıyaslama gerekir.

Ayrıca, yalnızca belirli bir uygulamanın mevcut CPU işlemlerinde daha verimli olması, her zaman böyle olacağı anlamına gelmediğini unutmayın . Soruya cevabım Kodlamada mikro optimizasyon önemli midir? modası geçmiş bir optimizasyonun bir büyüklük yavaşlamasıyla sonuçlandığı kişisel deneyimlerden bir örnek verir.

Çoğu işlemcide, hizalanmamış bellek erişimi, hizalı bellek erişiminden önemli ölçüde daha maliyetlidir. Yapınıza birkaç kısa devre koymak, her iki değere de dokunduğunuzda programınızın paket / paket açma işlemini gerçekleştirmesi gerektiği anlamına gelebilir .

Bu nedenle, modern derleyiciler önerilerinizi görmezden gelir. As nikie yorumlarla:

Standart paketleme / hizalama derleyici ayarlarıyla, değişkenler yine de 4 byte sınırına hizalanır, bu nedenle hiçbir fark olmayabilir.

İkincisi, derleyicinizi tehlikeye atarsınız.

Terabayt veri kümeleriyle veya gömülü mikro denetleyicilerle çalışırken bu tür optimizasyonlara yer var, ancak çoğumuz için bu gerçekten bir sorun değil.


3

Buradaki asıl fark, kısa int'nin 2 byte belleği alırken, int 4 byte'ı alır ve kısa int'nin değeri daha düşük olur, ancak bunu daha da küçük yapmak için çağırabiliriz:

Bu yanlış. charHer türün boyutu bir öncekinden büyük veya eşit olduğunda, her bayt başına bir bayt ve en az 8 bit haricinde kaç bayt bulunduğuna dair varsayımlarda bulunamazsınız .

Performans avantajları yığın değişkenleri için inanılmaz derecede düşüktür - büyük olasılıkla yine de aynı hizada / yastıklı olacaktır.

Bu nedenle shortve longbugünlerde pratikte bir faydası yok ve neredeyse her zaman daha iyi kullanıyorsunuz int.


Tabii ki, kesmeyin stdint.hzaman kullanmak için tamamen iyi olan intda var. Eğer hiç bir zaman tam sayı / yapı dizisi ayırıyorsanız intX_t, verimli ve yazı tipinin büyüklüğüne güvenebileceğiniz bir anlam ifade eder. Bu megabayt hafızayı kaydedebileceğiniz için henüz erken değildir.


1
Aslında, 64 bit ortamların ortaya çıkması ile, longfarklı olabilir int. Derleyiciniz LP64 ise int, 32 bit ve long64 bit ve ints hala 4 byte hizalı olabilir (örneğin derleyicim bunu yapar).
JeremyP

1
@ JeremyP Evet, başka bir şey mi söyledim?
Pubby

Kısa ve uzun olduğunu iddia eden son cümlenin pratikte faydası yok. Tabii ki uzun zamandır kesinlikle bir kullanım alanı varint64_t
JeremyP

@JeremyP: int ile uzun süre yaşayabilirsin.
gnasher729

@ gnasher729: 65 binden fazla, ancak asla bir milyar kadar olmayan değerleri tutabilen bir değişkene ihtiyacınız varsa ne kullanırsınız? int32_t, int_fast32_tve longhepsi iyi seçeneklerdir, long longsadece boşuna ve inttaşınabilir değildir.
Ben Voigt

3

Bu, bir tür OOP ve / veya girişimci / uygulama bakış açısından olacaktır ve belirli alanlarda / alanlarda uygulanabilir olmayabilir, ancak ilkel saplantı kavramını ortaya koymak istiyorum .

Uygulamanızda farklı bilgi türleri için farklı veri türleri kullanmak iyi bir fikirdir. Bununla birlikte, bazı ciddi performans sorunlarınız (ölçülmüş ve doğrulanmış vb.) Olmadıkça, bunun için yerleşik tipleri kullanmanız muhtemelen iyi bir fikir DEĞİLDİR.

Bizim uygulamada Kelvin cinsinden modeli sıcaklıklara istiyorsak, bir kullanmak OLABİLİR ushortya uintya da göstermek için benzer bir şey "negatif derece kavramını Kelvin saçma ve etki alanı mantık hatası". Bunun arkasındaki fikir ses, ama sonuna kadar gitmiyorsun. Yaptığımız şey, negatif değerlere sahip olamayacağımızdır, bu yüzden derleyiciyi Kelvin sıcaklığına negatif bir değer atamayacağından emin olmak için derleyiciyi alabilirsek kullanışlıdır. Sıcaklıklarda bitsel işlemleri yapamayacağınız da doğru. Ve bir sıcaklığa (K) bir ağırlık ölçüsü (kg) ekleyemezsiniz. Fakat hem sıcaklığı hem de kütleyi uints olarak modellerseniz , bunu yapabiliriz.

DOMAIN varlıklarımızı modellemek için yerleşik türlerin kullanılması, bazı dağınık kodlara, bazı cevapsız kontrollere ve kırılmış değişmezlere yol açmak zorundadır. Bir tür, varlığın BAZI bir kısmını yakalasa bile (negatif olamaz), başkalarını özlemek zorundadır (rastgele aritmetik ifadelerde kullanılamaz, bir bit dizisi, vb. Olarak değerlendirilemez).

Çözüm, değişmezleri içine alan yeni tipler tanımlamaktır . Bu şekilde paranın para olduğundan ve mesafelerin mesafeler olduğundan emin olabilirsiniz ve bunları bir araya ekleyemezsiniz ve negatif bir mesafe oluşturamazsınız, ancak negatif miktarda para (veya borç) oluşturabilirsiniz. Tabii ki, bu türler dahili türleri dahili olarak kullanır, ancak bu istemcilerden gizlenir . Performans / bellek tüketimi hakkında sorunuza İlişkin, bu tür şeylerin o lanet öğrenmeliyiz, etki alanı varlıkları üzerinde işlem Fonksiyonlarınızdan arayüzünü değiştirmeden dahili olarak nasıl depolandığını şeyler değişmeye izin verebilir, bir shortçok lanet adildir büyük.


1

Evet tabi ki. uint_least8_tSözlükler, büyük sabit diziler, tamponlar vb. İçin kullanmak iyi bir fikirdir uint_fast8_t. İşleme amacıyla kullanmak daha iyidir .

uint8_least_t(depolama) -> uint8_fast_t(işleniyor) -> uint8_least_t(depolama).

Örneğin, 8 bitlik bir sembolden source, 16 bitlik kodlardan dictionariesve biraz da 32 bit alıyorsunuz constants. Onlarla 10-15 bitlik işlemler yapıyorsunuz ve 8 bitlik çıktılar destination.

2 gigabaytlık bir işlem yapmanız gerektiğini düşünelim source. Bit işlemlerinin miktarı çok büyük. İşleme sırasında hızlı tiplere geçerseniz, mükemmel performans bonusu alırsınız. Hızlı tipler, her CPU ailesi için farklı olabilir. Sen içerebilir stdint.hkullanımı ve uint_fast8_t, uint_fast16_t, uint_fast32_tvb

Taşınabilirlik uint_least8_tyerine kullanabilirsiniz uint8_t. Fakat kimse modern cpu'nun bu özelliği ne kullanacağını bilmiyor . VAC makine bir müze eseridir. Yani belki bir overkill.


1
Listelenen veri türleriyle ilgili bir noktanız olsa da, sadece olduğunu belirtmek yerine neden daha iyi olduklarını açıklamalısınız . Benim gibi, bu veri türlerine aşina olmayan insanlar için, neden bahsettiğinizi anlamak için Google'a gitmek zorunda kaldım.
Peter M,
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.