web-dev-qa-db-ja.com

isset()対strlen()-高速/クリアな文字列長の計算

私はこのコードに出くわしました...

if(isset($string[255])) {
    // too long
}

isset()は6〜40

if(strlen($string) > 255) {
    // too long
}

Isset()の唯一の欠点は、コードが不明確であることです-何が行われているかすぐにはわかりません(pekkaの回答を参照)。 isset()を関数内でラップできます(つまり、strlt($ string、255))。ただし、isset()の速度の利点を失います。

コードの可読性を維持しながら、より高速なisset()関数をどのように使用できますか?

編集:速度を表示するテスト http://codepad.org/ztYF0bE

strlen() over 1000000 iterations 7.5193998813629
isset() over 1000000 iterations 0.29940009117126

EDIT2:isset()の方が速い理由はここにあります

$string = 'abcdefg';
var_dump($string[2]);
Output: string(1) “c”

$string = 'abcdefg';
if (isset($string[7])){
     echo $string[7].' found!';
  }else{
     echo 'No character found at position 7!';
}

「...関数の呼び出しは、言語構造を使用するよりもコストがかかるため」、これはstrlen()を使用するよりも高速です。 http://www.phpreferencebook.com/tips/use-isset-instead-of-strlen/

EDIT3:私は常にmirco最適化に興味があると教えられました。たぶん、コンピューターのリソースが少ないときに教えられたからでしょう。私はそれが重要ではないかもしれないという考えを受け入れています。答えにはそれに対するいくつかの良い議論があります。私はこれを探る新しい質問を始めました... https://stackoverflow.com/questions/6983208/is-micro-optimisation-important-when-coding

55
Boz

Isset()メソッドの方が速いとは思えないので、テストを実行しましたが、そうです。 isset()メソッドは常に約6倍高速です。

私はさまざまなサイズの文字列を試し、さまざまな量の反復を実行しました。 isset()とstrlen()の両方がO(1)(これは意味があります)であるため、比率は変わりません(また、さまざまなサイズの文字列の場合) -issetはC配列の検索のみを行う必要があり、strlen()は文字列に対して保持されているサイズカウントのみを返します)。

私はそれをphpソースで調べました、そして私は大体その理由を理解していると思います。 isset()は関数ではなく言語構造であるため、Zend VMには独自のオペコードがあります。したがって、関数テーブルで検索する必要はなく、より特殊なパラメーター解析を行うことができます。コードは、strlen()のzend_builtin_functions.cとisset()のzend_compile.cにあります。

これを元の質問に結びつけるために、技術的な観点からisset()メソッドに問題はありません。しかし、イモは慣用句に慣れていない人には読みにくいです。さらに、isset()メソッドは時間的に一定ですが、PHPに組み込まれている関数の量を変化させると、strlen()メソッドはO(n))になります。 build PHPおよび多くの関数で静的にコンパイルすると、すべての関数呼び出し(strlen()を含む)は遅くなりますが、isset()は定数になります。ただし、この違いは実際には無視できます。また、維持されている関数ポインタテーブルの数がわからないので、ユーザー定義関数も影響している場合、それらが別のテーブルにあるため、このケースには関係がないことを覚えているようですが、実際に最後に久しぶりですこれで働いた。

残りの部分については、isset()メソッドに欠点はありません。文字列の長さを取得する他の方法は、explode + countのようなわざと複雑なものやそのようなことを考慮しないとわかりません。

最後に、上記のisset()を関数にラップするという提案もテストしました。これはstrlen()メソッドよりも低速です。別の関数呼び出しが必要になるため、別のハッシュテーブル検索が必要になるためです。 (サイズをチェックするための)追加パラメーターのオーバーヘッドは無視できます。参照によって渡されない場合の文字列のコピーと同様です。

51
Roel

この速度の違いは絶対に影響を与えません。せいぜい数ミリ秒です。

あなたとコードに取り組んでいる他の誰にとっても最も読みやすいスタイルを使用してください-私は個人的に2番目の例に強く投票します。

19
Pekka 웃

コードが不完全です。

ここで、私はあなたのためにそれを修正しました:

if(isset($string[255])) {
    // something taking 1 millisecond
}

if(strlen($string) > 255) {
    // something taking 1 millisecond
}

これで、空のループはなくなりましたが、現実的なループになります。何かを行うのに1ミリ秒かかると考えてみましょう。

最近のCPUは、1ミリ秒で多くのことを実行できます。しかし、ランダムなハードドライブアクセスやデータベースリクエストなどには数ミリ秒かかります。これも現実的なシナリオです。

タイミングをもう一度計算してみましょう:

realistic routine + strlen() over 1000000 iterations 1007.5193998813629
realistic routine + isset() over 1000000 iterations 1000.29940009117126

違いを見ます?

12
Boris Yankov

