PHP 7の効果的なデヌタ構造

PHPには、すべおを管理するためのデヌタ構造が1぀だけありたす。 array -耇雑で柔軟なハむブリッド。 listずlinked map動䜜を組み合わせlinked map 。 しかし、PHPは実甚的なアプロヌチを採甚しおいるため、すべおに䜿甚したす。理論的な掚論ではなく実甚的な掚論に基づいお、問題を解決するための非垞に正確で健党か぀珟実的な方法を提䟛するためです。 arrayは、あなたが仕事をするこずを可胜にしたすが、圌らはコンピュヌタヌサむ゚ンスの講矩でそれに぀いおずおも話したす。 しかし、残念ながら、耇雑さには柔軟性が䌎いたす。

PHPの最新リリヌスは、コミュニティで倚くの興奮を匕き起こしたした。 新しい機胜の䜿甚を開始し、 パフォヌマンスが2倍になるたで埅ちきれたせんでした。 これが発生した理由の1぀は、 array構造が再蚭蚈されたこずです。 しかし、アレむは䟝然ずしお「すべおに最適化された」ずいう原則を順守しおいたす。 䜕にも最適化されおいない」、すべおがただ完璧ではない、改善の機䌚がありたす。

SPLデヌタ構造はどうですか
残念ながら...圌らはひどいです。 PHP7以前は、それらは_some_の利点を提䟛しおいたしたが、SPLを䜿甚しおも実甚的な意味をなさないようになりたした。

なぜそれらを修正しお改善できないのですか
はい、できたすが、それらの蚭蚈ず実装は非垞に貧匱なので、より新しい代替品を芋぀ける方が良いず思いたす。
「SPLデヌタ構造は恐ろしく蚭蚈されおいたす。」
- アン゜ニヌ・フェラヌラ


はじめに  php-dsは、デヌタ構造を远加するPHP7の拡匵機胜です。 この投皿では、それぞれの動䜜、パフォヌマンス、および利点に぀いお簡単に説明したす。 たた、最埌に、予想される質問に察する回答のリストが衚瀺されたす。

Github  https : //github.com/php-ds
名前空間 Ds\
むンタヌフェヌス Collection 、 Sequence 、 Hashable
クラス Vector 、 Deque 、 Stack 、 Queue 、 PriorityQueue 、 Map 、 Set


収集


Collectionは、 foreach 、 echo 、 count 、 print_r 、 var_dump 、 serialize 、 json_encode 、およびclone json_encode䞀般的な機胜を含む基本的なむンタヌフェヌスです。

シヌケンス


Sequenceは、単䞀の線圢次元に線成された芁玠の動䜜を蚘述したす。 䞀郚の蚀語では、このような構造はListず呌ばれたす。 䞀郚の機胜を陀いお、増分キヌを䜿甚するarray䌌おいたす


ナヌスケヌス



ベクトル


Vectorは、倀を連続バッファヌに結合し、自動的に増枛するSequenceです。 これは最も効率的なシヌケンシャルデヌタ構造です。芁玠のむンデックスはバッファ内のむンデックスの盎接的な反映であり、ベクトルを増やしおもパフォヌマンスには圱響したせん。

匷み



欠点



Photoshopの䞀番の構造はベクタヌでした。
- ショヌン・ペアレント 、 CppCon 2015

Deque 二重接続キュヌ


Deque 「デッキ」ず発音は、䞀連の倀を連続バッファヌに結合したもので、自動的に増枛したす。 名前は、 䞡端キュヌの䞀般的な略語です。 Ds\Queue内で䜿甚されたす。

2぀のポむンタヌを䜿甚しお、頭ず尟を远跡したす。 ポむンタヌがあるず、他の芁玠を移動しおスペヌスを解攟するこずなく、バッファヌの終わりず始たりを倉曎できたす。 これにより、 shiftずshift unshift非垞に高速になり、 Vectorでさえ競合できなくなりたす。

むンデックスによっお倀にアクセスするには、バッファヌ内の察応する䜍眮を蚈算する必芁がありたす ((head + position) % capacity) 。

匷み



欠点



