it-swarm.com.de

Ist es eine gute Praxis, kleinere Datentypen für Variablen zu verwenden, um Speicherplatz zu sparen?

Als ich die C++ - Sprache zum ersten Mal lernte, erfuhr ich, dass neben int, float usw. kleinere oder größere Versionen dieser Datentypen in der Sprache vorhanden waren. Zum Beispiel könnte ich eine Variable x aufrufen

int x;
or 
short int x;

Der Hauptunterschied besteht darin, dass short int 2 Bytes Speicher benötigt, während int 4 Bytes benötigt, und short int einen geringeren Wert hat, aber wir könnten dies auch nennen, um es noch kleiner zu machen:

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

das ist noch restriktiver.

Meine Frage hier ist, ob es eine gute Praxis ist, separate Datentypen zu verwenden, je nachdem, welche Werte Ihre Variable im Programm annimmt. Ist es eine gute Idee, Variablen immer nach diesen Datentypen zu deklarieren?

32
Bugster

In den meisten Fällen sind die Platzkosten vernachlässigbar und Sie sollten sich keine Sorgen machen. Sie sollten sich jedoch Gedanken über die zusätzlichen Informationen machen, die Sie durch die Angabe eines Typs angeben. Zum Beispiel, wenn Sie:

unsigned int salary;

Sie geben einem anderen Entwickler eine nützliche Information: Das Gehalt kann nicht negativ sein.

Der Unterschied zwischen kurz, int und lang führt selten zu Speicherplatzproblemen in Ihrer Anwendung. Es ist wahrscheinlicher, dass Sie versehentlich die falsche Annahme treffen, dass eine Zahl immer in einen Datentyp passt. Es ist wahrscheinlich sicherer, immer int zu verwenden, es sei denn, Sie sind zu 100% sicher, dass Ihre Zahlen immer sehr klein sind. Selbst dann ist es unwahrscheinlich, dass Sie spürbar viel Platz sparen.

42
Oleksi

Das OP sagte nichts über die Art des Systems aus, für das sie Programme schreiben, aber ich gehe davon aus, dass das OP an einen typischen PC mit GB Speicher dachte, da C++ erwähnt wird. Wie einer der Kommentare besagt, kann selbst bei dieser Art von Speicher die Größe der Variablen einen Unterschied machen, wenn Sie mehrere Millionen Elemente eines Typs haben - beispielsweise ein Array.

Wenn Sie in die Welt der eingebetteten Systeme einsteigen - was nicht wirklich außerhalb des Rahmens der Frage liegt, da das OP es nicht auf PCs beschränkt -, ist die Größe der Datentypen sehr wichtig. Ich habe gerade ein kurzes Projekt auf einem 8-Bit-Mikrocontroller abgeschlossen, der nur 8 KB Programmspeicher und 368 Bytes RAM enthält. Dort zählt natürlich jedes Byte. Man verwendet niemals eine Variable, die größer ist als sie benötigt (sowohl vom Platzstandpunkt als auch von der Codegröße - 8-Bit-Prozessoren verwenden viele Anweisungen, um 16- und 32-Bit-Daten zu bearbeiten). Warum eine CPU mit so begrenzten Ressourcen verwenden? In großen Mengen können sie nur ein Viertel kosten.

Ich mache derzeit ein weiteres Embedded-Projekt mit einem 32-Bit-MIPS-basierten Mikrocontroller, der 512 KByte Flash und 128 KByte RAM (und kostet ca. 6 USD in der Menge) hat. Wie bei einem PC Die "natürliche" Datengröße beträgt 32 Bit. Jetzt wird es in Bezug auf den Code effizienter, Ints für die meisten Variablen anstelle von Zeichen oder Kurzschlüssen zu verwenden. Aber auch hier muss jede Art von Array oder Struktur berücksichtigt werden, unabhängig davon, ob es sich um kleinere Daten handelt Im Gegensatz zu Compilern für größere Systeme ist es wahrscheinlicher, dass Variablen in einer Struktur auf einem eingebetteten System gepackt werden. Ich versuche es immer alle 32-Bit-Variablen zuerst setzen, dann 16-Bit, dann 8-Bit, um "Löcher" zu vermeiden.

29
tcrosley

Die Antwort hängt von Ihrem System ab. Im Allgemeinen sind hier die Vor- und Nachteile der Verwendung kleinerer Typen:

