2012-05-29 3 views
7

Ich bin auf der Suche nach einem Heap-Allocation-Algorithmus in C für einen Speicher-Constrained-Mikrocontroller zu implementieren. Ich habe meine Suche auf 2 Optionen beschränkt, die mir bekannt sind, aber ich bin sehr offen für Vorschläge, und ich suche Rat oder Kommentare von jemandem mit Erfahrung in diesem Bereich.Fragmentierung-resistente Microcontroller Heap-Algorithmus

Meine Anforderungen:

-Geschwindigkeit zählt auf jeden Fall, aber es ist zweitrangig.

-Timing Determinismus ist nicht wichtig - jeder Teil des Codes, der deterministische Worst-Case-Timing erfordert, hat seine eigene Zuweisungsmethode.

-Die wichtigste Voraussetzung ist Fragmentierung Immunität. Auf dem Gerät wird eine Lua-Skript-Engine ausgeführt, die eine Reihe von Zuweisungsgrößen erfordert (schwer auf den 32-Byte-Blöcken). Die Hauptanforderung ist, dass dieses Gerät für eine lange Zeit ausgeführt wird, ohne seinen Heap in einen unbrauchbaren Zustand zu versetzen. Hinweis

auch:

-Für Referenz, wir sprechen über ein Cortex-M und PIC32-Teile, mit Speicher im Bereich von 128K und 16MB oder Speicher (mit einem Schwerpunkt auf dem unteren Ende).

-Ich möchte nicht den Heap des Compilers verwenden, weil 1) Ich möchte konsistente Leistung über alle Compiler und 2) ihre Implementierungen sind in der Regel sehr einfach und sind die gleichen oder schlechter für die Fragmentierung.

-Double indirekte Optionen sind out wegen der großen Lua-Code-Basis, die ich nicht grundlegend ändern und revalidate wollen.

Meine Begünstigungs Ansätze So Far:

1) Haben Sie einen binären Kumpel allocator und Speichernutzungseffizienz opfern (Rundung auf eine Leistung von 2 Größe nach oben). -dies würde (wie ich es verstehe) einen binären Baum für jede Reihenfolge/Ablage erfordern, um freie Knoten zu speichern, sortiert nach Speicheradresse für einen schnellen Buddy-Block-Lookup zum erneuten Speichern.

2) Zwei Binärbäume für freie Blöcke haben, eine sortiert nach Größe und eine nach Speicheradresse sortiert. (Alle binären Baumverknüpfungen sind im Block selbst gespeichert.) -allocation würde am besten mit einem Nachschlagen in der Tabelle nach Größe und dann entfernen Sie diesen Block aus dem anderen Baum durch Adresse -deallocation würde benachbarte Blöcke nach Adresse für suchen wiedererhalten

-Beide Algorithmen würden auch erfordern, eine Zuweisungsgröße vor dem Start des zugewiesenen Blocks zu speichern, und Blöcke gehen als eine Potenz von 2 minus 4 (oder 8 abhängig von der Ausrichtung). (Es sei denn, sie speichern einen Binärbaum an anderer Stelle, um Zuordnungen nach Speicheradresse zu verfolgen, was ich nicht für eine gute Option halte)

- Beide Algorithmen erfordern höhenausgleichenden Binärcode.

-Algorithmus 2 hat nicht die Anforderung, Speicher durch Aufrunden auf eine Zweierpotenz zu verschwenden.

- In jedem Fall werde ich wahrscheinlich eine feste Bank von 32-Byte-Blöcken durch verschachtelte Bitfelder zugewiesen haben, um Blöcke dieser Größe oder kleiner zu entladen, die gegen externe Fragmentierung immun wären.

Meine Fragen:

-Ist es einen Grund, warum Ansatz 1 mehr immun gegen Fragmentierung als Ansatz wäre 2?

- Gibt es irgendwelche Alternativen, die ich vermisse, die den Anforderungen entsprechen könnten?

+0

Das Buddy-System muss keinen Binärbaum verwenden, um den möglichen Buddy eines Blocks zu finden, wenn er freigegeben wird. Sie können die Adresse eines Kontakts aus der Adresse des ursprünglichen Blocks ermitteln. Dann überprüfen Sie ein bisschen, ob dieser Freund frei ist oder benutzt wird. Sie können dieses Bit an den Anfang des Bereichs setzen - also würden Sie nicht wirklich 32 Bytes bekommen, sondern vielleicht 31 verwendbare Bytes. Sie können dies ändern, indem Sie ein separates Bitmap für free/used verwenden. Knuth beschreibt eine Version, die Tag-Bits für freie/gebrauchte verwendet – mcdowella

+0

Yah, ich dachte gerade darüber nach. Ich denke, 2GB Blockgröße ist wahrscheinlich gut genug :) Was ich denke, würde funktionieren, um den Binär-Baum für freie Knoten loszuwerden wäre, die freien Knoten jedes bin/order in einer doppelt verknüpften zirkulären Liste zu speichern, und nur die Buddy's verwenden Zeiger, um den nächsten und vorherigen Knoten zu verketten (und den Kopf ptr um einen Knoten voranzutreiben, wenn er auf den entfernten Knoten zeigt). Auf diese Weise gibt es überhaupt keine Suche, um einen kostenlosen Kumpel zu finden und zu entfernen. Also keine Binärbaumimplementierung. Gedanken? –

+0

müssen Sie wirklich zuordnen? Eingebettete Systeme wie diese weisen in der Regel keine festen Strukturen zu (außer natürlich Stack, auf den Sie achten müssen). fügt etwas zu viel Risiko zu etwas hinzu, das felsenfest sein soll. –

Antwort

1

