LINQ for PHP:速度が重要

LINQとは何か、PHPをあきらめた理由がわからない場合は、 以前のYaLinqoの記事を参照してください



残りを続けます。 私はすぐに警告します:イテレータがPHPで何らかの理由でドラッグした不要なものであると考える場合、匿名関数を含むこれらの新しいものすべてが残酷にサグしているため、そのパフォーマンスはマイクロ秒ごとに測定する必要があります発明されていない-それから通り過ぎる。 ライブラリと記事はあなたのためではありません。



残りを続けます。 LINQは優れていますが、使用するとパフォーマンスはどれだけ低下しますか? ベアサイクルと比較すると、速度は3〜5倍未満です。 無名関数が渡される配列の関数と比較すると、2〜4ごとになります。 小さなデータ配列はライブラリの助けを借りて処理され、複雑なデータ処理はスクリプトの外部(データベース内、サードパーティのWebサービス内)にあると想定されているため、実際にはスクリプト全体でわずかな損失があります。 主なものは読みやすさです。



YaLinqoライブラリを作成してからさらに2つの競合他社が明らかになったので、それらは実際にLINQであり(つまり、遅延計算とその他の基本機能をサポートしています)、ライブラリを比較することをお勧めします。 最も単純で最も論理的なのは、機能とパフォーマンスを比較することです。 前の比較のように、少なくともこれは幼児の暴行にはなりません。



(また、競合他社の出現により、最終的にYaLinqoのドキュメントをオンラインで投稿するようになりました 。)



免責事項:これらはニーハイテストです。 彼らはすべてのパフォーマンスの損失の推定値を与えていません。 特に、メモリ消費を完全に考慮していません。 普通にそれをする方法がわからないからです。 その場合、 プルリクエストは歓迎されます。

競合他社

YaLinqo - PHPのオブジェクトへのもう1つのLINQ 。 オブジェクト(配列とイテレータ)でのみクエリをサポートします。 PHP 5.3以降(yieldなし)とPHP 5.5以降(yieldあり)の2つのバージョンがあります。 最新バージョンは、すべての操作でyieldと配列のみに依存しています。 無名関数に加えて、文字列ラムダをサポートしています。 提示されたライブラリの中で最もミニマルなもの:4つのクラスのみが含まれています。 機能の-非常に大規模なドキュメント、MSDNから適応



Ginq- 「LINQ to Object」はPHPのDSLに影響を与えました 。 同様に、オブジェクトのクエリのみをサポートします。 SPLイテレータに基づいているため、PHP 5.3以降の要件。 無名関数に加えて、Symfonyからの「プロパティアクセス」をサポートします。 中規模のライブラリ:移植されたコレクション、コンパレータ、キーと値のペア、および.NETからのその他のもの。 合計70クラス。 平均的な呼び出しのドキュメント:せいぜい、署名が示されるだけです。 主な機能はイテレータです。これにより、ライブラリを使用して、メソッドのチェーンの形式でクエリを作成し、ネストされたイテレータを使用できます。



Pinq - PHP統合クエリ、PHP用の実際のLINQライブラリ 。 オブジェクトとデータベースを操作できる唯一のライブラリ( 理論的には...)。 匿名関数のみをサポートしますが、 PHP-Parserを使用してコードを解析できます。 ドキュメントは(詳細な場合でも)最も詳細ではありませんが、素晴らしいWebサイトがあります。 提示されたものの中で最も大規模なライブラリ:500を超えるクラスで、150のテストクラスをカウントしていません(正直なところ、私はコードが怖いので入ることもしませんでした)。



テストおよび品質のその他の兆候が示されたライブラリはすべて問題ありません。 永久ライセンス:BSD、MIT。 すべてがComposerをサポートし、Packagistに表示されます。



テスト



以降、関数の配列がbenchmark_linq_groups



_linq_groups関数に渡されます:ネイキッドPHP、YaLinqo、Ginq、およびPinqそれぞれ。



テストは、PHP 5.5.14、Windows 7 SP1で実行されます。 テストは「膝の上」で行われるため、鉄のスペックは持ち込みません。タスクは、損失を目で評価することであり、ミリメートル単位ですべてを測定することではありません。 正確なテストが必要な場合は、githubでソースコードを入手できます。それを改善して、プルリクエストを受け入れます。



悪いところから始めましょう-純粋なオーバーヘッド。



 benchmark_linq_groups("Iterating over $ITER_MAX ints", 100, null, [ "for" => function () use ($ITER_MAX) { $j = null; for ($i = 0; $i < $ITER_MAX; $i++) $j = $i; return $j; }, "array functions" => function () use ($ITER_MAX) { $j = null; foreach (range(0, $ITER_MAX - 1) as $i) $j = $i; return $j; }, ], [ function () use ($ITER_MAX) { $j = null; foreach (E::range(0, $ITER_MAX) as $i) $j = $i; return $j; }, ], [ function () use ($ITER_MAX) { $j = null; foreach (G::range(0, $ITER_MAX - 1) as $i) $j = $i; return $j; }, ], [ function () use ($ITER_MAX) { $j = null; foreach (P::from(range(0, $ITER_MAX - 1)) as $i) $j = $i; return $j; }, ]);
      
      





Pinqにはrange



関数がありません;ドキュメントには標準関数を使用するように記載されています。 実際、私たちはそれをやっています。



そして結果:



  1000を超えるintの繰り返し
 ------------------------
   PHP [for] 0.00006秒x1.0(100%)
   PHP [配列関数] 0.00011秒x1.8(+ 83%)
   YaLinqo 0.00041秒x6.8(+ 583%)
   Ginq 0.00075秒x 12.5(+ 1150%)
   Pinq 0.00169秒x28.2(+ 2717%) 


イテレータは容赦なく速度を食べる。



しかし、はるかに印象的なのは、最後のライブラリでの速度のひどい落ち込みです。30倍です。 私はあなたに警告しなければなりません:このライブラリはまだ数字を怖がらせる時間があるので、初期の不思議。



ここで、単純な反復の代わりに、連続した数字の配列を生成します。



 benchmark_linq_groups("Generating array of $ITER_MAX integers", 100, 'consume', [ "for" => function () use ($ITER_MAX) { $a = [ ]; for ($i = 0; $i < $ITER_MAX; $i++) $a[] = $i; return $a; }, "array functions" => function () use ($ITER_MAX) { return range(0, $ITER_MAX - 1); }, ], [ function () use ($ITER_MAX) { return E::range(0, $ITER_MAX)->toArray(); }, ], [ function () use ($ITER_MAX) { return G::range(0, $ITER_MAX - 1)->toArray(); }, ], [ function () use ($ITER_MAX) { return P::from(range(0, $ITER_MAX - 1))->asArray(); }, ]);
      
      





そして結果:



  1000個の整数の配列の生成
 ---------------------------------
   PHP [for] 0.00025秒x1.3(+ 32%)
   PHP [配列関数] 0.00019秒x1.0(100%)
   YaLinqo 0.00060秒x3.2(+ 216%)
   Ginq 0.00107秒x5.6(+ 463%)
   Pinq 0.00183秒x9.6(+ 863%) 


現在、YaLinqoは、サイクルでの額の決定の2倍しか失いません。 他のライブラリの結果はさらに悪いですが、生きることはできます。



次に、テストデータで計算を行います。注文ポイントが5つ以上ある注文をカウントします。 5つ以上のアイテムが3つ以上ある注文をカウントします。



 benchmark_linq_groups("Counting values in arrays", 100, null, [ "for" => function () use ($DATA) { $numberOrders = 0; foreach ($DATA->orders as $order) { if (count($order['items']) > 5) $numberOrders++; } return $numberOrders; }, "array functions" => function () use ($DATA) { return count( array_filter( $DATA->orders, function ($order) { return count($order['items']) > 5; } ) ); }, ], [ function () use ($DATA) { return E::from($DATA->orders) ->count(function ($order) { return count($order['items']) > 5; }); }, "string lambda" => function () use ($DATA) { return E::from($DATA->orders) ->count('$o ==> count($o["items"]) > 5'); }, ], [ function () use ($DATA) { return G::from($DATA->orders) ->count(function ($order) { return count($order['items']) > 5; }); }, ], [ function () use ($DATA) { return P::from($DATA->orders) ->where(function ($order) { return count($order['items']) > 5; }) ->count(); }, ]); benchmark_linq_groups("Counting values in arrays deep", 100, null, [ "for" => function () use ($DATA) { $numberOrders = 0; foreach ($DATA->orders as $order) { $numberItems = 0; foreach ($order['items'] as $item) { if ($item['quantity'] > 5) $numberItems++; } if ($numberItems > 2) $numberOrders++; } return $numberOrders; }, "array functions" => function () use ($DATA) { return count( array_filter( $DATA->orders, function ($order) { return count( array_filter( $order['items'], function ($item) { return $item['quantity'] > 5; } ) ) > 2; }) ); }, ], [ function () use ($DATA) { return E::from($DATA->orders) ->count(function ($order) { return E::from($order['items']) ->count(function ($item) { return $item['quantity'] > 5; }) > 2; }); }, ], [ function () use ($DATA) { return G::from($DATA->orders) ->count(function ($order) { return G::from($order['items']) ->count(function ($item) { return $item['quantity'] > 5; }) > 2; }); }, ], [ function () use ($DATA) { return P::from($DATA->orders) ->where(function ($order) { return P::from($order['items']) ->where(function ($item) { return $item['quantity'] > 5; }) ->count() > 2; }) ->count(); }, ]);
      
      





著しく3つのニュアンス。 まず、配列の標準関数の関数スタイルにより、コードが楽しく読めないラダーに変わります。 第二に、エスケープされたコード内のコードのエスケープは脳の除去であるため、文字列ラムダは使用できません。 第三に、Pinqには述語を取るcount



関数が用意されていないため、メソッドのチェーンを構築する必要があります。 後で判明したように、これはPinqの唯一の制限からはほど遠いです:メソッドが非常に少なく、非常に制限されています。



結果を確認します。



 配列の値を数える
 -------------------------
   PHP [for] 0.00023秒x1.0(100%)
   PHP [配列関数] 0.00052秒x2.3(+ 126%)
   YaLinqo 0.00056秒x2.4(+ 143%)
   YaLinqo [文字列ラムダ] 0.00059秒x2.6(+ 157%)
   Ginq 0.00129秒x5.6(+ 461%)
   Pinq 0.00382秒x16.6(+ 1561%)

配列の値を深くカウントする
 ------------------------------
   PHP [for] 0.00064秒x1.0(100%)
   PHP [配列関数] 0.00323秒x5.0(+ 405%)
   YaLinqo 0.00798秒x12.5(+ 1147%)
   Ginq 0.01416秒x22.1(+ 2113%)
   Pinq 0.04928秒x77.0(+ 7600%) 


恐ろしいPinqの結果は別として、結果は多かれ少なかれ予測可能です。 私はコードを見ました。 コレクション全体がそこで生成され、次にcount()



が呼び出されます...しかし、それでも驚くには早すぎます!



フィルタリングをしましょう。 すべては前回と同じですが、カウントする代わりにコレクションを生成します。



 benchmark_linq_groups("Filtering values in arrays", 100, 'consume', [ "for" => function () use ($DATA) { $filteredOrders = [ ]; foreach ($DATA->orders as $order) { if (count($order['items']) > 5) $filteredOrders[] = $order; } return $filteredOrders; }, "array functions" => function () use ($DATA) { return array_filter( $DATA->orders, function ($order) { return count($order['items']) > 5; } ); }, ], [ function () use ($DATA) { return E::from($DATA->orders) ->where(function ($order) { return count($order['items']) > 5; }); }, "string lambda" => function () use ($DATA) { return E::from($DATA->orders) ->where('$order ==> count($order["items"]) > 5'); }, ], [ function () use ($DATA) { return G::from($DATA->orders) ->where(function ($order) { return count($order['items']) > 5; }); }, ], [ function () use ($DATA) { return P::from($DATA->orders) ->where(function ($order) { return count($order['items']) > 5; }); }, ]); benchmark_linq_groups("Filtering values in arrays deep", 100, function ($e) { consume($e, [ 'items' => null ]); }, [ "for" => function () use ($DATA) { $filteredOrders = [ ]; foreach ($DATA->orders as $order) { $filteredItems = [ ]; foreach ($order['items'] as $item) { if ($item['quantity'] > 5) $filteredItems[] = $item; } if (count($filteredItems) > 0) { $order['items'] = $filteredItems; $filteredOrders[] = [ 'id' => $order['id'], 'items' => $filteredItems, ]; } } return $filteredOrders; }, "array functions" => function () use ($DATA) { return array_filter( array_map( function ($order) { return [ 'id' => $order['id'], 'items' => array_filter( $order['items'], function ($item) { return $item['quantity'] > 5; } ) ]; }, $DATA->orders ), function ($order) { return count($order['items']) > 0; } ); }, ], [ function () use ($DATA) { return E::from($DATA->orders) ->select(function ($order) { return [ 'id' => $order['id'], 'items' => E::from($order['items']) ->where(function ($item) { return $item['quantity'] > 5; }) ->toArray() ]; }) ->where(function ($order) { return count($order['items']) > 0; }); }, "string lambda" => function () use ($DATA) { return E::from($DATA->orders) ->select(function ($order) { return [ 'id' => $order['id'], 'items' => E::from($order['items'])->where('$v["quantity"] > 5')->toArray() ]; }) ->where('count($v["items"]) > 0'); }, ], [ function () use ($DATA) { return G::from($DATA->orders) ->select(function ($order) { return [ 'id' => $order['id'], 'items' => G::from($order['items']) ->where(function ($item) { return $item['quantity'] > 5; }) ->toArray() ]; }) ->where(function ($order) { return count($order['items']) > 0; }); }, ], [ function () use ($DATA) { return P::from($DATA->orders) ->select(function ($order) { return [ 'id' => $order['id'], 'items' => P::from($order['items']) ->where(function ($item) { return $item['quantity'] > 5; }) ->asArray() ]; }) ->where(function ($order) { return count($order['items']) > 0; }); }, ]);
      
      





配列の関数のコードは、すでに著しく臭いがし始めています。 特に、 array_map



array_filter



引数array_filter



異なるため、その後に何が起こるかを把握するのは困難です。



クエリを使用するコードは、意図的に最適性が低くなります。オブジェクトは、その後除外されても生成されます。 これは一般に、LINQの伝統であり、中間の計算結果とともに「匿名型」の作成を伴います。



結果は、以前のテストと比較すると、かなり均一です。



 配列内の値のフィルタリング
 --------------------------
   PHP [for] 0.00049秒x1.0(100%)
   PHP [配列関数] 0.00072秒x1.5(+ 47%)
   YaLinqo 0.00094秒x1.9(+ 92%)
   YaLinqo [文字列ラムダ] 0.00094秒x1.9(+ 92%)
   Ginq 0.00295秒x6.0(+ 502%)
   Pinq 0.00328秒x6.7(+ 569%)

配列の値を深くフィルタリングする
 -------------------------------
   PHP [for] 0.00514秒x1.0(100%)
   PHP [配列関数] 0.00739秒x1.4(+ 44%)
   YaLinqo 0.01556秒x3.0(+ 203%)
   YaLinqo [文字列ラムダ] 0.01750秒x3.4(+ 240%)
   Ginq 0.03101秒x6.0(+ 503%)
   Pinq 0.05435秒x10.6(+ 957%) 


ソートに移りましょう:



 benchmark_linq_groups("Sorting arrays", 100, 'consume', [ function () use ($DATA) { $orderedUsers = $DATA->users; usort( $orderedUsers, function ($a, $b) { $diff = $a['rating'] - $b['rating']; if ($diff !== 0) return -$diff; $diff = strcmp($a['name'], $b['name']); if ($diff !== 0) return $diff; $diff = $a['id'] - $b['id']; return $diff; }); return $orderedUsers; }, ], [ function () use ($DATA) { return E::from($DATA->users) ->orderByDescending(function ($u) { return $u['rating']; }) ->thenBy(function ($u) { return $u['name']; }) ->thenBy(function ($u) { return $u['id']; }); }, "string lambda" => function () use ($DATA) { return E::from($DATA->users)->orderByDescending('$v["rating"]')->thenBy('$v["name"]')->thenBy('$v["id"]'); }, ], [ function () use ($DATA) { return G::from($DATA->users) ->orderByDesc(function ($u) { return $u['rating']; }) ->thenBy(function ($u) { return $u['name']; }) ->thenBy(function ($u) { return $u['id']; }); }, "property path" => function () use ($DATA) { return G::from($DATA->users)->orderByDesc('[rating]')->thenBy('[name]')->thenBy('[id]'); }, ], [ function () use ($DATA) { return P::from($DATA->users) ->orderByDescending(function ($u) { return $u['rating']; }) ->thenByAscending(function ($u) { return $u['name']; }) ->thenByAscending(function ($u) { return $u['id']; }); }, ]);
      
      





usort



の比較関数のコードusort



見苦しいですが、慣れてしまったので、このような関数をためらうことなく書くことができます。 LINQソートはほぼ完全にきれいに見えます。 また、Ginqの「プロパティへのアクセス」を利用できるのはこれが初めてです。これ以上コードを作成することはできません。



結果は驚くべきものです。



 配列の並べ替え
 --------------
   PHP 0.00037秒x1.0(100%)
   YaLinqo 0.00161秒x4.4(+ 335%)
   YaLinqo [文字列ラムダ] 0.00163秒x4.4(+ 341%)
   Ginq 0.00402秒x10.9(+ 986%)
   Ginq [プロパティパス] 0.01998秒x54.0(+ 5300%)
   Pinq 0.00132秒x3.6(+ 257%) 


まず、Pinqはわずかではありますが前進しています。 ネタバレ:これは最初と最後に起こりました。



第二に、Ginqのプロパティにアクセスすると、ひどくパフォーマンスが低下します。つまり、実際のコードではこの機能を利用できません。 構文は、50倍の速度を失う価値はありません。



私たちは楽しみに目を向けます-参加するには、別名キーによる2つのコレクションの組み合わせ。



 benchmark_linq_groups("Joining arrays", 100, 'consume', [ function () use ($DATA) { $usersByIds = [ ]; foreach ($DATA->users as $user) $usersByIds[$user['id']][] = $user; $pairs = [ ]; foreach ($DATA->orders as $order) { $id = $order['customerId']; if (isset($usersByIds[$id])) { foreach ($usersByIds[$id] as $user) { $pairs[] = [ 'order' => $order, 'user' => $user, ]; } } } return $pairs; }, ], [ function () use ($DATA) { return E::from($DATA->orders) ->join($DATA->users, function ($o) { return $o['customerId']; }, function ($u) { return $u['id']; }, function ($o, $u) { return [ 'order' => $o, 'user' => $u, ]; }); }, "string lambda" => function () use ($DATA) { return E::from($DATA->orders) ->join($DATA->users, '$o ==> $o["customerId"]', '$u ==> $u["id"]', '($o, $u) ==> [ "order" => $o, "user" => $u, ]'); }, ], [ function () use ($DATA) { return G::from($DATA->orders) ->join($DATA->users, function ($o) { return $o['customerId']; }, function ($u) { return $u['id']; }, function ($o, $u) { return [ 'order' => $o, 'user' => $u, ]; }); }, "property path" => function () use ($DATA) { return G::from($DATA->orders) ->join($DATA->users, '[customerId]', '[id]', function ($o, $u) { return [ 'order' => $o, 'user' => $u, ]; }); }, ], [ function () use ($DATA) { return P::from($DATA->orders) ->join($DATA->users) ->onEquality( function ($o) { return $o['customerId']; }, function ($u) { return $u['id']; } ) ->to(function ($o, $u) { return [ 'order' => $o, 'user' => $u, ]; }); }, ]);
      
      





Pinqは構文的に際立っており、1つの基本的な機能がいくつかの呼び出しに分割されています。 おそらくこれは読みやすいですが、LINQに慣れたメソッドの場合、この構文はあまり馴染みがないかもしれません。



そして...結果:



 配列の結合
 --------------
   PHP 0.00021秒x1.0(100%)
   YaLinqo 0.00065秒x3.1(+ 210%)
   YaLinqo [文字列ラムダ] 0.00070秒x3.3(+ 233%)
   Ginq 0.00103秒x4.9(+ 390%)
   Ginq [プロパティパス] 0.00200秒x9.5(+ 852%)
   Pinq 1.24155秒x5,911.8(+ 591084%) 


いいえ、間違いはありません。 Pinqは実際に速度を6千回殺します。 最初はスクリプトがハングしていると思っていましたが、最終的には終了し、この想像を絶する数を与えました。 Pinqのソースコードでこの一連の関数のコードがどこにあるかはわかりませんでしたが、辞書の配列がないfor-for-ifがあると感じています。 ここにOOPがあります。



もう1つの簡単なテスト-集計(または累積、畳み込み-好きなように)を考えてみましょう:



 benchmark_linq_groups("Aggregating arrays", 100, null, [ "for" => function () use ($DATA) { $sum = 0; foreach ($DATA->products as $p) $sum += $p['quantity']; $avg = 0; foreach ($DATA->products as $p) $avg += $p['quantity']; $avg /= count($DATA->products); $min = PHP_INT_MAX; foreach ($DATA->products as $p) $min = min($min, $p['quantity']); $max = -PHP_INT_MAX; foreach ($DATA->products as $p) $max = max($max, $p['quantity']); return "$sum-$avg-$min-$max"; }, "array functions" => function () use ($DATA) { $sum = array_sum(array_map(function ($p) { return $p['quantity']; }, $DATA->products)); $avg = array_sum(array_map(function ($p) { return $p['quantity']; }, $DATA->products)) / count($DATA->products); $min = min(array_map(function ($p) { return $p['quantity']; }, $DATA->products)); $max = max(array_map(function ($p) { return $p['quantity']; }, $DATA->products)); return "$sum-$avg-$min-$max"; }, ], [ function () use ($DATA) { $sum = E::from($DATA->products)->sum(function ($p) { return $p['quantity']; }); $avg = E::from($DATA->products)->average(function ($p) { return $p['quantity']; }); $min = E::from($DATA->products)->min(function ($p) { return $p['quantity']; }); $max = E::from($DATA->products)->max(function ($p) { return $p['quantity']; }); return "$sum-$avg-$min-$max"; }, "string lambda" => function () use ($DATA) { $sum = E::from($DATA->products)->sum('$v["quantity"]'); $avg = E::from($DATA->products)->average('$v["quantity"]'); $min = E::from($DATA->products)->min('$v["quantity"]'); $max = E::from($DATA->products)->max('$v["quantity"]'); return "$sum-$avg-$min-$max"; }, ], [ function () use ($DATA) { $sum = G::from($DATA->products)->sum(function ($p) { return $p['quantity']; }); $avg = G::from($DATA->products)->average(function ($p) { return $p['quantity']; }); $min = G::from($DATA->products)->min(function ($p) { return $p['quantity']; }); $max = G::from($DATA->products)->max(function ($p) { return $p['quantity']; }); return "$sum-$avg-$min-$max"; }, "property path" => function () use ($DATA) { $sum = G::from($DATA->products)->sum('[quantity]'); $avg = G::from($DATA->products)->average('[quantity]'); $min = G::from($DATA->products)->min('[quantity]'); $max = G::from($DATA->products)->max('[quantity]'); return "$sum-$avg-$min-$max"; }, ], [ function () use ($DATA) { $sum = P::from($DATA->products)->sum(function ($p) { return $p['quantity']; }); $avg = P::from($DATA->products)->average(function ($p) { return $p['quantity']; }); $min = P::from($DATA->products)->minimum(function ($p) { return $p['quantity']; }); $max = P::from($DATA->products)->maximum(function ($p) { return $p['quantity']; }); return "$sum-$avg-$min-$max"; }, ]); benchmark_linq_groups("Aggregating arrays custom", 100, null, [ function () use ($DATA) { $mult = 1; foreach ($DATA->products as $p) $mult *= $p['quantity']; return $mult; }, ], [ function () use ($DATA) { return E::from($DATA->products)->aggregate(function ($a, $p) { return $a * $p['quantity']; }, 1); }, "string lambda" => function () use ($DATA) { return E::from($DATA->products)->aggregate('$a * $v["quantity"]', 1); }, ], [ function () use ($DATA) { return G::from($DATA->products)->aggregate(1, function ($a, $p) { return $a * $p['quantity']; }); }, ], [ function () use ($DATA) { return P::from($DATA->products) ->select(function ($p) { return $p['quantity']; }) ->aggregate(function ($a, $q) { return $a * $q; }); }, ]);
      
      





関数の最初のセットには説明するものがありません。 唯一のことは、すべてのケースで計算を別々のパスに分割したことです。



2番目のセットでは、製品が計算されます。 Pinqは再び失敗しました:開始値を受け取るオーバーロードを提供せず、代わりに常に最初の要素を受け取り(要素がない場合はnullを返し、例外をスローしません...)、結果として、値を追加でマップする必要があります。



結果:



 配列の集約
 ------------------
   PHP [for] 0.00059秒x1.0(100%)
   PHP [配列関数] 0.00193秒x3.3(+ 227%)
   YaLinqo 0.00475秒x8.1(+ 705%)
   YaLinqo [文字列ラムダ] 0.00515秒x8.7(+ 773%)
  ジンク0.00669秒x11.3(+ 1034%)
   Ginq [プロパティパス] 0.03955秒x67.0(+ 6603%)
   Pinq 0.03226秒x54.7(+ 5368%)

配列の集計カスタム
 -------------------------
   PHP 0.00007秒x1.0(100%)
   YaLinqo 0.00046秒x6.6(+ 557%)
   YaLinqo [文字列ラムダ] 0.00057秒x8.1(+ 714%)
  ジンク0.00046秒x6.6(+ 557%)
   Pinq 0.00610秒x87.1(+ 8615%) 


GinqのPinqと文字列のプロパティは見苦しい結果を示し、YaLinqoは悲しみ、組み込み関数は悲惨な結果をもたらしました。 去勢牛用。



さて、デザートの場合、ReadMe YaLinqoの例は、すべての機能を組み合わせたリクエストです。



 benchmark_linq_groups("Process data from ReadMe example", 5, function ($e) { consume($e, [ 'products' => null ]); }, [ function () use ($DATA) { $productsSorted = [ ]; foreach ($DATA->products as $product) { if ($product['quantity'] > 0) { if (empty($productsSorted[$product['catId']])) $productsSorted[$product['catId']] = [ ]; $productsSorted[$product['catId']][] = $product; } } foreach ($productsSorted as $catId => $products) { usort($productsSorted[$catId], function ($a, $b) { $diff = $a['quantity'] - $b['quantity']; if ($diff != 0) return -$diff; $diff = strcmp($a['name'], $b['name']); return $diff; }); } $result = [ ]; $categoriesSorted = $DATA->categories; usort($categoriesSorted, function ($a, $b) { return strcmp($a['name'], $b['name']); }); foreach ($categoriesSorted as $category) { $categoryId = $category['id']; $result[$category['id']] = [ 'name' => $category['name'], 'products' => isset($productsSorted[$categoryId]) ? $productsSorted[$categoryId] : [ ], ]; } return $result; }, ], [ function () use ($DATA) { return E::from($DATA->categories) ->orderBy(function ($cat) { return $cat['name']; }) ->groupJoin( from($DATA->products) ->where(function ($prod) { return $prod['quantity'] > 0; }) ->orderByDescending(function ($prod) { return $prod['quantity']; }) ->thenBy(function ($prod) { return $prod['name']; }), function ($cat) { return $cat['id']; }, function ($prod) { return $prod['catId']; }, function ($cat, $prods) { return array( 'name' => $cat['name'], 'products' => $prods ); } ); }, "string lambda" => function () use ($DATA) { return E::from($DATA->categories) ->orderBy('$cat ==> $cat["name"]') ->groupJoin( from($DATA->products) ->where('$prod ==> $prod["quantity"] > 0') ->orderByDescending('$prod ==> $prod["quantity"]') ->thenBy('$prod ==> $prod["name"]'), '$cat ==> $cat["id"]', '$prod ==> $prod["catId"]', '($cat, $prods) ==> [ "name" => $cat["name"], "products" => $prods ]'); }, ], [ function () use ($DATA) { return G::from($DATA->categories) ->orderBy(function ($cat) { return $cat['name']; }) ->groupJoin( G::from($DATA->products) ->where(function ($prod) { return $prod['quantity'] > 0; }) ->orderByDesc(function ($prod) { return $prod['quantity']; }) ->thenBy(function ($prod) { return $prod['name']; }), function ($cat) { return $cat['id']; }, function ($prod) { return $prod['catId']; }, function ($cat, $prods) { return array( 'name' => $cat['name'], 'products' => $prods ); } ); }, ], [ function () use ($DATA) { return P::from($DATA->categories) ->orderByAscending(function ($cat) { return $cat['name']; }) ->groupJoin( P::from($DATA->products) ->where(function ($prod) { return $prod['quantity'] > 0; }) ->orderByDescending(function ($prod) { return $prod['quantity']; }) ->thenByAscending(function ($prod) { return $prod['name']; }) ) ->onEquality( function ($cat) { return $cat['id']; }, function ($prod) { return $prod['catId']; } ) ->to(function ($cat, $prods) { return array( 'name' => $cat['name'], 'products' => $prods ); }); }, ]);
      
      





裸のPHPのコードは、ここHabréでの一般的な努力によって書かれています。



結果:



 ReadMeの例からのプロセスデータ
--------------------------------
  PHP 0.00620秒x1.0(100%)
  YaLinqo 0.02840秒x4.6(+ 358%)
  YaLinqo [文字列ラムダ] 0.02920秒x4.7(+ 371%)
  Ginq 0.07720秒x12.5(+ 1145%)
  ピンキュー2.71616秒x438.1(+ 43707%) 


GroupJoinはPinqのパフォーマンスを低下させました。残りは多かれ少なかれ期待される結果を示しました。



ライブラリの詳細



Pinqは、PHPを解析してSQLクエリを生成できる唯一のライブラリであるため、この機能を考慮しないと記事は不完全になります。残念ながら、判明したように、唯一のプロバイダーはMySQL用ですが、「デモ」の形式になっています。実際、この機能は宣言されており、Pinqに基づいて実装できますが、実際には使用することはできません。



結論



Webサービスから受け取った100つか2つの結果をすばやく除外する必要がある場合、LINQライブラリはニーズを十分に満たすことができます。



ライブラリーの中で、パフォーマンスにおける議論の余地のない勝者はYaLinqoです。クエリを使用してオブジェクトをフィルタリングする必要がある場合、これが最も論理的な選択です。



Ginqは、メソッドのチェーンではなくネストされたイテレーターを使用することを好む人にアピールするかもしれません。このようなSPLイテレーターの愛好家がいるかどうかはわかりません。



Pinqは巨大なライブラリであることが判明しました。多くの抽象化層にもかかわらず、いくつかの機能がうんざりするほど実装されています。このライブラリは、データベースクエリのサポートにより可能性がありますが、現時点ではまだ実現されていません。



データベースへのクエリが必要な場合、唯一のオプション-PHPLinqがあります。しかし、通常のORMライブラリがあるため、非常に疑わしい品質のライブラリを使用する意味はありません。



参照資料






All Articles