Vorteile

  • Kleinere Typen verbrauchen auf den meisten Systemen weniger Speicher.
  • Kleinere Typen ermöglichen auf einigen Systemen schnellere Berechnungen. Dies gilt insbesondere für Float vs Double auf vielen Systemen. Und kleinere int-Typen liefern auch auf 8- oder 16-Bit-CPUs deutlich schnelleren Code.

Nachteile

  • Viele CPUs haben Ausrichtungsanforderungen. Einige greifen schneller auf ausgerichtete Daten zu als nicht ausgerichtete. Einige müssen die Daten ausgerichtet haben, um überhaupt darauf zugreifen zu können. Die größeren Ganzzahltypen entsprechen einer ausgerichteten Einheit, sodass sie höchstwahrscheinlich nicht falsch ausgerichtet sind. Dies bedeutet, dass der Compiler möglicherweise gezwungen ist, Ihre kleineren Ganzzahlen in größere zu setzen. Und wenn die kleineren Typen Teil einer größeren Struktur sind, können Sie vom Compiler verschiedene Füllbytes erhalten, die stillschweigend an einer beliebigen Stelle in der Struktur eingefügt werden, um die Ausrichtung zu korrigieren.
  • Gefährliche implizite Konvertierungen. C und C++ haben mehrere obskure, gefährliche Regeln dafür, wie Variablen zu größeren heraufgestuft werden, implizit ohne Typumwandlung. Es gibt zwei Sätze impliziter Konvertierungsregeln, die miteinander verflochten sind, die als "Ganzzahl-Heraufstufungsregeln" und als "übliche arithmetische Konvertierungen" bezeichnet werden. Lesen Sie mehr über sie hier . Diese Regeln sind eine der häufigsten Ursachen für Fehler in C und C++. Sie können viele Probleme vermeiden, indem Sie einfach im gesamten Programm denselben Integer-Typ verwenden.

Mein Rat ist, dies zu mögen:

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.

Alternativ können Sie das int_leastn_t oder int_fastn_t von stdint.h, wobei n die Zahl 8, 16, 32 oder 64 ist. int_leastn_t type bedeutet "Ich möchte, dass dies mindestens n Bytes sind, aber es ist mir egal, ob der Compiler es als größeren Typ entsprechend der Ausrichtung zuweist".

int_fastn_t bedeutet "Ich möchte, dass dies n Bytes lang ist, aber wenn dadurch mein Code schneller ausgeführt wird, sollte der Compiler einen größeren Typ als angegeben verwenden.".

Im Allgemeinen sind die verschiedenen stdint.h-Typen viel besser als einfache int usw., da sie portabel sind. Mit int sollte nicht nur eine bestimmte Breite angegeben werden, um es portabel zu machen. In Wirklichkeit ist es jedoch schwierig zu portieren, da Sie nie wissen, wie groß es auf einem bestimmten System sein wird.

13
user29079

Abhängig von der Funktionsweise des jeweiligen Betriebssystems erwarten Sie im Allgemeinen, dass der Speicher nicht optimiert zugewiesen wird, sodass der Wert beim Aufrufen eines Bytes oder eines Word oder eines anderen kleinen Datentyps ein ganzes Register belegt besitzen. Wie Ihr Compiler oder Interpreter dies interpretiert, ist jedoch etwas anderes. Wenn Sie beispielsweise ein Programm in C # kompilieren, belegt der Wert möglicherweise physisch ein Register für sich selbst. Der Wert wird jedoch einer Grenzprüfung unterzogen, um sicherzustellen, dass Sie dies nicht tun Versuchen Sie, einen Wert zu speichern, der die Grenzen des beabsichtigten Datentyps überschreitet.

In Bezug auf die Leistung und wenn Sie in solchen Dingen wirklich pedantisch sind, ist es wahrscheinlich schneller, einfach den Datentyp zu verwenden, der der Größe des Zielregisters am ehesten entspricht, aber dann verpassen Sie all den schönen syntaktischen Zucker, der das Arbeiten mit Variablen so einfach macht .

Wie hilft dir das? Nun, es liegt wirklich an Ihnen, zu entscheiden, für welche Art von Situation Sie programmieren. Für fast jedes Programm, das ich jemals geschrieben habe, reicht es aus, einfach Ihrem Compiler zu vertrauen, um die Dinge zu optimieren und den Datentyp zu verwenden, der für Sie am nützlichsten ist. Wenn Sie eine hohe Genauigkeit benötigen, verwenden Sie die größeren Gleitkomma-Datentypen. Wenn Sie nur mit positiven Werten arbeiten, können Sie wahrscheinlich eine Ganzzahl ohne Vorzeichen verwenden, aber zum größten Teil reicht es aus, nur den Datentyp int zu verwenden.

