it-swarm.com.de

Dürfen Compiler Realloc optimieren?

Ich bin auf eine Situation gestoßen, in der es unnötig wäre, unnötige Aufrufe von realloc zu erhalten. Es scheint jedoch, als würden weder clang noch gcc so etwas tun ( godbolt ). - Ich sehe zwar Optimierungen mit mehreren Aufrufen von malloc.

Das Beispiel:

void *myfunc() {
    void *data;
    data = malloc(100);
    data = realloc(data, 200);
    return data;
}

Ich habe erwartet, dass es auf Folgendes optimiert wird:

void *myfunc() {
    return malloc(200);
}

Warum optimiert es weder clang noch gcc? - Dürfen sie das nicht?

37
Julius

Dürfen sie das nicht?

Möglicherweise ist die Optimierung in diesem Fall jedoch auf die funktionalen Unterschiede der Ecken zurückzuführen.


Wenn 150 Byte zuordenbarer Speicher verbleiben,
data = malloc(100); data = realloc(data, 200); gibt NULL zurück, wobei 100 Byte verbraucht (und durchgesickert) sind und 50 übrig bleiben.

data = malloc(200); gibt NULL zurück, wobei 0 Byte verbraucht wurden (keine durchgesickert) und 150 übrig bleiben.

Eine andere Funktionalität kann in diesem engen Fall eine Optimierung verhindern.


Dürfen Compiler Realloc optimieren? 

Vielleicht - ich würde erwarten, dass es erlaubt ist. Es kann jedoch nicht den Effekt wert sein, den Compiler dahingehend zu verbessern, dass er feststellen kann, wann er es kann.

Erfolgreich malloc(n); ... realloc(p, 2*n) unterscheidet sich von malloc(2*n);, wenn ... möglicherweise einen Teil des Speichers eingestellt hat. 

Es könnte über den Entwurf dieses Compilers hinausgehen, um sicherzustellen, dass ... auch wenn leerer Code keinen Speicherplatz belegt hat.

25
chux

Ein Compiler, der seine eigenen, eigenständigen Versionen von Malloc/Calloc/Free/Realloc bündelt, könnte die angegebene Optimierung rechtmäßig durchführen, wenn die Autoren der Meinung waren, dass dies die Anstrengung wert war. Ein Compiler, der an extern bereitgestellte Funktionen kettet, kann solche Optimierungen dennoch durchführen, wenn er dokumentiert, dass er die genaue Reihenfolge der Aufrufe von Funktionen nicht als beobachtbaren Nebeneffekt betrachtet, aber eine solche Behandlung könnte etwas schwerer sein.

Wenn zwischen malloc () und realloc () kein Speicher zugewiesen oder freigegeben wird, ist die Größe von realloc () beim Ausführen von malloc () bekannt, und die Größe von realloc () ist größer als die Größe von malloc () Es kann sinnvoll sein, die Operationen malloc () und realloc () zu einer einzigen größeren Zuweisung zu konsolidieren. Wenn sich jedoch der Speicherzustand in der Zwischenzeit ändern könnte, kann eine solche Optimierung dazu führen, dass Vorgänge fehlschlagen, die erfolgreich ausgeführt werden sollten. Zum Beispiel die Reihenfolge gegeben:

void *p1 = malloc(2000000000);
void *p2 = malloc(2);
free(p1);
p2 = realloc(p2, 2000000000);

ein System verfügt möglicherweise nicht über 2000000000 Bytes für p2, bis p1 freigegeben wird. Wenn es den Code ändern würde in:

void *p1 = malloc(2000000000);
void *p2 = malloc(2000000000);
free(p1);

dies würde dazu führen, dass die Zuweisung von P2 fehlschlägt. Da der Standard niemals den Erfolg der Zuweisungsanforderungen garantiert, wäre ein solches Verhalten nicht konform. Andererseits wäre das Folgende auch eine "konforme" Implementierung:

void *malloc(size_t size) { return 0; }
void *calloc(size_t size, size_t count) { return 0; }
void free(void *p) {  }
void *realloc(void *p, size_t size) { return 0; }

Eine solche Implementierung kann als "effizienter" angesehen werden als die meisten anderen, aber man müsste eher stumm sein, um sie für sehr nützlich zu halten, außer vielleicht in seltenen Situationen, in denen die oben genannten Funktionen auf Codepfaden aufgerufen werden nie ausgeführt.