最初に、 Artefactoによる回答 を指摘したいと思います。なぜ、関数呼び出しが言語構成よりもオーバーヘッドを伴うのかを説明します。

第二に、XDebugが関数呼び出しのパフォーマンスを大幅に低下させるという事実を知ってもらいたいので、XDebugを実行している場合、複雑な数値が得られる可能性があります。 参照(質問の2番目のセクション) したがって、本番環境では(XDebugがインストールされていない場合)、差はさらに小さくなります。 6倍から2倍に減少します。

3番目に、測定可能な違いがあるにもかかわらず、この違いは、このコードが数百万回の反復によるタイトループで実行される場合にのみ現れることを知っておく必要があります。通常のWebアプリケーションでは、違いは測定できず、変動のノイズにさらされます。

第4に、現在の開発時間はサーバーの負荷よりもはるかに高価であることに注意してください。 issetコードが何をするかを理解するのに0.5秒も費やす開発者は、CPU負荷の節約よりもはるかにコストがかかります。さらに、サーバーの負荷は、実際に違いを生む最適化(キャッシュなど)を適用することで、はるかに節約できます。

4
NikiC

これは最新のテストです:

function benchmark_function($fn,$args=null)
{
    if(!function_exists($fn))
    {
        trigger_error("Call to undefined function $fn()",E_USER_ERROR);
    }

    $t = microtime(true);

    $r = call_user_func_array($fn,$args);

    return array("time"=>(microtime(true)-$t),"returned"=>$r,"fn"=>$fn);
}

function get_len_loop($s)
{
    while($s[$i++]){}
    return $i-1;
}
echo var_dump(benchmark_function("strlen","kejhkhfkewkfhkwjfjrw"))."<br>";
echo var_dump(benchmark_function("get_len_loop","kejhkhfkewkfhkwjfjrw"));

返された結果:

実行1:

array(3){["time"] => float(2.1457672119141E-6)["returned"] => int(20)["fn"] => string(6) "strlen"} array(3){ ["time"] => float(1.1920928955078E-5)["returned"] => int(20)["fn"] => string(12) "get_len_loop"}

実行2:

array(3){["time"] => float(4.0531158447266E-6)["returned"] => int(20)["fn"] => string(6) "strlen"} array(3){ ["time"] => float(1.5020370483398E-5)["returned"] => int(20)["fn"] => string(12) "get_len_loop"}

実行3:

array(3){["time"] => float(4.0531158447266E-6)["returned"] => int(20)["fn"] => string(6) "strlen"} array(3){ ["time"] => float(1.2874603271484E-5)["returned"] => int(20)["fn"] => string(12) "get_len_loop"}

実行4:

array(3){["time"] => float(3.0994415283203E-6)["returned"] => int(20)["fn"] => string(6) "strlen"} array(3){ ["time"] => float(1.3828277587891E-5)["returned"] => int(20)["fn"] => string(12) "get_len_loop"}

実行5:

array(3){["time"] => float(5.0067901611328E-6)["returned"] => int(20)["fn"] => string(6) "strlen"} array(3){ ["time"] => float(1.4066696166992E-5)["returned"] => int(20)["fn"] => string(12) "get_len_loop"}

2
Ismael Miguel

欠点は、islenがまったく明示的ではないのに対して、strlenはあなたの意図が本当に明確であることです。誰かがあなたのコードを読んで、あなたが何をしているかを理解しなければならない場合、それは彼にバグをもたらし、本当に明確ではないかもしれません。

あなたがfacebookを実行しているのでなければ、私はあなたのサーバーが彼のリソースのほとんどを費やす場所がstrlenであることを疑っています、そしてあなたはstrlenを使い続けるべきです。

Strlenをテストしたところ、issetがはるかに速くなりました。

0.01 seconds for 100000 iterations with isset

0.04 seconds for 100000 iterations with strlen

しかし、私が今言ったことは変わりません。

一部の人々が尋ねたばかりのスクリプト:

$string =    'xdfksdjhfsdljkfhsdjklfhsdlkjfhsdjklfhsdkljfhsdkljfhsdljkfsdhlkfjshfljkhfsdljkfhsdkljfhsdkljfhsdklfhlkjfhkljfsdhfkljsdhfkljsdhfkljhsdfjklhsdjklfhsdkljfhklsdhfkljsdfhdjkshfjlhdskljfhsdkljfhsdjkfhsjkldhfklsdjhfkjlsfhdjkflsdhfjklfsdljfsdlkdlfkjflfkjsdfkl';

for ($i = 0; $i < 100000; $i++) {
   if (strlen($string) == 255) {
   // if (isset($string[255])) {
       // do nothing
   }
}
1
yokoloko

最新のObjectOriented Webアプリケーションでは、小さなクラス内で簡単に作成する1行を数百回実行して、1つのWebページを構築できます。
XDebugを使用してWebサイトのプロファイルを作成すると、クラスの各メソッドが実行された回数に驚かれる場合があります。
その後、実際のシナリオでは、小さな文字列だけでなく、最大3MBサイズまでの非常に大きなドキュメントでも機能する可能性があります。
また、ラテン文字以外のテキストが表示される場合があります。
最終的に、最初はわずかなパフォーマンスの損失であったものが、Webページのレンダリングで数百ミリ秒のサーバーになる可能性があります。

したがって、私はこの問題に非常に興味があり、4つの異なるメソッドをテストして、文字列が本当に空であるか、または実際に「0」のようなものが含まれているかどうかを確認する小さなテストを書きました。

_function stringCheckNonEmpty0($string)
{
  return (empty($string));
}

function stringCheckNonEmpty1($string)
{
  return (strlen($string) > 0);
}

function stringCheckNonEmpty1_2($string)
{
  return (mb_strlen($string) > 0);
}

function stringCheckNonEmpty2($string)
{
  return ($string !== "");
}

function stringCheckNonEmpty3($string)
{
  return (isset($string[0]));
}
_

PHPラテン語以外の文字を扱うのは大変なことです。Webページからロシア語のテキストをコピーして、小さな文字列 "0"と大きなロシア語のテキストの結果を比較しました。

 $ steststring = "0"; 
 
 $ steststring2 = "Hotel Majesticвгородегасабланкарасполагаетсявсеговнесколькихминт.. "следующихдостопримечательностейиобъектов:" 
。 "Playas Ain Diab y La CornicheиЦентральныйрынокКасабланки。" 
。 "Этототельнаходитсявблизиследующихдостопримечательностейиобъектов:" 
。 "ПлощадьМухаммедаVиКультурныйкомплексСиди-Бельот。"; 

実際に違いを確認するために、各テスト関数を数百万回呼び出しました。

_$iruncount = 10000000;

echo "test: empty(\"0\"): starting ...\n";

$tmtest = 0;
$tmteststart = microtime(true);
$tmtestend = 0;

for($irun = 0; $irun < $iruncount; $irun++)
  stringCheckNonEmpty0($steststring);

$tmtestend = microtime(true);
$tmtest = $tmtestend - $tmteststart;

echo "test: empty(\"0\"): '$tmtest' s\n";
_

テスト結果

_$ php test_string_check.php
test0.1: empty("0"): starting ...
test0.1: empty("0"): '7.0262970924377' s
test0.2: empty(russian): starting ...
test0.2: empty(russian): '7.2237210273743' s
test1.1.1: strlen("0"): starting ...
test1.1.1: strlen("0"): '11.045154094696' s
test1.1.2: strlen(russian): starting ...
test1.1.2: strlen(russian): '11.106546878815' s
test1.2.1: mb_strlen("0"): starting ...
test1.2.1: mb_strlen("0"): '11.320801019669' s
test1.2.2: mb_strlen(russian): starting ...
test1.2.2: mb_strlen(russian): '23.082058906555' s
test2.1: ("0" !== ""): starting ...
test2.1: ("0" !== ""): '7.0292129516602' s
test2.2: (russian !== ""): starting ...
test2.2: (russian !== ""): '7.1041729450226' s
test3.1: isset(): starting ...
test3.1: isset(): '6.9401099681854' s
test3.2: isset(russian): starting ...
test3.2: isset(russian): '6.927631855011' s

$ php test_string_check.php
test0.1: empty("0"): starting ...
test0.1: empty("0"): '7.0895299911499' s
test0.2: empty(russian): starting ...
test0.2: empty(russian): '7.3135821819305' s
test1.1.1: strlen("0"): starting ...
test1.1.1: strlen("0"): '11.265664100647' s
test1.1.2: strlen(russian): starting ...
test1.1.2: strlen(russian): '11.282053947449' s
test1.2.1: mb_strlen("0"): starting ...
test1.2.1: mb_strlen("0"): '11.702164888382' s
test1.2.2: mb_strlen(russian): starting ...
test1.2.2: mb_strlen(russian): '23.758249998093' s
test2.1: ("0" !== ""): starting ...
test2.1: ("0" !== ""): '7.2174110412598' s
test2.2: (russian !== ""): starting ...
test2.2: (russian !== ""): '7.240779876709' s
test3.1: isset("0"): starting ...
test3.1: isset("0"): '7.2104151248932' s
test3.2: isset(russian): starting ...
test3.2: isset(russian): '7.2232971191406' s
_

結論

  • 従来のemtpy()関数はうまく機能しますが、「0」のような文字列では失敗します。
  • ラテン文字以外のテキストをチェックするために必要なmb_strlen()関数は、大きなテキストではパフォーマンスが低下します。
  • チェック_$string !== ""_は非常にうまく機能します。 empty()関数よりも優れています。
  • しかし、最高のパフォーマンスがisset($string[0]) Checkを提供します。

私は間違いなくオブジェクトライブラリ全体を処理する必要があります。

1