リフレクションの加速に関する倱敗した蚘事

早速蚘事タむトルを説明しおいきたす。圓初の蚈画では、シンプルだが珟実的な䟋を䜿甚しお、リフレクションの䜿甚を高速化するための適切で信頌できるアドバむスを提䟛する予定でしたが、ベンチマヌク䞭に、リフレクションは思ったほど遅くなく、悪倢よりも LINQ が遅いこずが刀明したした。しかし、結局のずころ、私も寞法を間違えおいたこずが刀明したした... この人生の物語の詳现はカットの䞋ずコメントにありたす。この䟋は非垞にありふれたものであり、原則ずしお䌁業で通垞行われおいるように実装されおいるため、人生のデモンストレヌションずしお非垞に興味深いものであるこずがわかりたした。蚘事の䞻芁な䞻題の速床ぞの圱響は次のずおりです。倖郚ロゞック (Moq、Autofac、EF Core、その他の「バンディング」) のため目立ちたせん。

私はこの蚘事を読んで次のような印象を受けお仕事を始めたした。 リフレクションが遅いのはなぜですか

ご芧のずおり、䜜成者は、アプリケヌションを倧幅に高速化する優れた方法ずしお、リフレクション型メ゜ッドを盎接呌び出すのではなく、コンパむルされたデリゲヌトを䜿甚するこずを提案しおいたす。もちろん、IL の攟出はありたすが、これはタスクを実行するための最も劎働集玄的な方法であり、゚ラヌが倚いため、これは避けたいず考えおいたす。

私も反射の速さに぀いおは垞に同様の意芋を持っおいたこずを考慮するず、特に著者の結論に疑問を呈する぀もりはありたせんでした。

䌁業内でリフレクションが単玔に䜿甚されおいるこずによく遭遇したす。型が取られおいたす。物件に関する情報を取埗したす。 SetValue メ゜ッドが呌び出され、党員が喜びたす。目的のフィヌルドに倀が到着し、誰もが満足しおいたす。非垞に賢い人たち、぀たり䞊玚者やチヌムリヌダヌは、あるタむプから別のタむプぞのこのような単玔な実装の「ナニバヌサル」マッパヌに基づいお、オブゞェクトぞの拡匵機胜を䜜成したす。通垞、本質は次のずおりです。すべおのフィヌルドを取埗し、すべおのプロパティを取埗しお、それらを反埩凊理したす。型メンバヌの名前が䞀臎する堎合は、SetValue を実行したす。堎合によっおは、型の 1 ぀で䜕らかのプロパティが芋぀からなかったずいう間違いによる䟋倖が発生するこずがありたすが、ここでもパフォヌマンスを向䞊させる方法がありたす。詊しお/捕たえおください。

以前のマシンがどのように動䜜するかに぀いおの情報を十分に持たずに、パヌサヌやマッパヌを再発明する人々を芋おきたした。私は、あたかも埌続のバカバカしさを蚀い蚳にするかのように、玠朎な実装を戊略の背埌、むンタヌフェヌスの背埌、むンゞェクションの背埌に隠す人々を芋おきたした。私はそのような認識に錻を鳎らしたした。実際、私は実際のパフォヌマンス リヌクを枬定したわけではなく、可胜であれば、実装を入手できれば、より「最適な」実装に倉曎しただけです。したがっお、以䞋で説明する最初の枬定倀は私をひどく混乱させたした。

リヒタヌや他のむデオロギヌ論者を読んでいる倚くの人は、コヌド内のリフレクションはアプリケヌションのパフォヌマンスに非垞に悪圱響を䞎える珟象であるずいう完党に公平な声明に出䌚ったこずがあるず思いたす。

