奇特なブログ

「殊勝に値する行いや心掛け」を意味する、奇特な人になる為のブログです

文字列連結、どれが最適?

えっと、先日某所より
「ループ内では、「.=」で文字列連結しないでarrayの要素に一つずつ値を突っ込んで、ループ抜けた後にimpludeせよ」
的なお話をいただいたのですが、
「確かに速いかもしれないけど、メモリ的にはどうよ?」とも思いましたので、
これはちょっと検証してみましょうという記事です。
検証に使ったPHPのバージョンは、5.4.8です。

じゃあ、まずは「.=」での連結から。

-----------------------------------------------------------------------------------------


<?php

function diff_microtime($start, $end)
{
list($start_m, $start_t) = explode(' ', $start);
list($end_m, $end_t) = explode(' ', $end);
return ((float)$end_t + (float)$end_m) - ((float)$start_t + (float)$start_m);
}

var_dump(memory_get_usage());
var_dump(memory_get_usage(true));
var_dump(memory_get_peak_usage());
var_dump(memory_get_peak_usage(true));

$start = microtime();

$str = '';
for ($i = 0; $i < 100000; $i++)
{
$str .= 'a';
}

$end = microtime();

echo diff_microtime($start, $end) . "\n";

var_dump(memory_get_usage());
var_dump(memory_get_usage(true));
var_dump(memory_get_peak_usage());
var_dump(memory_get_peak_usage(true));

-----------------------------------------------------------------------------------------

実行結果は以下の通り。

int(228664)
int(262144)
int(231608)
int(262144)
0.016595125198364
int(329440)
int(524288)
int(332456)
int(524288)

次に、「= 変数 . 連結したい文字列」の連結。
さっきと同じソースの箇所は省略しています。

-----------------------------------------------------------------------------------------


〜略〜

$str = '';
for ($i = 0; $i < 100000; $i++)
{
$str = $str . 'a';
}

〜略〜

-----------------------------------------------------------------------------------------

実行結果は、

int(228712)
int(262144)
int(231608)
int(262144)
0.68254494667053
int(329480)
int(786432)
int(465864)
int(786432)

う〜ん、明らかに遅いですねぇ。ということで却下かと。
で、ちょっと気になったのは、まぁ本題と関係ないんですけど、
ループを抜けた後の「memory_get_usage(true)」と「memory_get_peak_usage()」の値。
まず前者については、memory_get_usage()の値はほぼ同じなのに、
なんで、こんなに差異が生じてしまっているのかと。
後者は、memory_get_peak_usage(true)の値がほぼ同じなのに、
なんで、こんなに差異が・・・以下略。
う〜ん、分からないですねぇ。マニュアルにはemalloc()と書いてあるので、
C言語の知識が必要かもしれませんね。
まぁ、勉強ですね。
最近は、師匠も教えてくれなくなってきましたしねぇw

次は、こんな形。

-----------------------------------------------------------------------------------------


〜略〜

$str = '';
for ($i = 0; $i < 100000; $i++)
{
$str = "{$str}a";
}

〜略〜

-----------------------------------------------------------------------------------------

実行結果は、

int(228720)
int(262144)
int(231608)
int(262144)
0.53206992149353
int(329488)
int(786432)
int(565888)
int(786432)

これまた遅いですね。
次は、array_pushを使ったケース。

-----------------------------------------------------------------------------------------


〜略〜

$arr = array();
for ($i = 0; $i < 100000; $i++)
{
array_push($arr, 'a');
}

$str = implode('', $arr);

〜略〜

-----------------------------------------------------------------------------------------

実行結果。

int(229216)
int(262144)
int(231608)
int(262144)
0.071195125579834
int(18178848)
int(18612224)
int(18183968)
int(18612224)

うん、確かに遅くはないですけど、やっぱりメモリが〜ですね。
最後に、array_pushを使わない配列への要素の追加のケース。

-----------------------------------------------------------------------------------------


〜略〜

$arr = array();
for ($i = 0; $i < 100000; $i++)
{
$arr[] = 'a';
}

$str = implode('', $arr);

〜略〜

-----------------------------------------------------------------------------------------

実行結果。

int(229088)
int(262144)
int(231608)
int(262144)
0.1010959148407
int(18179280)
int(18612224)
int(18181992)
int(18612224)

array_pushを使った時と、殆ど変わらないですね。

ということで結論。
「.=」での文字列連結が、速度的にもメモリ的にも一番良いと思います。
まぁ、「unsetすれば配列使っても問題ないだろ」ってツッコミに対しては、
「unsetを書き忘れたら?」って反論で終わりだと思いますしね。
書かなくて問題ないなら、書かない方が良いでしょ?

あと、最初に書いた発言は、多分PHPのバージョンが古かったか、
検証方法が上記とは違うやり方だった(と言われても、どういうやり方よって話ですけど)か、
単純に検証が甘かったかのいずれかじゃないですかね。
個人的予想では、検証が甘かったに一票を投じますけど B-p

ああ、あと文字列連結の実装方法は、上記以外にもあるのかなぁっていう疑問も少しありますね。
まぁ、もし見つけたら、また検証してみたいと思います。