Fehler bei der CPython-Zeichenfolge-Additionsoptimierung

9

Die Frage

Warum, in CPython,

%Vor%

nehmen Sie lineare Zeit, aber

%Vor%

nehmen Sie quadratische Zeit?

Beweis:

%Vor% %Vor%

Was ich weiß

CPython verfügt über eine Optimierung für die String-Addition, wenn die hinzuzufügende Zeichenfolge eine Referenzzahl von 1 hat.

Dies liegt daran, dass Strings in Python unveränderlich sind und daher normalerweise nicht bearbeitet werden können. Wenn mehrere Referenzen auf eine Zeichenfolge vorhanden sind und diese mutiert ist, wird beide Referenzen die geänderte Zeichenfolge anzeigen. Dies ist offensichtlich nicht gewollt, so dass Mutation nicht mit mehreren Referenzen auftreten kann.

Wenn nur ein Verweis auf die Zeichenfolge vorhanden ist, ändert die Änderung des Werts nur die Zeichenfolge für diese eine Referenz, die geändert werden soll. Sie können testen, dass dies eine wahrscheinliche Ursache ist:

%Vor%

Ich bin nicht sicher, warum der Faktor nur 30x statt der erwarteten 100x ist, aber ich glaube, dass es Overhead ist.

Was ich nicht weiß

Warum erstellt die Listenversion also zwei Referenzen? Ist das sogar, was die Optimierung verhindert?

Sie können überprüfen, ob normale Objekte nicht anders behandelt werden:

%Vor%     
Veedrac 04.06.2014, 14:28
quelle

2 Antworten

9

Bei dem auf Listen beruhenden Ansatz wird eine Zeichenkette von Index 0 der Liste genommen und modifiziert, bevor sie zu der Liste bei Index 0 zurückgestellt wird Für diesen kurzen Moment hat der Interpreter immer noch die alte Version von string in der Liste und kann keine Platzmodifikation durchführen.
Wenn Sie sich die Python-Quelle ansehen, werden Sie sehen, dass es sie gibt keine Unterstützung für das Ändern des Elements der Liste an Ort und Stelle. Also das Objekt (String in diesem Fall) muss aus der Liste abgerufen, modifiziert und dann zurückgestellt werden.
Mit anderen Worten: list type ist völlig unabhängig von der Unterstützung von str für += operator.

Und betrachten Sie den folgenden Code:

%Vor%

Der Wert von l ist ['abcmno', 'jkl'] , was beweist, dass 'abc' aus der Liste genommen wurde, dann wurde nasty() ausgeführt, indem der Inhalt der Liste geändert wurde, die Strings 'abc' und 'mno' wurden verknüpft und resultierten wurde l[0] zugewiesen. Wenn nasty() vor dem Zugriff auf l[0] ausgewertet wurde, um es an Ort und Stelle zu ändern, wäre das Ergebnis 'ghimno' .

    
ElmoVanKielmo 04.06.2014, 14:52
quelle
6
  

Warum erstellt die Listenversion also zwei Referenzen?

In l[0] += ' ' ist eine Referenz in l[0] . Eine Referenz wird vorübergehend erstellt, um += on auszuführen.

Hier sind zwei einfachere Funktionen, um den Effekt zu zeigen:

%Vor%

Demontage sie gibt

%Vor%

In f , die Anweisung BINARY_SUBSCR (Schneiden) setzt l[0] an der Spitze des VM-Stacks. DUP_TOPX dupliziert die obersten n Elemente auf dem Stapel . Beide Funktionen (siehe ceval.c ) erhöhen die Anzahl der Referenzen. DUP_TOPX ( DUP_TOP_TWO in Py3) tut dies direkt, während BINARY_SUBSCR PyObject_GetItem verwendet. . Also, die Referenzzahl der Zeichenfolge ist jetzt mindestens drei.

g hat dieses Problem nicht. Es erstellt einen zusätzlichen Verweis, wenn das Element mit LOAD_FAST verschoben wird, was einen Refcount von zwei ergibt, die minimale Anzahl für ein Element im VM-Stack, sodass es die Optimierung durchführen kann.

    
Fred Foo 04.06.2014 15:07
quelle