Wenn Sie jedoch einige sehr strenge Datenanforderungen haben, z. B. das Schreiben eines Kommunikationsprotokolls oder eines Verschlüsselungsalgorithmus, kann die Verwendung von Datentypen mit Bereichsprüfung sehr nützlich sein, insbesondere wenn Sie versuchen, Probleme im Zusammenhang mit Datenüberschreitungen/-unterschreitungen zu vermeiden oder ungültige Datenwerte.

Der einzige andere Grund, warum ich mir vorstellen kann, bestimmte Datentypen zu verwenden, ist, wenn Sie versuchen, Absichten in Ihrem Code zu kommunizieren. Wenn Sie beispielsweise eine Abkürzung verwenden, teilen Sie anderen Entwicklern mit, dass Sie positive und negative Zahlen innerhalb eines sehr kleinen Wertebereichs zulassen.

11
S.Robins

Wie scarfridge kommentiert, ist dies ein

Klassischer Fall von vorzeitige Optimierung .

Der Versuch, die Speichernutzung zu optimieren könnte Auswirkungen auf andere Leistungsbereiche haben, und die goldene Optimierungsregeln sind:

Die erste Regel der Programmoptimierung: Tun Sie es nicht .

Die zweite Regel der Programmoptimierung (nur für Experten!): Mach es noch nicht . "

- Michael A. Jackson

Um zu wissen, ob jetzt die Zeit für eine Optimierung gekommen ist, müssen Benchmarking und Tests durchgeführt werden. Sie müssen wissen, wo Ihr Code ineffizient ist, damit Sie Ihre Optimierungen gezielt durchführen können.

Um festzustellen, ob die optimierte Version des Codes tatsächlich besser ist als die naive Implementierung zu einem bestimmten Zeitpunkt, müssen Sie sie nebeneinander vergleichen. Seite mit den gleichen Daten.

Denken Sie auch daran, dass eine bestimmte Implementierung, die für die aktuelle Generation von CPUs effizienter ist, nicht bedeutet, dass dies immer ist. Meine Antwort auf die Frage Ist Mikrooptimierung beim Codieren wichtig? beschreibt ein Beispiel aus persönlicher Erfahrung, bei dem eine veraltete Optimierung zu einer Verlangsamung um eine Größenordnung führte.

Auf vielen Prozessoren sind nicht ausgerichtete Speicherzugriffe erheblich teurer als ausgerichtete Speicherzugriffe. Das Packen einiger Shorts in Ihre Struktur kann nur bedeuten, dass Ihr Programm eine Pack/Unpack-Operation ausführen muss jedes Mal Sie berühren einen der beiden Werte.

Aus diesem Grund ignorieren moderne Compiler Ihre Vorschläge. As nikie Kommentare:

Bei den Standardeinstellungen für das Packen/Ausrichten des Compilers werden die Variablen ohnehin an 4-Byte-Grenzen ausgerichtet, sodass möglicherweise überhaupt kein Unterschied besteht.

Erraten Sie Ihren Compiler auf eigene Gefahr.

Es gibt einen Platz für solche Optimierungen, wenn mit Terabyte-Datensätzen oder eingebetteten Mikrocontrollern gearbeitet wird, aber für die meisten von uns ist dies kein wirkliches Problem.

6
Mark Booth

Dies wird aus einer Art OOP und/oder Unternehmens-/Anwendungssicht erfolgen und ist möglicherweise in bestimmten Bereichen/Domänen nicht anwendbar, aber ich möchte das Konzept von primitive Besessenheit .

Es ist IS eine gute Idee, verschiedene Datentypen für verschiedene Arten von Informationen in Ihrer Anwendung zu verwenden. Es ist jedoch wahrscheinlich keine gute Idee, die integrierten Typen dafür zu verwenden, es sei denn, Sie haben ernsthafte Leistungsprobleme (die gemessen und verifiziert wurden usw.).