リフレクションを呌び出すず、CLR はアセンブリを通過しお必芁なアセンブリを芋぀けたり、メタデヌタを取埗したり、解析したりする必芁がありたす。さらに、シヌケンスをトラバヌスする際のリフレクションにより、倧量のメモリが割り圓おられたす。メモリを䜿い果たしおおり、CLR が GC を発芋し、フリヌズが始たりたす。信じおください、かなり遅くなるはずです。最新の実皌働サヌバヌやクラりド マシンに倧量のメモリが搭茉されおいおも、凊理の倧幅な遅延は避けられたせん。実際、メモリが倚いほど、GC がどのように動䜜するかに気づく可胜性が高くなりたす。理論䞊、圌にずっお反省は䜙蚈な雑巟のようなものである。

ただし、私たちは皆 IoC コンテナヌず日付マッパヌを䜿甚しおおり、その動䜜原理もリフレクションに基づいおいたすが、通垞、それらのパフォヌマンスに぀いお疑問はありたせん。いいえ、䟝存関係の導入ず倖郚の限定されたコンテキスト モデルからの抜象化が非垞に必芁であるため、いずれにしおもパフォヌマンスを犠牲にしなければならないからではありたせん。すべおがシンプルなので、パフォヌマンスにはあたり圱響したせん。

実際、リフレクション テクノロゞに基づく最も䞀般的なフレヌムワヌクは、リフレクション テクノロゞをより最適に操䜜するためにあらゆる皮類のトリックを䜿甚しおいたす。通垞、これはキャッシュです。通垞、これらは匏ツリヌからコンパむルされた匏ずデリゲヌトです。同じオヌトマッパヌは、リフレクションを呌び出さずに型を別の型に倉換できる関数ず型を照合する競合蟞曞を維持したす。

これはどのようにしお達成されるのでしょうか?基本的に、これはプラットフォヌム自䜓が JIT コヌドを生成するために䜿甚するロゞックず䜕ら倉わりたせん。メ゜ッドが初めお呌び出されるずき、そのメ゜ッドはコンパむルされたす (そしお、確かに、このプロセスは高速ではありたせん)。その埌の呌び出しでは、制埡は既にコンパむルされたメ゜ッドに移されるため、パフォヌマンスが倧幅に䜎䞋するこずはありたせん。

この䟋では、JIT コンパむルを䜿甚しお、コンパむルされた動䜜を察応する AOT ず同じパフォヌマンスで䜿甚するこずもできたす。この堎合、匏が圹に立ちたす。

問題の原則は次のように簡単に定匏化できたす。
リフレクションの最終結果は、コンパむルされた関数を含むデリゲヌトずしおキャッシュする必芁がありたす。たた、必芁なすべおのオブゞェクトを、オブゞェクトの倖郚に栌玍されおいる型 (ワヌカヌ) のフィヌルドに型情報ずずもにキャッシュするこずも意味がありたす。

これには論理がありたす。垞識的には、䜕かをコンパむルしおキャッシュできる堎合は、それを実行する必芁がありたす。

今埌のこずを考えるず、提案された匏のコンパむル方法を䜿甚しない堎合でも、リフレクションを䜿甚するキャッシュには利点があるず蚀えたす。実際、ここでは私が䞊で参照した蚘事の著者の䞻匵を繰り返しおいるだけです。

次にコヌドに぀いおです。深刻な信甚機関の本栌的な制䜜で私が盎面しなければならなかった最近の痛みに基づいた䟋を芋おみたしょう。すべおの存圚は架空のものであるため、誰も掚枬できたせん。

ある皋床の本質はある。連絡を入れたしょう。暙準化された本文を持぀文字があり、そこからパヌサヌずハむドレヌタヌが同じ連絡先を䜜成したす。手玙が到着し、私たちはそれを読み、キヌず倀のペアに解析しお連絡先を䜜成し、デヌタベヌスに保存したした。

初歩的なこずです。連絡先に氏名、幎霢、連絡先電話番号のプロパティがあるずしたす。このデヌタは手玙で送信されたす。たた、䌁業は、゚ンティティのプロパティをレタヌ本文のペアにマッピングするための新しいキヌを迅速に远加できるサポヌトも求めおいたす。誰かがテンプレヌトにタむプミスをした堎合、たたはリリヌス前に新しいフォヌマットに適応しお新しいパヌトナヌからマッピングを緊急に起動する必芁がある堎合。その埌、新しいマッピング盞関を安䟡なデヌタフィックスずしお远加できたす。぀たり、人生の䞀䟋です。

私たちはテストを実装し、䜜成したす。動䜜したす。

コヌドは提䟛したせん。゜ヌスは倚数あり、蚘事の最埌にあるリンクから GitHub で入手できたす。あなたのケヌスに圱響を䞎えるように、それらをロヌドし、認識できないほど拷問し、枬定するこずができたす。高速であるず想定されるハむドレヌタヌず、䜎速であるず想定されるハむドレヌタヌを区別する 2 ぀のテンプレヌト メ゜ッドのコヌドのみを瀺したす。

ロゞックは次のずおりです。テンプレヌト メ゜ッドは、基本的なパヌサヌ ロゞックによっお生成されたペアを受け取りたす。 LINQ レむダヌはパヌサヌおよびハむドレヌタヌの基本ロゞックであり、デヌタベヌス コンテキストにリク゚ストを発行し、キヌをパヌサヌからのペアず比范したす (これらの関数には、比范甚の LINQ を䜿甚しないコヌドがありたす)。次に、ペアはメむンの氎和メ゜ッドに枡され、ペアの倀が゚ンティティの察応するプロパティに蚭定されたす。

「高速」 (ベンチマヌクでは接頭蟞ずしお「高速」):

 protected override Contact GetContact(PropertyToValueCorrelation[] correlations)
        {
            var contact = new Contact();
            foreach (var setterMapItem in _proprtySettersMap)
            {
                var correlation = correlations.FirstOrDefault(x => x.PropertyName == setterMapItem.Key);
                setterMapItem.Value(contact, correlation?.Value);
            }
            return contact;
        }

ご芧のずおり、セッタヌ プロパティを持぀静的コレクションが䜿甚されおいたす。これは、セッタヌ ゚ンティティを呌び出すコンパむルされたラムダです。次のコヌドによっお䜜成されたす。

        static FastContactHydrator()
        {
            var type = typeof(Contact);
            foreach (var property in type.GetProperties())
            {
                _proprtySettersMap[property.Name] = GetSetterAction(property);
            }
        }

        private static Action<Contact, string> GetSetterAction(PropertyInfo property)
        {
            var setterInfo = property.GetSetMethod();
            var paramValueOriginal = Expression.Parameter(property.PropertyType, "value");
            var paramEntity = Expression.Parameter(typeof(Contact), "entity");
            var setterExp = Expression.Call(paramEntity, setterInfo, paramValueOriginal).Reduce();
            
            var lambda = (Expression<Action<Contact, string>>)Expression.Lambda(setterExp, paramEntity, paramValueOriginal);

            return lambda.Compile();
        }

䞀般的にそれは明らかです。プロパティを走査し、セッタヌを呌び出すプロパティのデリゲヌトを䜜成し、それらを保存したす。その埌、必芁に応じお電話をかけたす。

「遅い」 (ベンチマヌクでは接頭蟞が「遅い」):

        protected override Contact GetContact(PropertyToValueCorrelation[] correlations)
        {
            var contact = new Contact();
            foreach (var property in _properties)
            {
                var correlation = correlations.FirstOrDefault(x => x.PropertyName == property.Name);
                if (correlation?.Value == null)
                    continue;

                property.SetValue(contact, correlation.Value);
            }
            return contact;
        }

ここでは、すぐにプロパティをバむパスし、SetValue を盎接呌び出したす。

明確にするため、および参考ずしお、盞関ペアの倀を゚ンティティ フィヌルドに盎接曞き蟌む単玔なメ゜ッドを実装したした。プレフィックス – 手動。

それでは、BenchmarkDotNet を䜿甚しおパフォヌマンスを調べおみたしょう。そしお突然... (ネタバレ - これは正しい結果ではありたせん。詳现は以䞋にありたす)

リフレクションの加速に関する倱敗した蚘事

ここで䜕が芋えるでしょうか Fast プレフィックスを誇らしげに持぀メ゜ッドは、ほずんどすべおのパスで Slow プレフィックスを持぀メ゜ッドよりも遅いこずがわかりたす。これは、䜜業の割り圓おず速床の䞡方に圓おはたりたす。䞀方、可胜な限りこれを目的ずした LINQ メ゜ッドを䜿甚した矎しく゚レガントなマッピングの実装は、逆に生産性を倧幅に䜎䞋させたす。その違いには秩序がありたす。パス数が倉わっおも傟向は倉わりたせん。唯䞀の違いはスケヌルです。 LINQ では 4  200 倍遅くなり、ほが同じ芏暡でより倚くのゎ​​ミが発生したす。

UPDATED

私は自分の目を信じおいたせんでしたが、さらに重芁なこずに、同僚は私の目もコヌドも信じおいたせんでした - ドミトリヌ・チホノフ 0x1000000。私の゜リュヌションを再確認した埌、圌は、最初から最埌たで、実装における倚数の倉曎により私が芋萜ずしおいた゚ラヌを芋事に発芋し、指摘しおくれたした。 Moq セットアップで芋぀かったバグを修正した埌、すべおの結果が適切な䜍眮に収たりたした。再テストの結果によるず、䞻な傟向は倉わっおいたせん。LINQ は䟝然ずしおリフレクションよりもパフォヌマンスに圱響を䞎えおいたす。ただし、Expression コンパむルの䜜業が無駄に行われず、割り圓おず実行時間の䞡方で結果が芋えるのは玠晎らしいこずです。静的フィヌルドが初期化される最初の起動は、圓然「高速」メ゜ッドの方が遅くなりたすが、その埌は状況が倉わりたす。

再テストの結果は次のずおりです。

リフレクションの加速に関する倱敗した蚘事

結論: 䌁業でリフレクションを䜿甚する堎合、特にトリックに頌る必芁はありたせん。LINQ は生産性をより倚く消費したす。ただし、最適化が必芁な高負荷メ゜ッドでは、むニシャラむザずデリゲヌト コンパむラの圢匏でリフレクションを保存でき、これにより「高速」ロゞックが提䟛されたす。こうするこずで、リフレクションの柔軟性ずアプリケヌションの速床の䞡方を維持できたす。

ベンチマヌク コヌドはここから入手できたす。誰でも私の蚀葉を再確認できたす。
ハブラ反射テスト

PS: テストのコヌドは IoC を䜿甚し、ベンチマヌクでは明瀺的な構造を䜿甚したす。実際のずころ、最終的な実装では、パフォヌマンスに圱響を及がし、結果にノむズが倚くなる可胜性のある芁玠をすべおカットしたした。

PPS: ナヌザヌに感謝したす ドミトリヌ・ティホノフ @0x1000000 最初の枬定に圱響を䞎えた、Moq 蚭定時の私の間違いを発芋しおくれたした。読者の䞭で十分なカルマを持っおいる人がいたら、「いいね」をしおください。男は立ち止たり、男は本を読み、男は再確認しお間違いを指摘した。これは尊敬ず共感に倀するず思いたす。

PPPS: スタむルずデザむンの本質を理解した现心の読者に感謝したす。私は統䞀性ず利䟿性を重芖しおいたす。プレれンテヌションの倖亀的な郚分には倚くの芁望が残されおいたすが、私は批刀を考慮に入れたした。飛び道具をお願いしたす。

出所 habr.com

コメントを远加したす