2016-06-15 10 views
3

Ist es möglich, den GC für Delegierte zu vermeiden?Ist es möglich den GC für Delegierte zu vermeiden?

Ich baue ein Task-System. Ich habe N-Threads mit einer lokalen Task-Warteschlange. Eine Aufgabenwarteschlange ist im Grunde nur eine Array!Fiber tasks. Da davon abgeraten wird, Fasern an einen anderen Thread zu senden, sende ich einen Abschluss/Delegaten an einen Thread, erstelle die Faser von diesem Delegaten und lege sie in das Array tasks.

Jetzt sind die Delegaten, die ich sende, Delegaten, die Variablen erfassen.

//Some Pseudo code 

auto f = //some function; 
auto cell = Cell(...); 

auto del =() { 
    let res = f(); 
    cell.write(res); 
} 

send(del); 

}

Nun Zelle ist Speicher zugeordnet und mit einem Atom Zähler synchronisiert. Ich kann dann prüfen, ob der Atomzähler von cell0 erreicht hat, wenn ich es gelesen habe, kann ich sicher davon lesen.

Das Problem ist, dass Delegaten, die Variablen erfassen, die Variablen auf dem GC zuweisen. Jetzt zuteile ich nur einen Zeiger und es ist wahrscheinlich kein großes Problem, aber ich möchte immer noch den GC vermeiden.

Wie würde ich das tun?

Antwort

8

Sie wissen vielleicht schon alles, aber das ist ein bisschen eine FAQ, also werde ich ein paar Details schreiben.

Zunächst wollen wir verstehen, was ein Delegat ist. So wie ein Slice nur ein C-Datenzeiger ist, der mit einer Länge gepaart ist, ist ein Delegat nur ein C-Datenzeiger, der mit einem Funktionszeiger gepaart ist. Diese werden zusammen auf Funktionen sie erwarten, als ob sie definiert wurde

struct d_delegate { 
    void* ptr; // yes, it is actually typed void*! 
    T* funcptr; // this is actually a function pointer 
}; 

(Beachten Sie, dass die Tatsache, dass es nur eine Daten ptr da drin ist der Grund für einige Compiler-Fehler, wenn Sie versuchen, eine verschachtelte Delegierten zu nehmen ! innerhalb einer Klasse-Methode)

die void* ist, was mit den Datenpunkten und wie mit einer Scheibe kann aus einer Vielzahl von Orten kommen:

Object obj = new Object(); 
string delegate() dg = &obj.toString; 

an diesem Punkt dg.ptr Punkte obj, die zufällig ein Garbage Collected Class Object ist, aber nur, weil ich es oben beschrieben habe new.

struct MyStruct { 
    string doSomething() { return "hi"; } 
} 

MyStruct obj; 

string delegate() dg = &obj.doSomething; 

In diesem Fall obj Leben auf dem Stapel durch, wie ich es oben zugeordnet, so auch die dg.ptr zu diesem temporären Objekt zeigt.

Ob etwas ein Delegierter ist oder nicht, sagt nichts über das verwendete Speicherzuweisungsschema aus - das ist wohl gefährlich, weil ein übergebener Delegat auf ein temporäres Objekt verweisen könnte, das verschwindet, bevor Sie damit fertig sind! (Das ist der Hauptgrund, warum GC übrigens benutzt wird, um solche Bugs zu vermeiden.)

Also, wenn Delegierte von irgendeinem Objekt kommen können, warum wird angenommen, dass sie so viel GC sind? Nun, der automatisch generierte Abschluss kann lokale Variablen in ein GC-Segment kopieren, wenn der Compiler der Meinung ist, dass die Lebensdauer des Delegaten länger ist als die äußere Funktion.

void some_function(void delegate() dg); 

void foo() { 
    int a; 
    void nested() { 
     a++; 
    } 
    some_function(&nested); 
} 

Hier wird der Compiler die Variable a auf ein GC-Segment kopieren, da es some_function davon ausgeht, eine Kopie davon halten und will use-after-free Bugs verhindern (die Schmerzen sind, wie es zu debuggen häufig führt zu Speicherbeschädigung!) sowie Speicherlecks.

Wenn Sie jedoch den Compiler versprechen, dass Sie es sich richtig machen werde durch die scope Schlüsselwort auf der Delegierten Definition verwenden, wird es Sie vertrauen, und die Einheimischen verlassen genau dort, wo sie sind:

void some_function(scope void delegate() dg); 

Keeping der Rest das gleiche, es wird keine Kopie mehr zuordnen. Auf der Seite der Funktionsdefinition ist es am besten, weil Sie als Autor der Funktion sicherstellen können, dass Sie keine Kopie behalten. obwohl

Auf der Verwendungsseite, können Sie es auch Umfang beschriften:

void foo() { 
    int a; 
    void nested() { 
     a++; 
    } 
    // this shouldn't allocate either 
    scope void delegate() dg = &nested; 
    some_function(&dg); 
} 

Also, der einzige Mal, Speicher automatisch vom GC zugeordnet wird, wenn lokale Variablen durch eine verschachtelte Funktion verwendet werden, die seine Adresse hat genommen ohne das Schlüsselwort scope.

Beachten Sie, dass die () => whatever und () { return foo; } Syntax nur Abkürzungen für eine benannte verschachtelte Funktion sind, deren Adresse automatisch vergeben wird, so dass sie genauso funktionieren wie oben. dg = {a++;}; ist dasselbe wie dg = &nested; oben.

Daher ist der entscheidende Vorteil für Sie, dass wenn Sie einen Delegaten manuell zuweisen möchten, Sie nur ein Objekt manuell zuweisen müssen und einen Delegaten von einer seiner Methoden machen, anstatt Variablen automatisch zu erfassen! Aber, Sie müssen die Lebensdauer überwachen und sie richtig freigeben. Das ist der schwierige Teil.

So für Ihr Beispiel:

auto del =() { 
    let res = f(); 
    cell.write(res); 
}; 

Sie könnten übersetzen, dass in:

struct Helper { 
    T res; 
    void del() { 
     cell.write(res); 
    } 
} 

Helper* helper = malloc(Helper.sizeof); 
helper.res = res; // copy the local explicitly 

send(&helper.del); 

Dann wird auf der Empfangsseite, nicht vergessen free(dg.ptr);, wenn Sie, so dass Sie don fertig sind Leck es nicht.

Oder, noch besser, wenn Sie send ändern können, um nur Helper Objekte zu nehmen, müssen Sie es überhaupt nicht zuweisen, Sie können es nur nach Wert übergeben.


Es kommt auch zu mir, dass Sie einige anderen Daten in diesem Zeiger packen könnten andere Daten an Ort und Stelle passieren, aber das abi Hacking und möglicherweise undefiniertes Verhalten würde. Versuchen Sie es, wenn Sie spielen möchten :)

+0

Große Erklärung, danke. –