Wenn wir in unserer Anwendung Temperaturen in Kelvin modellieren möchten, KÖNNEN wir ein ushort oder uint oder ähnliches verwenden, um zu bezeichnen, dass "der Begriff der negativen Kelvin-Grade absurd ist und ein Domänenlogikfehler". . Die Idee dahinter ist Klang, aber Sie gehen nicht den ganzen Weg. Wir haben festgestellt, dass wir keine negativen Werte haben können. Daher ist es praktisch, wenn der Compiler sicherstellen kann, dass niemand einer Kelvin-Temperatur einen negativen Wert zuweist. Es ist AUCH wahr, dass Sie bei Temperaturen keine bitweisen Operationen durchführen können. Und Sie können einer Temperatur (K) kein Maß für das Gewicht (kg) hinzufügen. Wenn Sie jedoch sowohl Temperatur als auch Masse als uints modellieren, können wir genau das tun.

Die Verwendung integrierter Typen zur Modellierung unserer DOMAIN-Entitäten führt zwangsläufig zu unordentlichem Code, fehlenden Überprüfungen und fehlerhaften Invarianten. Selbst wenn ein Typ EINEN Teil der Entität erfasst (kann nicht negativ sein), muss er andere übersehen (kann nicht in beliebigen arithmetischen Ausdrücken verwendet werden, kann nicht als Array von Bits behandelt werden usw.)

Die Lösung besteht darin, neue Typen zu definieren, die die Invarianten einkapseln. Auf diese Weise können Sie sicherstellen, dass Geld Geld ist und Entfernungen Entfernungen sind, und Sie können sie nicht addieren, und Sie können keine negative Distanz erstellen, aber Sie KÖNNEN einen negativen Geldbetrag (oder eine Schuld) erstellen. Natürlich verwenden diese Typen die integrierten Typen intern, aber dies ist vor Clients verborgen . In Bezug auf Ihre Frage zur Leistung/zum Speicherverbrauch können Sie mit dieser Art von Dingen ändern, wie Dinge intern gespeichert werden, ohne die Schnittstelle Ihrer Funktionen zu ändern, die auf Ihren Domänenentitäten ausgeführt werden, falls Sie herausfinden, dass verdammt, ein short ist einfach zu groß.

3
sara

Der Hauptunterschied besteht darin, dass short int 2 Bytes Speicher benötigt, während int 4 Bytes benötigt, und short int einen geringeren Wert hat, aber wir könnten dies auch nennen, um es noch kleiner zu machen:

Das ist falsch. Sie können keine Annahmen darüber treffen, wie viele Bytes jeder Typ enthält, außer dass char ein Byte und mindestens 8 Bits pro Byte ist und die Größe jedes Typs größer oder gleich der vorherigen ist.

Die Leistungsvorteile sind für Stapelvariablen unglaublich gering - sie werden wahrscheinlich sowieso ausgerichtet/aufgefüllt.

Aus diesem Grund haben short und long heutzutage praktisch keine Verwendung mehr, und Sie sind fast immer besser dran, int zu verwenden.


Natürlich gibt es auch stdint.h was vollkommen in Ordnung ist, wenn int es nicht schneidet. Wenn Sie jemals große Arrays von Ganzzahlen/Strukturen zuweisen, wird ein intX_t ist sinnvoll, da Sie effizient sein und sich auf die Größe des Typs verlassen können. Dies ist überhaupt nicht verfrüht, da Sie Megabyte Speicher sparen können.

3
Pubby

Ja natürlich. Es ist eine gute Idee, uint_least8_t für Wörterbücher, Arrays mit großen Konstanten, Puffer usw. Es ist besser, uint_fast8_t für Verarbeitungszwecke.

uint8_least_t (Speicher) -> uint8_fast_t (Verarbeitung) -> uint8_least_t (Lager).

Zum Beispiel nehmen Sie ein 8-Bit-Symbol von source, 16-Bit-Codes von dictionaries und einige 32-Bit-constants. Dann verarbeiten Sie 10-15-Bit-Operationen mit ihnen und geben 8-Bit destination aus.

Stellen wir uns vor, Sie müssen 2 Gigabyte source verarbeiten. Die Anzahl der Bitoperationen ist enorm. Sie erhalten einen großen Leistungsbonus, wenn Sie während der Verarbeitung zu schnellen Typen wechseln. Schnelle Typen können für jede CPU-Familie unterschiedlich sein. Sie können stdint.h und benutze uint_fast8_t, uint_fast16_t, uint_fast32_t, usw.

Du könntest benutzen uint_least8_t Anstatt von uint8_t für Portabilität. Aber niemand weiß es wirklich welche moderne CPU diese Funktion verwenden wird. VAC Maschine ist ein Museumsstück. Vielleicht ist es ein Overkill.

1
puchu