Was sind einige Best Practices zur Reduzierung der Speichernutzung in C?

Lesezeit: 6 Minuten

Benutzeravatar von user27424
Benutzer27424

Was sind einige Best Practices für “speichereffiziente C-Programmierung”. Was sollten die Richtlinien für einen geringen Speicherverbrauch sein, hauptsächlich für eingebettete / mobile Geräte?

Ich denke, es sollte eine separate Richtlinie für a) Codespeicher b) Datenspeicher geben

  • Schön, eine solche Vielfalt an ganz unterschiedlichen guten Antworten zu sehen, anstatt Wiederholungen.

    – SmacL

    1. Januar 2009 um 11:17 Uhr

  • Lassen Sie mich Ihren Titel verfeinern. “Speichereffizient” könnte auch Lese- und/oder Schreibgeschwindigkeit bedeuten.

    – Sebastian Mach

    8. April 2014 um 11:25 Uhr

Betrachten Sie in C auf einer viel einfacheren Ebene Folgendes:

  • Verwenden Sie #pragma pack(1), um Ihre Strukturen byteauszurichten
  • Verwenden Sie Vereinigungen, wenn eine Struktur verschiedene Datentypen enthalten kann
  • Verwenden Sie Bitfelder anstelle von Ints, um Flags und kleine Ganzzahlen zu speichern
  • Vermeiden Sie die Verwendung von Zeichenarrays mit fester Länge zum Speichern von Zeichenfolgen, implementieren Sie einen Zeichenfolgenpool und verwenden Sie Zeiger.
  • Beim Speichern von Verweisen auf eine aufgezählte Zeichenfolgenliste, z. B. Schriftartname, speichern Sie einen Index in der Liste statt der Zeichenfolge
  • Wenn Sie die dynamische Speicherzuweisung verwenden, berechnen Sie die Anzahl der erforderlichen Elemente im Voraus, um Neuzuweisungen zu vermeiden.

  • Ah, die ersten drei Punkte habe ich selbst ganz vergessen. Ich fand das zu offensichtlich und habe es übersehen. xD

    – Nachzügler

    1. Januar 2009 um 19:04 Uhr

  • Ja. Die obige Antwort wurde hinzugefügt, nachdem ich Ihre gelesen hatte, nimrodms & monjardins, und war da, um alle offensichtlicheren Auslassungen hinzuzufügen.

    – SmacL

    2. Januar 2009 um 9:01 Uhr

  • 1. Das kann mit einer Geschwindigkeitsstrafe einhergehen. Besser: Structs intelligent strukturieren: struct s { int i; Zeichen c; } statt struct s {char c; int i;} 2. Gewerkschaften sind sehr gut. 3. Bitfelder können schmerzhaft langsam sein. In acht nehmen

    – Mikeage

    23. Februar 2009 um 4:51 Uhr

  • @Mikeage – Ja, ich finde, dass Sie normalerweise entweder den Speicher oder die Geschwindigkeit optimieren, wobei die beiden gegensätzlich sind.

    – SmacL

    23. Februar 2009 um 7:35 Uhr

  • Die meisten eingebetteten Systeme verfügen über Anweisungen für Bitoperationen, daher ist Bitfield eine gute Wahl

    – phuklv

    16. April 2014 um 13:49 Uhr

Benutzeravatar von ChrisN
ChrisN

Einige Vorschläge, die ich bei der Arbeit mit eingebetteten Systemen als nützlich empfunden habe:

  • Stellen Sie sicher, dass alle Nachschlagetabellen oder andere konstante Daten tatsächlich mit deklariert sind const. Wenn const verwendet wird, können die Daten in einem Nur-Lese-Speicher (z. B. Flash oder EEPROM) gespeichert werden, andernfalls müssen die Daten beim Start in den RAM kopiert werden, und dies nimmt sowohl Flash- als auch RAM-Speicherplatz ein. Stellen Sie die Linker-Optionen so ein, dass eine Zuordnungsdatei erstellt wird, und untersuchen Sie diese Datei, um genau zu sehen, wo Ihre Daten in der Speicherzuordnung zugewiesen werden.

  • Stellen Sie sicher, dass Sie alle verfügbaren Speicherbereiche verwenden. Beispielsweise verfügen Mikrocontroller häufig über einen integrierten Speicher, den Sie verwenden können (auf den möglicherweise auch schneller zugegriffen werden kann als auf externes RAM). Sie sollten in der Lage sein, die Speicherbereiche zu steuern, denen Code und Daten zugewiesen werden, indem Sie Compiler- und Linker-Optionseinstellungen verwenden.

  • Um die Codegröße zu reduzieren, überprüfen Sie die Optimierungseinstellungen Ihres Compilers. Die meisten Compiler haben Schalter zur Optimierung der Geschwindigkeit oder Codegröße. Es kann sich lohnen, mit diesen Optionen zu experimentieren, um zu sehen, ob die Größe des kompilierten Codes reduziert werden kann. Und eliminieren Sie natürlich doppelten Code, wo immer dies möglich ist.

  • Überprüfen Sie, wie viel Stapelspeicher Ihr System benötigt, und passen Sie die Linker-Speicherzuweisung entsprechend an (siehe Antworten auf diese Frage). Um die Stack-Nutzung zu reduzieren, vermeiden Sie es, große Datenstrukturen auf dem Stack zu platzieren (unabhängig davon, welcher Wert von „groß“ für Sie relevant ist).

