Translate: 
EnglishFrenchGermanItalianPolishPortugueseRussianSpanish

StringBuilder w PHP

Jak dobrze wiadomo, PHP nie jest wyposażony w mechanizm typu StringBuilder znany z innych języków jak C# lub JAVA. W Internecie znaleźć można wiele dyskusji na to, czy taka funkcjonalność jest w ogóle do czegoś potrzebna, oraz gotowe implementacje jako dowody na to, że wydajność nie ulega zmianie.

Ja spróbuję przedstawić dowody odwrotne: mechanizm StringBuilder można w PHP zaimplementować, co korzystnie wpływa na wydajność skryptu.

Czemu zwykłe sklejanie stringów jest powolne?

Głównym powodem utraty wydajności w przypadku konkatenacji stringów jest fakt, że PHP przy tworzeniu tekstu wynikowego musi zaalokować dodatkową pamięć przechowującą wynik takiej operacji. W niektórych przypadkach sytuację pogarsza także wewnętrzne operowanie przez język PHP na strukturach typu zval.

O ile w przypadku niewielkiej liczby konkatenacji narzut jest niewielki, to przy intensywnych działaniach na stringach przyczyniających się do zmiany ich ostatecznej długości, różnice w wydajności stają się odczuwalne.

Czemu gotowe implementacje StringBuildera nie działają?

Odpowiedź jest prosta: twórcy tych mechanizmów nie próbują zrozumieć, co dzieje się wewnątrz silnika PHP. Gotowe rozwiązania polegają przede wszystkim na prostych operacjach na tablicach (które też są wolne), buforowaniu wyjścia przez ob_start() (co dla PHP jest identyczne jak zwykła konkatenacja), czy używaniu funkcji typu sprintf() i podobnych.

Prosty przykład, który działa

W celu wykazania różnic w wydajności między różnymi algorytmami, na początek przedstawiam podręcznikowy przykład konkatenacji stringów oraz czas jego wykonania:

< ?php
 
$result = "";
$max = 16000;
 
$start = microtime(true);
 
for($i = 0; $i < $max; $i++) {
    $result.= "a";
}
 
printf("GENERATED %d BYTES OF DATA IN %f SEC", strlen($result), microtime(true) - $start);

I rezultat wykonania:

GENERATED 16000 BYTES OF DATA IN 0.001739 SEC

A oto kod najprostszego skryptu symulującego częściowo funkcjonalność StringBuilder'a (prealokującego pamięć jeszcze przed jej użyciem):

< ?php
 
$max = 16000;
$result = str_pad("", $max, chr(0));
 
$start = microtime(true);
 
for($i = 0; $i < $max; $i++) {
    $result[$i] = "a";
}
 
printf("GENERATED %d BYTES OF DATA IN %f SEC", strlen($result), microtime(true) - $start);

Oraz wynik wykonania:

GENERATED 16000 BYTES OF DATA IN 0.001482 SEC

Jak widać, różnica jest spora. Wystarczyło wcześniej zainicjować zmienną $result nadając jej od razu końcową wielkość;

Co szczególnie wymaga takiej funkcjonalności?

Okazuje się, że opisany przeze mnie problem z wydajnością operacji na stringach jest szczególnie dotkliwy w przypadku kompilatora HipHop for PHP (artykuł na ten temat można przeczytać tutaj).

Tagi: , , ,

Dodaj odpowiedź