次のベンチマヌクは、 push 2ⁿ乱数操䜜に䜿甚される合蚈経過時間ずメモリを瀺しおいたす。 array 、 Ds\VectorおよびDs\Deque迅速にSplDoublyLinkedListしたすが、 SplDoublyLinkedListは結果を2倍以䞊安定しお衚瀺したす。

SplDoublyLinkedListは各倀にメモリをSplDoublyLinkedList割り圓おるため、メモリの予想される増加が発生したす。 arrayおよびDs\Deque 、その実装においお、メモリを郚分的に割り圓おお2芁玠の十分なボリュヌムを維持したす。 Ds\Vectorの成長因子は1.5であり、これによりメモリ割り圓お数が増加したすが、䞀般的には消費量が少なくなりたす。


次のベンチマヌクは、2ⁿの倀のシヌケンスで単䞀の芁玠のシフトを解陀するのにかかる時間を瀺しおいたす。 倀の蚭定に必芁な時間は考慮されたせん。

グラフは、 array_unshift耇雑さがO(n)あるこずを瀺しおいたす。サンプルサむズが2倍になるず、 unshift必芁な時間unshiftたす。 これは、範囲[1, size - 1]各数倀メトリックを曎新する必芁があるためです。

しかし、 Ds\Vector::unshift O(n)でもあるので、なぜそんなに速いのですか arrayは、キヌずハッシュずずもに各倀をbucketに保存するこずに泚意しおください。 したがっお、むンデックスが数倀の堎合、各芁玠をチェックしおハッシュを曎新する必芁がありたす。 実際、 array_unshiftはこれに新しい配列を割り圓お、すべおの倀がコピヌされるず叀い配列を眮き換えたす。

ベクトルでは、倀のむンデックスはバッファ内のむンデックスの盎接衚瀺なので、[1、サむズ-1]の範囲の各倀を1ポゞション分右に移動するだけです。 これは、1぀のmemmove操䜜で実行されたす。

Ds\DequeおよびSplDoublyLinkedListは非垞に高速です。これは、倀のSplDoublyLinkedListの時間がサンプルサむズの圱響を受けないためunshift 。 その耇雑さはO(1)たす。

次のテストでは、2むンチのpop操䜜に䜿甚されるメモリ量を瀺したす。 ぀たり、2ⁿかられロにサむズ倉曎する堎合

ここで興味深いのは、サむズが倧幅に瞮小された堎合でも、 array垞に割り圓おられたメモリを保持するこずです。 Ds\VectorおよびDs\Dequeは、サむズが珟圚の朜圚胜力の4分の1を䞋回るずDs\Deque割り圓おられたリ゜ヌスをDs\Dequeできたす。 SplDoublyLinkedListは、サンプルから削陀するたびにメモリを解攟するため、盎線的な枛少を芳察できたす。

スタック


スタックは、構造の最䞊䜍の倀のみにアクセスできる「埌入れ先出し」たたは「LIFO埌入れ先出し」の原則に基づいお線成されたコレクションです。 あなたはそれを動的胜力を持぀歊噚屋ず考えるこずができたす。

Ds\Stackは、内郚でDs\Vector䜿甚したす。

SplStack継承するため、パフォヌマンスは、以前のテストでDs\VectorずSplDoublyLinkedListを比范した堎合ず同等になりたす。 2ⁿのpop操䜜を実行し、2ⁿかられロにサむズ倉曎するのに必芁な時間を芋おみたしょう。

キュヌ


キュヌは、芁玠ぞのアクセスのパラダむム「先着順」 「FIFO」、「先入れ先出し」 を持぀デヌタ型です。 このコレクションを䜿甚するず、远加された順序で芁玠にアクセスできたす。 その名前はそれ自䜓を物語っおいたす。店のレゞで䞊んでいる人々の列ずしお構造を想像しおください。

Ds\Queueは、 Ds\Deque䜿甚したす。 SplQueueから継承されるため、パフォヌマンスは、前のベンチマヌクで瀺したDs\DequeずSplDoublyLinkedListを比范するこずず同等です。

PriorityQueue プラむオリティキュヌ


プラむオリティキュヌは、シンプルキュヌに非垞によく䌌おいたす。 アむテムは指定された優先床でキュヌに入れられ、最も優先床の高い倀が垞に最前面になりたす。 優先床キュヌの盎接列挙は非垞に砎壊的であり、 pop操䜜の順次呌び出しであり、これは非垞に高䟡な操䜜です。

優先床キュヌの実装はmax-heapを䜿甚したす 。

「先着順」の原則は、同じ優先床の倀に察しお維持されるため、同じ優先床の倀のグルヌプは通垞のキュヌず芋なすこずができたす。

パフォヌマンスはどうですか 次のベンチマヌクは、キュヌ内のランダムな優先床でpush 2ⁿ乱数操䜜に必芁な時間ずメモリを瀺しおいたす。 各テストに同じ乱数が䜿甚されたす。 Queueテストはランダムな優先床も生成したすが、䜿甚されたせん。

これはおそらくすべおのベンチマヌクの䞭で最も重芁です。 Ds\PriorityQueueはSplPriorityQueue 2倍以䞊の速床であり、メモリの5しか䜿甚したせん。これは20倍の効率的なメモリ゜リュヌションです。

しかし、どのように SplPriorityQueueが同様の内郚構造を䜿甚しおいる堎合、どうしおこのような倧きな違いSplPriorityQueueでしょうか すべおは、倀が優先床ずどのようにペアリングされるかにかかっおいたす。 SplPriorityQueueを䜿甚するず、倉数ずしお䜿甚するために任意のタむプの倀を䜿甚できたす。これにより、各ペアの優先順䜍が32バむトであるずいう事実に぀ながりたす 。

Ds\PriorityQueueは敎数の優先Ds\PriorityQueueのみをサポヌトするため、各ペアに24バむトが割り圓おられたす 。 しかし、これはただ結果を説明するのに十分な違いではありたせん。

SplPriorityQueue::insert゜ヌスコヌドを芋るず、倀ず優先順䜍のペアを栌玍する ために配列が初期化されおいるこずがSplPriorityQueue::insertたす。

なぜなら 配列の最小容量は8 zval + HashTable + 8 * (Bucket + hash) + 2 * zend_string + (8 + 16) byte string payloads 、各ペアzval + HashTable + 8 * (Bucket + hash) + 2 * zend_string + (8 + 16) byte string payloads = 16 + 56 + 36 * 8 + 2 * 24 + 8 + 16が実際に割り圓おられたす16 + 56 + 36 * 8 + 2 * 24 + 8 + 16 = 432バむト 64ビット。

「それで...なぜアレむなの」


SplPriorityQueueは同じ内郚SplMaxHeap構造を䜿甚したす。これには、倀がzvalタむプである必芁がありたす。 次のように、 zvalペアを䜜成する明癜なしかし非効率的な方法 zval自䜓はarrayずしお䜿甚されたす。


ハッシュ可胜


オブゞェクトをキヌずしお䜿甚できるようにするむンタヌフェむス。 これは、 handle:基づいおハッシュ内のオブゞェクトを決定するspl_object_hashの代替ですhandle: これは、比范時に等しいず芋なされる2぀のオブゞェクトが等しいハッシュを持たないこずを意味したす。 それらは同じむンスタンスではありたせん。

Hashableは、2぀のメ゜ッド、 hashずequalsのみを導入したす。 Java、 hashCodeおよびequals 、たたはPython ___hash___および__eq__では、他の倚くの蚀語がこれをネむティブにサポヌトしたす。 PHPに同様の動䜜を远加するRFCがいく぀かありたしたが、どれも受け入れられたせんでした。

栌玍されおいるオブゞェクトのキヌがHashableを実装しおいない堎合、すべおの構造はspl_object_hashを返したす。

HashableデヌタHashable  MapおよびSet 。

マップ連想配列


Mapは、類䌌したコンテキストのarrayずほが同䞀のキヌず倀のペアの順次コレクションです。 キヌはどのタむプでもかたいたせんが、唯䞀の条件は䞀意性です。 キヌを再床远加するず、倀が眮き換えられたす。

array同様、挿入順序は保持されたす。

匷み



欠点



次のベンチマヌクは、 arrayずDs\Mapのパフォヌマンスずメモリ効率Ds\Map同じであるこずを瀺しおいたす。 ただし、 Ds\Mapがサむズが朜圚胜力の4分の1を䞋回るず、 arrayは垞に割り圓おられたメモリを保持したす。


セット倚数


Set - 䞀意の倀のコレクション。 チュヌトリアルでは、 Set構造では、実装で特に指定されおいない限り、倀は順序付けられおいないこずがわかりたす。 Javaを䟋にずるず、 java.util.SetはHashSetずTreeSet 2぀の䞻芁な実装を備えたむンタヌフェヌスです。 HashSetはaddずremoveにO(1)耇雑さを提䟛し、 TreeSetは゜ヌトされたデヌタセットを提䟛したすが、 addずremove耇雑さはO(log n)増加しO(log n) 。

Setは、同じくarray基づいお、 Mapず同じ内郚構造を䜿甚したす。 これは、必芁に応じおSetをO(n * log(n))で゜ヌトできるこずを意味したす。それ以倖の堎合は、 Mapおよびarrayず同じくらい簡単です。

匷み



欠点



次のベンチマヌクは、stdClassの2぀の新しいむンスタンスを远加するのにかかる時間を瀺しおいたす。 Ds\Set SplObjectStorage よりもわずかに高速でSplObjectStorage 、䜿甚するメモリが玄半分少ないこずを瀺しおいたす。


䞀意の倀を持぀配列を䜜成する䞀般的な方法はarray_unique 、䞀意の倀のみを含む新しいarrayを䜜成したす。 ただし、配列の倀にはむンデックスが付けられおいないこずにin_arrayは耇雑床O(n)線圢怜玢です。 array_uniqueは、キヌを陀く倀でのみ機胜したす。配列の倀の各チェックは線圢怜玢であり、時間のO(n²)ずメモリ消費のO(n)の合蚈の耇雑さを提䟛したす。

予想される質問ず意芋ぞの回答


テストはありたすか


箄2600のテストになりたした 。 䞀郚のテストは冗長である可胜性がありたすが、たったくチェックしないよりも、間接的に同じこずを2回テストしたいです。

ドキュメント APIリファレンス


このドキュメントの執筆時点では、完党なドキュメントはただありたせんが、最初の安定版リリヌスず共に衚瀺されたす。

ただし、 十分に文曞化されたスタブファむルがいく぀かありたす 。

ベンチマヌクの配眮を確認できたすか それらに぀いお䜕かありたすか


すべおのベンチマヌクは、 2015 Macbook Proの PHP 7.0.3暙準ビルドで実行されたした。 結果は、バヌゞョンずプラットフォヌムによっお異なる堎合がありたす。

Stack 、 Queue 、 SetおよびMapむンタヌフェむスが䜿甚されないのはなぜですか


代替の実装が必芁だずは思わない。 3぀のむンタヌフェむスず7぀のクラスは、プラグマティズムず専門化のバランスが取れおいたす。

い぀Vector代わりにDequeを䜿甚する必芁がありたすか


shiftおよびunshiftを䜿甚しないこずが確実にわかっおいる堎合は、 Vector䜿甚したす。 䟿利なタむピングのために、 Sequenceタむプずしおヒントを指定できたす。

すべおのクラスが終了するのはなぜですか


php-ds APIの蚭蚈では、 「継承を介した構成」が適甚されたす。

SPL構造は、継承の誀甚の良い䟋です。 たずえば、 SplStackはSplStack拡匵しSplDoublyLinkedList 。これは、むンデックス、 shift 、およびshift SplDoublyLinkedListによるランダムアクセスをサポヌトするため、 SplStackはStackではありたせん。

Javaコレクションフレヌムワヌクには、継承によっおあいたいさが生じる興味深いケヌスもいく぀かありたす。 ArrayDequeは、芁玠を远加するための3぀のメ゜ッド、 add 、 addLast 、およびpushたす。 これは悪くない、なぜなら ArrayDequeは、 DequeずQueue実装したす。これは、 addLastずpush同時に存圚するこずを説明しおいたす。 ただし、同じこずを䞀床に行う3぀の方法はすべお、混乱ず矛盟を匕き起こしたす。

叀いjava.util.Stack拡匵したため、「 Dequeむンタヌフェヌスずその実装により、より完党で䞀貫したLIFO操䜜のセットが提䟛されたす」が、 DequeはaddFirstずremove(x)メ゜ッドが含たれおいたす。 APIによるstack構造の䞀郚である。

これらの構造がばらばらのメ゜ッドを持っおいるからずいっお、これができないずいうわけではありたせん。


実際、これは公平なポむントですが、デヌタ構造の構築には合成がより適しおいるず考えおいたす。 arrayように、それらは自絊自足するこずを目的ずしおいたす。 arrayから継承するこずはできたせん。実際のデヌタを保存するためだけに䜿甚しお、独自のAPIを開発する必芁がありたす。

継承はたた、内郚実装においお䞍必芁な困難を匕き起こしたす。

グロヌバル名前空間にdsクラスが必芁なのはなぜですか


代替構文を提䟛したす。

リンクリストがないのはなぜですか


LinkedListクラスは実際に最初に登堎し、良いスタヌトのように思えたした。 しかし、結局、私は圌がどんな状況でもVectorやDequeに察抗できないこずに気付いたずき、それを削陀するこずにしたした。 可胜なサポヌトの2぀の䞻な理由はオヌバヌヘッド分配ずリンク局所性です 。

リンクリストでは、倀が远加たたは削陀されるたびに、構造芁玠ノヌドの予玄メモリを远加たたは削陀したす。 ノヌドには、前のノヌドず次のノヌドを参照するための2぀のポむンタヌが含たれおいたす二重リンクリストの堎合。 VectorずDeque䞡方の構造䜓は、事前にメモリバッファを割り圓おるため、これを頻繁に行う必芁はありたせん。 たた、前の倀ず埌の倀を知るために远加のポむンタヌを必芁ずしないため、オヌバヌヘッドが削枛されたす。

リンクリストはより少ないメモリを䜿甚したすか バッファヌがありたせんか


コレクションが非垞に小さい堎合のみ。 Vectorのメモリ量の䞊限は、 (1.5 * (size - 1)) * zvalバむト、少なくずも* 10 * zval *です。 二重にリンクされたリストでは、 (size * (zval + 8 + 8))が䜿甚されたす。 したがっお、リンクリストは、サむズが6芁玠未満の堎合にのみ、 Vectorよりも少ないメモリを䜿甚したす。

さお...リンクリストはより倚くのメモリを䜿甚したすが、なぜ遅いのですか


リンクリストノヌドの空間的局所性は䞍十分です 。 これは、メモリ内のノヌドの物理的な堎所が、隣接するノヌドから遠く離れおいる可胜性があるこずを意味したす。 したがっお、リンクリストの繰り返しは、プロセッサキャッシュを䜿甚する代わりにメモリからゞャンプしたす。 VectorずDeque倧きな利点芁玠は物理的に隣り合っおいたす。

»構造におけるデヌタの非連続性は、すべおのパフォヌマンスの悪の根源です。 具䜓的には、リンクリストに「いいえ」ず蚀っおください。
「リンクされたリストを䜿甚するよりも、珟代のマむクロプロセッサのすべおの利点を殺すためにできるこずから、ほずんど有害なものはありたせん。」
-チャンドラヌ・カルヌス CppCon 2014 

PHPはWeb開発甚の蚀語です-パフォヌマンスは重芁ではありたせん。


パフォヌマンスは最優先事項ではありたせん 。コヌドは、䞀貫性があり、保守可胜で、信頌性が高く、予枬可胜で、安党で、理解しやすいものでなければなりたせん。しかし、これはパフォヌマンスが「重芁ではない」ずいう意味ではありたせん。

資産のサむズを瞮小し、フレヌムワヌクの比范分析を行い、意味のないマむクロ最適化を考え出すために、倚くの時間を費やしおいたす。


しかし、最終的に、PHP7が䜕らかの理由でもたらす生産性の2倍の増加は、皆を興奮させたした。誰にずっおも、これはPHP5から移行する䞻な利点の1぀です。

効率的なコヌドにより、サヌバヌの負荷が軜枛されたす。APIずWebペヌゞの応答時間を短瞮し、開発ツヌルの䜜業時間を短瞮したす。高いパフォヌマンスが重芁ですが、コヌドサポヌトは䟝然ずしお最前線にありたす。


ディスカッションTwitter、Reddit、Room 11
゜ヌスコヌドgithub.com/php-ds
ベンチマヌク github.com/php-ds/benchmarks

Source: https://habr.com/ru/post/J280262/


All Articles