Stellen Sie sicher, dass Sie, wo immer möglich, Festkomma-/Integer-Mathematik verwenden. Viele Entwickler verwenden Fließkomma-Mathematik (zusammen mit der trägen Leistung und der großen Bibliotheken- und Speichernutzung), wenn einfache skalierte ganzzahlige Mathematik ausreicht.

  • Dies setzt voraus, dass dem System eine FPU fehlt und Gleitkommaoperationen emuliert werden müssen. Float- und Double-Werte benötigen jedoch in den meisten Fällen mehr Speicherplatz als Festkomma-Ganzzahlen, daher ist es hilfreich, letztere zu verwenden.

    – Nachzügler

    1. Januar 2009 um 19:07 Uhr

  • Den meisten Systemen fehlt die FPU. Ganze Zahlen!

    – jakobengblom2

    9. Januar 2009 um 8:14 Uhr

Benutzeravatar von Mike Dunlavey
Mike Dunlavey

Alles gute Empfehlungen. Hier sind einige Designansätze, die ich als nützlich empfunden habe.

  • Byte-Codierung

Schreiben Sie einen Interpreter für einen speziellen Bytecode-Befehlssatz und schreiben Sie so viel Programm wie möglich in diesen Befehlssatz. Wenn bestimmte Vorgänge eine hohe Leistung erfordern, machen Sie sie zu nativem Code und rufen Sie sie vom Interpreter aus auf.

  • Codegenerierung

Wenn sich ein Teil der Eingabedaten sehr selten ändert, könnten Sie einen externen Codegenerator haben, der ein Ad-hoc-Programm erstellt. Das ist kleiner als ein allgemeineres Programm, läuft schneller und muss keinen Speicherplatz für die sich selten ändernde Eingabe zuweisen.

  • Sei ein Datenhasser

Seien Sie bereit, viele Zyklen zu verschwenden, wenn Sie dadurch eine absolut minimale Datenstruktur speichern können. Normalerweise werden Sie feststellen, dass die Leistung nur sehr wenig darunter leidet.

Benutzeravatar von strager
Nachzügler

Höchstwahrscheinlich müssen Sie Ihre Algorithmen sorgfältig auswählen. Streben Sie Algorithmen an, die O(1) oder O(log n) Speicherverbrauch (dh niedrig) haben. Beispielsweise können kontinuierlich in der Größe veränderbare Arrays (z std::vector) benötigen in den meisten Fällen weniger Speicher als verkettete Listen.

Manchmal kann die Verwendung von Nachschlagetabellen für beide Codegrößen vorteilhafter sein und Geschwindigkeit. Wenn Sie nur 64 Einträge in einer LUT benötigen, sind das 16*4 Bytes für sin/cos/tan (Symmetrie verwenden!) im Vergleich zu einer großen sin/cos/tan-Funktion.

Kompression hilft manchmal. Einfache Algorithmen wie RLE lassen sich leicht komprimieren/dekomprimieren, wenn sie sequenziell gelesen werden.

Wenn Sie mit Grafiken oder Audio arbeiten, ziehen Sie unterschiedliche Formate in Betracht. Paletten- oder Bitpack*-Grafiken können ein guter Kompromiss für die Qualität sein, und Paletten können von vielen Bildern gemeinsam genutzt werden, wodurch die Datengröße weiter reduziert wird. Audio kann von 16 Bit auf 8 Bit oder sogar 4 Bit reduziert werden, und Stereo kann in Mono umgewandelt werden. Die Abtastraten können von 44,1 kHz auf 22 kHz oder 11 kHz reduziert werden. Diese Audiotransformationen reduzieren ihre Datengröße (und leider auch Qualität) erheblich und sind trivial (außer Resampling, aber dafür ist Audiosoftware da =]).

* Ich denke, Sie könnten dies unter Komprimierung setzen. Bitpacking für Grafiken bezieht sich normalerweise auf die Reduzierung der Anzahl von Bits pro Kanal, sodass jedes Pixel in zwei Bytes (z. B. RGB565 oder ARGB155) oder eins (ARGB232 oder RGB332) von den ursprünglichen drei oder vier (RGB888 bzw. ARGB8888) passt.

Benutzeravatar von nimrodm
nimrodm

Vermeiden Sie Speicherfragmentierung, indem Sie Ihren eigenen Speicherzuordner verwenden (oder vorsichtig den Zuordner des Systems verwenden).

Eine Methode ist die Verwendung eines „Slab-Allocators“ (siehe this Artikel zum Beispiel) und mehrere Speicherpools für Objekte unterschiedlicher Größe.

Benutzeravatar von Judge Maygarden
Richter Maygarden

Die Vorabzuweisung des gesamten Speichers (dh keine malloc-Aufrufe außer der Startinitialisierung) ist definitiv hilfreich für die deterministische Speichernutzung. Ansonsten bieten unterschiedliche Architekturen Techniken, um auszuhelfen. Beispielsweise bieten bestimmte ARM-Prozessoren einen alternativen Befehlssatz (Thumb), der die Codegröße fast halbiert, indem er 16-Bit-Befehle anstelle der normalen 32 Bit verwendet. Dabei wird natürlich Geschwindigkeit geopfert…

  • Bei komplizierten Algorithmen kann der ARM-Modus die Codegröße reduzieren. Für einfache Operationen kann der THUMB-Modus besser sein. Es hängt wirklich alles vom Kontext ab. Aber für allgemeinen Code stimme ich zu, dass THUMB kleiner als ARM ist.

    – Nachzügler

    1. Januar 2009 um 6:37 Uhr

  • Die Vorabzuweisung funktioniert gut für die Geschwindigkeit und Größe bestimmter Anwendungen. Für andere können sich die Größenanforderungen verschlechtern, da die App den Speicher normalerweise nicht modulübergreifend wiederverwenden kann.

    – SmacL

    1. Januar 2009 um 11:15 Uhr

1401460cookie-checkWas sind einige Best Practices zur Reduzierung der Speichernutzung in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy