1. Diese Seite verwendet Cookies. Wenn du dich weiterhin auf dieser Seite aufhältst, akzeptierst du unseren Einsatz von Cookies. Weitere Informationen

Free Element Array - Feedback für Algorithmus

Dieses Thema im Forum "Coders Talk" wurde erstellt von cwriter, 24. August 2017.

  1. cwriter

    cwriter Erfahrenes Mitglied

    Hi

    Ich arbeite an einem kleinen OS und brauche dafür eine Datenstruktur, um freie Seiten (Pages) zu halten, um zu wissen, welche physischen Adressen verfügbar sind.

    Zwei Datenstrukturen sind mir für diesen Zweck bekannt:
    Free-List: Freie Pages sind in einer (Double Linked) Liste gespeichert; eine Freigabe fügt die page wieder in die Liste ein, eine Allozierung nimmt einfach ein Element aus der Liste heraus.
    alloc: O(1)
    free: O(1)
    Speicher: O(3 * n) (1 prev, 1 next, 1 value).

    Hierbei störte mich jedoch der Speicheraufwand gewaltig (mehr als unbedingt nötig, also O(n), will ich nicht aufwenden).

    Free-Bitmap/Array:
    alloc: O(n)
    free: O(1)
    Speicher: O(n) (yippie!)

    Die alloc-Kosten waren mir da aber wesentlich zu teuer.
    Also eine kleine Modifikation des Ganzen:
    Zusätzlich werden zwei Sentinels gehalten: min_free und max_free.

    Der Algorithmus:
    Init:
    array = {0}^n //1 == used, 0 == free
    min_free = 0;
    max_free = n;

    Code (Text):
    1. alloc:
    2. if(min_free >= max_free)
    3.     fail();
    4.  
    5. array[min_free] = 1;
    6. while (++min_free < max_free)
    7.     if (array[min_free] == 0)
    8.         break;
    9.  
    10. if(min_free >= max_free)
    11.     max_free = 0;
    Code (Text):
    1. free: index i
    2.  
    3. array[i] = 0;
    4. min_free = min(min_free, i);
    5. max_free = max(max_free, i+1);
    Bei der Analyse tue ich mir ein bisschen schwer, daher gehe ich die einzelnen Fälle durch:
    1) max_free == n
    Das bedeutet, dass der gesamte obere Bereich des Arrays (oberhalb von min_free) frei ist, ein alloc also O(1)-komplex ist
    2) max_free == 0
    Das bedeutet, dass es gar keinen Platz mehr hat (implizit: min_free == n). Diese Tatsache ist in O(1) feststellbar.

    Die restlichen Fälle sind nur erreichbar, wenn mindestens 1 Element freigegeben worden ist.
    Beweis: min_free wird nie verkleinert werden, da monoton wachsend in alloc.

    3) Nach der ersten Freigabe in einem nicht-vollen Array
    Da alles in [min_free; max_free[ frei ist, und alles in [0, min_free[ besetzt, kann nur min_free verändert werden.

    Nun gibt es 2 Optionen:
    1. Es folgt ein alloc.
      min_free wird sofort wieder besetzt und in O(n) nach vorne iteriert, Kosten von alloc also prev - i,
      wobei prev = min_free vor dem free(i) und i der Parameter vom free ist.
    2. Es folgt ein weiteres free.
      min_free bleibt dabei entweder gleich oder wird noch kleiner. Fragmentierung beginnt.
    4) Nach der ersten Freigabe in einem vollen Array:
    min_free + 1 = max_free

    Nun gibt es 2 Optionen:
    1. Es folgt ein alloc.
      min_free wird sofort wieder besetzt, aber nur um 1 Schritt iteriert. max_free wird wieder 0. Kosten: O(1)
    2. Es folgt ein weiteres free.
      min_free bleibt dabei entweder gleich oder wird noch kleiner. Späteres Einfügen kostet wieder O(n).

    Meine Frage ist nun:
    Ist das ein guter Algorithmus?

    Die Analyse zeigt klar, dass er in nur wenigen Spezialfällen sehr gut ist (aus Sicht von alloc, free ist durch Random Access ja immer O(1), ausser, man amortisiert):
    • Der Array ist voll
    • Der Array hat genau 1 freies Element
    • Der Array hat keine Fragmentierung. Eine minimale Verbesserung wäre, min_free und max_free als modulo n anzugeben, damit "bitonische" Sequenzen möglich sind (i.e. 0 <= block_1 <= block_2 <= block_3 <= n, wobei block_1 und block_3 free sind und block_2 besetzt, was im angegebenen Algorithmus eine Fragmentierung darstellt, wenn sie auch nur Θ(|block_2|) kostet)
    Weitere Feststellungen:
    • Wenig Fragmentierung ist gut, da alloc: Θ(next_free - min_free)
      (alloc: O(max_free - min_free), falls Platz vorhanden, O(1) sonst)
    • Am teuersten ist daher {0, 1...1, 0}, da n-2 Schritte gemacht werden müssen.
      Schlimmer wird es, wenn dann immer wieder "free(0); alloc" folgt (O(n) für alloc, sogar schlechter als einfacher Array-Ringbuffer (O(n/2). Allerdings kann dieses Verhalten durch die oben genannte Optimierung auch für diesen Algorithmus hergestellt werden).

    Die Fragen, die sich nun stellen, sind:
    1. Wie wahrscheinlich sind diese Spezialfälle?
    2. Kann man diese Spezialfälle irgendwie häufiger auftreten lassen?
    3. Gibt es generell bessere Algorithmen für diesen Zweck?
    4. Wie teuer ist dieser Algorithmus im Durchschnitt? Kann er als effizient betrachtet werden?
    5. Wäre es sinnvoll, das setzen der Indizes lazy zu machen, also die Indizes nur setzen, wenn durch eine Löschung keine Fragmentierung entsteht, dafür eine dirty-flag zu setzen und wenn der Array voll wird und die dirty-flag gesetzt ist, die Indizes auf min_index = 0 und max_index = n zurückzusetzen und nochmals zu suchen?
      (Meine Angst hier ist, dass dadurch andere Nachteile entstehen, die mir gerade nicht einfallen)
    6. Gibt es anderes Feedback?


    Gruss
    cwriter
     
Die Seite wird geladen...