Kiedy po raz pierwszy nauczyłem się języka C ++, dowiedziałem się, że oprócz int, float itp. W tym języku istniały mniejsze lub większe wersje tych typów danych. Na przykład mógłbym wywołać zmienną x
int x;
or
short int x;
Główną różnicą jest to, że short int zajmuje 2 bajty pamięci, podczas gdy int zajmuje 4 bajty, a short int ma mniejszą wartość, ale możemy też to nazwać, aby był jeszcze mniejszy:
int x;
short int x;
unsigned short int x;
co jest jeszcze bardziej restrykcyjne.
Moje pytanie dotyczy tego, czy dobrą praktyką jest używanie osobnych typów danych zgodnie z wartościami, jakie zmienne przyjmują w programie. Czy dobrym pomysłem jest zawsze deklarowanie zmiennych zgodnie z tymi typami danych?
c++
data-structures
Bugster
źródło
źródło
unsigned
jakiś sposób powoduje, że liczba całkowita zajmuje mniej miejsca, co oczywiście jest fałszywe. Będzie miał taką samą liczbę dyskretnie reprezentowalnych wartości (daje lub przyjmuje 1 w zależności od tego, jak znak jest reprezentowany), ale po prostu został zmieniony wyłącznie na dodatni.Odpowiedzi:
Przez większość czasu koszt miejsca jest znikomy i nie powinieneś się tym przejmować, ale powinieneś martwić się o dodatkowe informacje, które podajesz, deklarując typ. Na przykład, jeśli:
Przekazujesz użyteczną informację innemu deweloperowi: wynagrodzenie nie może być ujemne.
Różnica między skrótem, int, long rzadko powoduje problemy z miejscem w aplikacji. Bardziej prawdopodobne jest, że przypadkowo fałszywe założenie, że liczba zawsze będzie pasować do jakiegoś typu danych. Prawdopodobnie bezpieczniej jest zawsze używać int, chyba że masz 100% pewności, że twoje liczby będą zawsze bardzo małe. Nawet wtedy mało prawdopodobne jest, aby zaoszczędzić zauważalną ilość miejsca.
źródło
unsigned
w tym przypadku jest złym pomysłem: nie tylko wynagrodzenie nie może być ujemne, ale różnica między dwoma wynagrodzeniami również nie może być ujemna. (Ogólnie rzecz biorąc, używanie niepodpisanego do niczego innego niż kręcenie bitów i posiadanie określonego zachowania w przypadku przepełnienia jest złym pomysłem.)OP nie powiedział nic o typie systemu, dla którego piszą programy, ale zakładam, że OP myślał o typowym komputerze z pamięcią GB, skoro wspomniano o C ++. Jak mówi jeden z komentarzy, nawet przy tego rodzaju pamięci, jeśli masz kilka milionów elementów jednego typu - takich jak tablica - to wielkość zmiennej może mieć znaczenie.
Jeśli wejdziesz w świat systemów wbudowanych - co tak naprawdę nie jest poza zakresem pytania, ponieważ OP nie ogranicza go do komputerów PC - rozmiar typów danych ma bardzo duże znaczenie. Właśnie skończyłem szybki projekt na 8-bitowym mikrokontrolerze, który ma tylko 8 000 słów pamięci programu i 368 bajtów pamięci RAM. Tam oczywiście liczy się każdy bajt. Nigdy nie używa się zmiennej większej niż potrzebują (zarówno z punktu widzenia przestrzeni, jak i rozmiaru kodu - procesory 8-bitowe używają wielu instrukcji do manipulowania danymi 16 i 32-bitowymi). Po co używać procesora z tak ograniczonymi zasobami? W dużych ilościach mogą kosztować zaledwie jedną czwartą.
Obecnie pracuję nad innym projektem osadzonym z 32-bitowym mikrokontrolerem opartym na MIPS, który ma 512 000 bajtów pamięci flash i 128 000 bajtów pamięci RAM (i kosztuje około 6 USD). Podobnie jak w przypadku komputera, „naturalny” rozmiar danych wynosi 32 bity. Teraz staje się bardziej wydajne, pod względem kodu, użycie liczb całkowitych dla większości zmiennych zamiast znaków lub skrótów. Ale jeszcze raz należy rozważyć każdy typ tablicy lub struktury, czy uzasadnione są mniejsze typy danych. W przeciwieństwie do kompilatorów dla większych systemów, bardziej prawdopodobne jest, że zmienne w strukturze zostaną upakowane w systemie osadzonym. Staram się zawsze umieszczać wszystkie zmienne 32-bitowe najpierw, a następnie 16-bitowe, a następnie 8-bitowe, aby uniknąć „dziur”.
źródło
Odpowiedź zależy od twojego systemu. Ogólnie rzecz biorąc, oto zalety i wady korzystania z mniejszych typów:
Zalety
Niedogodności
Radzę polubić to:
Alternatywnie możesz użyć
int_leastn_t
lubint_fastn_t
ze stdint.h, gdzie n jest liczbą 8, 16, 32 lub 64.int_leastn_t
typ oznacza „Chcę, aby to było co najmniej n bajtów, ale nie obchodzi mnie, czy kompilator przydzieli go jako większy typ pasujący do wyrównania ".int_fastn_t
oznacza: „Chcę, aby miał on długość n bajtów, ale jeśli sprawi to, że mój kod będzie działał szybciej, kompilator powinien użyć typu większego niż określony”.Ogólnie rzecz biorąc, różne typy stdint.h są znacznie lepszą praktyką niż zwykłe
int
itp., Ponieważ są przenośne. Chodziło oint
to, aby nie nadać mu określonej szerokości wyłącznie w celu zapewnienia przenośności. Ale w rzeczywistości trudno go przenieść, ponieważ nigdy nie wiadomo, jak duży będzie w danym systemie.źródło
W zależności od tego, jak działa określony system operacyjny, zazwyczaj oczekuje się, że pamięć zostanie przydzielona w sposób niezoptymalizowany, tak że gdy wywołuje się bajt, słowo lub inny niewielki typ danych do przydzielenia, wartość zajmuje cały rejestr, a wszystko to bardzo posiadać. Sposób, w jaki kompilator lub interpreter działa, aby to zinterpretować, jest jednak czymś innym, więc jeśli na przykład skompilujesz program w języku C #, wartość może fizycznie zająć rejestr dla siebie, jednak wartość zostanie sprawdzona na granicy, aby upewnić się, że nie spróbuj zapisać wartość, która przekroczy granice zamierzonego typu danych.
Jeśli chodzi o wydajność, a jeśli naprawdę jesteś pedantyczny na temat takich rzeczy, prawdopodobnie szybciej jest po prostu użyć typu danych, który najbardziej odpowiada docelowemu rozmiarowi rejestru, ale potem brakuje ci tego cudownego cukru syntaktycznego, który sprawia, że praca ze zmiennymi jest tak łatwa .
Jak ci to pomaga? Cóż, tak naprawdę to Ty decydujesz, w jakiej sytuacji kodujesz. Dla prawie każdego programu, jaki kiedykolwiek napisałem, wystarczy zaufać swojemu kompilatorowi, aby zoptymalizować rzeczy i użyć typu danych, który jest dla Ciebie najbardziej użyteczny. Jeśli potrzebujesz wysokiej precyzji, użyj większych typów danych zmiennoprzecinkowych. Jeśli pracujesz tylko z dodatnimi wartościami, prawdopodobnie możesz użyć liczby całkowitej bez znaku, ale w większości przypadków wystarczy użycie typu danych int.
Jeśli jednak masz bardzo surowe wymagania dotyczące danych, takie jak napisanie protokołu komunikacyjnego lub jakiś algorytm szyfrowania, użycie typów danych ze sprawdzonym zakresem może być bardzo przydatne, szczególnie jeśli próbujesz uniknąć problemów związanych z przekroczeniami / przekroczeniami danych lub nieprawidłowe wartości danych.
Jedynym innym powodem, dla którego mogę wymyślić z góry, aby używać określonych typów danych, jest to, że próbujesz komunikować zamiary w swoim kodzie. Na przykład, używając skrótu, mówisz innym programistom, że zezwalasz na liczby dodatnie i ujemne w bardzo małym zakresie wartości.
źródło
Jak skomentował scarfridge , jest to
Próba optymalizacji wykorzystania pamięci może mieć wpływ na inne obszary wydajności, a złotymi zasadami optymalizacji są:
Aby dowiedzieć się, czy nadszedł czas na optymalizację, należy przeprowadzić testy porównawcze i testy. Musisz wiedzieć, gdzie twój kod jest nieefektywny, abyś mógł kierować swoje optymalizacje.
Aby ustalić, czy zoptymalizowana wersja kodu jest faktycznie lepsza niż naiwna implementacja w danym momencie, musisz porównać je z tymi samymi danymi.
Pamiętaj też, że tylko dlatego, że dana implementacja jest bardziej wydajna w obecnej generacji procesorów, nie oznacza to, że zawsze tak będzie. Moja odpowiedź na pytanie Czy mikrooptymalizacja jest ważna podczas kodowania? szczegółowo przedstawia przykład z własnego doświadczenia, w którym przestarzała optymalizacja spowodowała spowolnienie rzędu wielkości.
W wielu procesorach niewyrównane dostępy do pamięci są znacznie droższe niż wyrównane dostępy do pamięci. Spakowanie kilku skrótów do struktury może po prostu oznaczać, że twój program musi wykonać operację spakowania / rozpakowania za każdym razem , gdy dotkniesz dowolnej wartości.
Z tego powodu nowoczesne kompilatory ignorują twoje sugestie. Jak komentuje Nikie :
Po drugie, zgadnij, że kompilator jest na własne ryzyko.
Jest miejsce na takie optymalizacje podczas pracy z terabajtowymi zestawami danych lub wbudowanymi mikrokontrolerami, ale dla większości z nas nie jest to tak naprawdę problemem.
źródło
To jest niepoprawne. Nie można zakładać, ile bajtów zawiera każdy typ, poza tym,
char
że jest to jeden bajt i co najmniej 8 bitów na bajt, a wielkość każdego typu jest większa lub równa poprzedniej.Korzyści z wydajności są niezwykle małe w przypadku zmiennych stosu - prawdopodobnie i tak zostaną wyrównane / uzupełnione.
Z tego powodu
short
ilong
obecnie nie mają praktycznie żadnego zastosowania, a prawie zawsze lepiej jest używaćint
.Oczywiście jest też coś,
stdint.h
co idealnie nadaje się do użycia, gdy sięint
go nie tnie. Jeśli kiedykolwiek przydzielasz ogromne tablice liczb całkowitych / struktur, tointX_t
ma sens, ponieważ możesz być wydajny i polegać na rozmiarze tego typu. Nie jest to wcale przedwczesne, ponieważ można zaoszczędzić megabajty pamięci.źródło
long
mogą się różnić odint
. Jeśli twoim kompilatorem jest LP64,int
ma 32 bity ilong
64 bity, a przekonasz się, żeint
s może być nadal wyrównany do 4 bajtów (na przykład mój kompilator ma taką opcję).int64_t
int32_t
,int_fast32_t
ilong
wszystkie są dobrymi opcjami,long long
jest po prostu marnotrawstwem iint
nieprzenośnym.Będzie to z pewnego rodzaju punktu widzenia OOP i / lub przedsiębiorczości / aplikacji i może nie mieć zastosowania w niektórych dziedzinach / domenach, ale chciałbym raczej przywołać pojęcie prymitywnej obsesji .
Dobrym pomysłem jest używanie różnych typów danych dla różnych rodzajów informacji w aplikacji. Jednak prawdopodobnie NIE jest dobrym pomysłem stosowanie do tego wbudowanych typów, chyba że masz poważne problemy z wydajnością (które zostały zmierzone i zweryfikowane itd.).
Jeśli chcemy modelowych temperatury w stopniach Kelvina w naszej aplikacji, możemy użyć
ushort
lubuint
lub coś podobnego do oznaczenia, że „pojęcie stopni Kelvina negatywnych jest absurdalne i błąd logiczny domeny”. Ideą tego jest dźwięk, ale nie idziesz do końca. Uświadomiliśmy sobie, że nie możemy mieć wartości ujemnych, więc jest to przydatne, jeśli możemy uzyskać kompilator, aby upewnić się, że nikt nie przypisuje wartości ujemnej do temperatury Kelvina. Jest również prawdą, że nie można wykonywać bitowych operacji na temperaturach. I nie można dodać miary masy (kg) do temperatury (K). Ale jeśli modelujesz zarówno temperaturę, jak i masę jakouint
s, możemy to zrobić.Używanie wbudowanych typów do modelowania naszych jednostek DOMAIN musi doprowadzić do niechlujnego kodu oraz niektórych nieodebranych czeków i uszkodzonych niezmienników. Nawet jeśli typ przechwytuje NIEKTÓRE części bytu (nie może być ujemny), musi ominąć inne (nie może być stosowany w dowolnych wyrażeniach arytmetycznych, nie może być traktowany jako tablica bitów itp.)
Rozwiązaniem jest zdefiniowanie nowych typów, które zawierają niezmienniki. W ten sposób możesz upewnić się, że pieniądze są pieniędzmi, a odległości są odległościami, i nie możesz ich sumować, i nie możesz stworzyć ujemnej odległości, ale MOŻESZ stworzyć ujemną kwotę pieniędzy (lub długu). Oczywiście te typy będą używać wbudowanych typów wewnętrznie, ale jest to ukryte przed klientami. Odnosząc się do twojego pytania dotyczącego wydajności / zużycia pamięci, tego rodzaju rzeczy mogą pozwolić ci zmienić sposób, w jaki rzeczy są przechowywane wewnętrznie, bez zmiany interfejsu twoich funkcji, które działają na twoich domenowych domenach, jeśli dowiesz się, że to cholerne, a
short
jest po prostu zbyt cholerne duży.źródło
Tak oczywiście. Dobrym pomysłem jest stosowanie
uint_least8_t
do słowników, tablic ogromnych stałych, buforów itp. Lepiej jest używaćuint_fast8_t
do celów przetwarzania.uint8_least_t
(przechowywanie) ->uint8_fast_t
(przetwarzanie) ->uint8_least_t
(przechowywanie).Na przykład bierzesz 8-bitowy symbol
source
, 16-bitowe kodydictionaries
i jakieś 32-bitoweconstants
. Następnie przetwarzasz z nimi 10-15 bitowe operacje i generujesz 8 bitówdestination
.Wyobraźmy sobie, że musisz przetworzyć 2 gigabajty
source
. Liczba operacji bitowych jest ogromna. Otrzymasz świetny bonus wydajności, jeśli przełączysz się na szybkie typy podczas przetwarzania. Szybkie typy mogą być różne dla każdej rodziny procesorów. Możesz dołączyćstdint.h
i wykorzystanieuint_fast8_t
,uint_fast16_t
,uint_fast32_t
, itd.Możesz użyć
uint_least8_t
zamiastuint8_t
przenośności. Ale nikt tak naprawdę nie wie, jakie nowoczesne procesory wykorzystają tę funkcję. Maszyna VAC jest dziełem muzealnym. Więc może to przesada.źródło