Ich denke, der Standard würde die Optimierung eindeutig zulassen, zumindest in Fällen, die so einfach sind wie in der ursprünglichen Frage. Selbst in Fällen, in denen Operationen fehlschlagen könnten, die andernfalls erfolgreich waren, würde der Standard dies dennoch zulassen. Der Grund, warum viele Compiler die Optimierung nicht durchführen, liegt höchstwahrscheinlich darin, dass die Autoren nicht der Meinung waren, dass die Vorteile ausreichen würden, um den Aufwand zu rechtfertigen, der erforderlich ist, um Fälle zu identifizieren, in denen dies sicher und nützlich ist.

10
supercat

Der Compiler darf mehrere Aufrufe von Funktionen optimieren, die als reine Funktionen betrachtet werden, d.

Die Frage ist also, ob realloc() eine reine Funktion ist oder nicht.

Der C11 Standard Committee Draft N1570 erklärt dies über die Funktion realloc:

7.22.3.5 Die Realloc-Funktion
...
2. Die Funktion realloc hebt das alte Objekt auf, auf das ptr zeigt, und gibt einen Zeiger auf ein neues Objekt zurück, dessen Größe durch die Größe festgelegt ist. Der Inhalt des neuen Objekts muss bis zur kleineren der neuen und der alten Größe mit dem des alten Objekts vor der Freigabe übereinstimmen. Alle Bytes im neuen Objekt, die über die Größe des alten Objekts hinausgehen, haben unbestimmte Werte.

Kehrt zurück
4. Die Funktion realloc gibt einen Zeiger auf das neue Objekt zurück (wobei may denselben Wert wie ein Zeiger auf das alte Objekt hat) oder einen Nullzeiger, wenn das neue Objekt nicht zugewiesen werden konnte.

Beachten Sie, dass der Compiler den Wert des Zeigers, der bei jedem Aufruf zurückgegeben wird, zur Kompilierzeit nicht vorhersagen kann.

Dies bedeutet, dass realloc() nicht als reine Funktion betrachtet werden kann und mehrere Aufrufe derselben vom Compiler nicht optimiert werden können.

4
P.W

Sie überprüfen jedoch nicht den Rückgabewert des ersten Malloc (), den Sie dann im zweiten Realloc () verwenden. Es könnte genauso gut NULL sein.

Wie könnte der Compiler die beiden Aufrufe zu einem einzigen Aufruf optimieren, ohne unberechtigte Annahmen über den Rückgabewert des ersten machen zu müssen?

Dann gibt es noch ein anderes mögliches Szenario. FreeBSD verwendet, um eine realloc() zu haben, die im Wesentlichen malloc + memcpy + den alten Zeiger frei machte. 

Angenommen, es sind nur noch 230 Byte freien Speicher verfügbar. In dieser Implementierung schlägt ptr = malloc(100) gefolgt von realloc(ptr, 200) fehl, eine einzelne malloc(200) schlägt jedoch fehl.

1
pizdelect

Ich verstehe, dass eine solche Optimierung möglicherweise verboten ist (insbesondere für den unwahrscheinlichen Fall, dass die Variable malloc erfolgreich ist, die folgende Variable realloc jedoch fehlschlägt).

Sie könnten annehmen, dass malloc und realloc immer erfolgreich sind (das ist gegen den C11-Standard, n1570 ; siehe auch meine Joke-Implementierung von malloc ). In dieser Hypothese (streng falsch, aber einige Linux-Systeme haben Speicherüberbelegung , um diese Illusion zu geben), wenn Sie GCC verwenden, schreiben Sie möglicherweise Ihr eigenes GCC-Plugin , um dies zu machen eine Optimierung.

Ich bin nicht sicher, ob es sich lohnt, ein paar Wochen oder Monate damit zu verbringen, ein solches GCC-Plugin zu codieren (in der Praxis sollte es wahrscheinlich sein, dass es manchmal mit etwas Code zwischen malloc und realloc umgeht, und dann ist es nicht so einfach, da man es charakterisieren muss und erkennen, welcher Zwischencode akzeptabel ist), aber diese Wahl liegt bei Ihnen.