Wenn Blockgrößen nicht auf Potenzen von zwei oder einem Äquivalent (*) aufgerundet werden, erzeugen bestimmte Folgen von Zuweisung und Freigabe eine im Wesentlichen unbegrenzte Menge an Fragmentierung, selbst wenn die Anzahl der nicht permanenten kleinen Objekte bei Jede gegebene Zeit ist begrenzt. Ein Binary-Buddy-Allokator wird dieses spezielle Problem natürlich vermeiden. Andernfalls, wenn man eine begrenzte Anzahl von gut verwandten Objektgrößen verwendet, aber kein "binäres Buddy" -System verwendet, muss man möglicherweise noch einige Urteile treffen, um zu entscheiden, wo neue Blöcke zugewiesen werden sollen.

Ein anderer zu berücksichtigender Ansatz ist die Verwendung verschiedener Zuweisungsmethoden für Dinge, von denen erwartet wird, dass sie permanent, temporär oder halbpersistent sind. Fragmentierung verursacht oft den größten Ärger, wenn temporäre und permanente Dinge auf dem Heap verschachtelt werden. Ein derartiges Verschachteln zu vermeiden, kann die Fragmentierung minimieren.

Schließlich weiß ich, dass Sie nicht wirklich doppel-indirekte Zeiger verwenden möchten, aber die Objektverschiebung zuzulassen, kann Fragmentierungs-Probleme stark reduzieren. Viele von Microsoft abgeleitete Mikrocomputer-BASICs verwendeten einen durch Müll gesammelten Saitenhaufen; Microsofts Garbage Collector war wirklich schrecklich, aber sein String-Heap-Ansatz kann mit einem guten verwendet werden.

+0

Sehr hilfreich in der Tat! Sprechen Sie mit Zweierpotenzen oder Äquivalent über Fibonacci-Freunde usw.? Können Sie mir ein einfaches Beispiel für ein Muster geben, das die Stärke eines binären/anderen Buddy-Systems zeigt? Ich kann nicht scheinen, selbst mit mir zu kommen ... –

+0

Auch, wie für doppelt indirekt, würde dies die vollständige Umschreibung der Lua-Engine zur Unterstützung der doppelten indirekten Zuteilung beinhalten ... vielleicht gibt es ein Juwel, das darauf wartet geschrieben zu werden. So oder so, das wäre eine beängstigende Menge an Validierung. Wie für das Betriebssystem verwendet es eine Form von Double-Indirect in der Handles-Tabelle und Pools von identisch großen Puffern für die Pufferung von Streams, also ist es wirklich nur die Lua, die mir Sorgen bereitet. Wie simuliert/testet/quantifiziert man Speicherraumfragmentierung? –

+0

Ein Vorteil der Macht von zwei Freunden ist, dass einige Benutzer von Speicher die Macht von zwei Blockgrößen benötigen - Sie erwähnten, dass Lua 32-Byte-Blöcke wollte, und einige eingebettete Systeme könnten FFTs mit der Macht von zwei Blockgrößen machen wollen. Wenn du tatsächlich alle Power-2-Buddy-Blockgrößen nutzen kannst (und nicht ein bisschen freien/gebrauchten widmen musst), dann passt das ziemlich gut - deshalb habe ich eine Demo von einem Buddy-System mit externem Free/Bitmap verwendet, um zu zeigen, dass es möglich ist. – mcdowella

1

Sie können einen (nie für echte) Buddy-System-Allokator bei http://www.mcdowella.demon.co.uk/buddy.html, mit meinem Segen für jeden Zweck, den Sie mögen. Aber ich glaube nicht, dass Sie ein Problem haben, das einfach durch das Einstecken eines Speicherzuordners gelöst werden kann. Die Systeme mit langer Laufzeit und hoher Integrität, mit denen ich vertraut bin, haben eine vorhersehbare Ressourcennutzung, die in mehr als 30 Seiten für jede Ressource beschrieben wird (hauptsächlich CPU- und E/A-Bus-Bandbreite - Speicher ist einfach, da sie beim Start immer dieselbe Menge zuweisen und dann nie wieder).

In Ihrem Fall kann keiner der üblichen Tricks - statische Zuweisung, freie Listen, Zuordnung auf dem Stapel, funktionieren, denn - zumindest wie beschrieben - haben Sie ein Lua interpretiert im Hintergrund schwebend bereit zu tun Wer weiß was zur Laufzeit - was ist, wenn es nur in eine Schleife gerät, die Speicher reserviert, bis sie ausgeht?

Können Sie die Speicherbelegung in zwei Abschnitte trennen - traditioneller Code, der fast alles, was er beim Start benötigt, und nie wieder verteilt, und erweiterbaren Code (z. B. Lua) zuzuteilen, was es benötigt, wenn es benötigt wird bleibt nach der statischen Zuweisung übrig? Könnten Sie dann einen Neustart oder eine Art Aufräumung des entbehrlichen Codes auslösen, wenn es seinen gesamten Speicherbereich belegt oder fragmentiert, ohne den traditionellen Code zu stören?

+0

Das ist ein sehr guter Punkt: einen geplanten Neustart der Lua-Engine zu entwerfen, von dem ich anfange zu denken, dass er unvermeidlich ist. Und ja, die Lua-Zuweisung ist vollständig von anderer Speicherzuweisung in Sandboxed. Ich kann die Fragmentierungsimmunität für alle anderen dynamischen Speicherzuweisungen sicherstellen. Ich kann einfach nicht garantieren, dass selbst der bekannte Lua-Code mit reichlich Ersatzhaufen sich nicht langsam zu Tode brodelt. Ich vermutete, dass ich den gesamten Inhalt der Lua-Umgebung von einer Lua-Engine zur anderen serialisieren und migrieren könnte, aber das wird irgendwie verrückt. –