Cは䜎レベル蚀語ですか

私はファビアン・サングラヌドのすべおの倧ファンであり、圌のブログが奜きです。たた、圌の䞡方の 本の衚玙を読みたした最近のHansleminutesポッドキャストで説明されおいたす 。



Fabienは最近、小さなレむトレヌサヌを解読し 、コヌドの難読化を解き、数孊を玠晎らしく矎しく説明する玠晎らしい投皿を曞きたした。 時間をかけおこれを読むこずをお勧めしたす



しかし、 このC ++コヌドをCに移怍するこずは可胜かどうか疑問に思いたした。 最近、 メむンの仕事でたくさんのC ++を曞かなければならなかったので、詊しおみようず思いたした。



しかし、もっず重芁なのは、Cが䜎レベル蚀語であるかどうかをよりよく知りたいず思ったこずです。



少し異なりたすが、関連する質問Cは「システムプログラミング」にどれくらい適しおいたすか この件に関しお、 2013幎のJoe Duffyの玠晎らしい投皿をお勧めしたす。



ラむンポヌト



たず、難読化解陀されたC ++コヌドを1行ず぀ Cに移怍するこずから始めたした。 それは非垞に簡単でした真実はただCがC ++++であるず蚀われおいるようです



この䟋は、䞻なデヌタ構造を瀺しおいたす-'ベクタヌ'、これは比范です。巊偎にC ++、右偎にCがありたす。







そのため、いく぀かの構文䞊の違いがありたすが、.NETでは独自の倀型を定矩できるため、同じ機胜を埗るこずができたした。 これは重芁です。「ベクタヌ」を構造ずしお扱うず、より良い「デヌタの局所性」を埗るこずができ、デヌタがスタックにプッシュされるため、.NETガベヌゞコレクタヌを関䞎させる必芁がないからですはい、これは実装の詳现です。



.NETのstructs



たたは「倀型」の詳现に぀いおは、次を参照しおください。





特に、Eric Lippertの最埌の投皿では、「倀型」が実際に䜕であるかを明確にする有甚な匕甚を芋぀けたした。



もちろん、倀のタむプに関する最も重芁な事実は、実装の詳现、 それらがどのように区別されるかではなく、 「倀のタむプ」の本来の意味的な意味 、 ぀たり垞に「倀によっお」コピヌされるこずです 。 割り圓お情報が重芁な堎合、ヒヌプタむプおよびスタックタむプず呌びたす。 しかし、ほずんどの堎合、それは重芁ではありたせん。 ほずんどの堎合、コピヌず識別のセマンティクスは関連しおいたす。


次に、他のメ゜ッドがどのように比范されるかを芋おみたしょう巊偎がC ++、右偎がC RayTracing(..)



最初にRayTracing(..)



たす。







その埌、 QueryDatabase (..)











これら2぀の機胜の機胜の説明に぀いおは、 Fabianの投皿を参照しおください



ただし、実際には、Cを䜿甚するず、C ++コヌドを非垞に簡単に䜜成できたす。 この堎合、 ref



キヌワヌドが最も圹立ちたす。これにより、参照によっお倀を枡すこずができたす 。 かなり以前からメ゜ッド呌び出しでref



を䜿甚しおいたしたが、最近、他の堎所でref



を解決する努力がなされおいたす。





構造をコピヌする必芁がないため、 ref



を䜿甚するずパフォヌマンスが向䞊する堎合がありたす。詳现に぀いおは、Adam Stinixによる投皿のベンチマヌクず「Performance Traps ref locals and ref return in C」を参照しおください。



しかし、最も重芁なこずは、そのようなスクリプトがCポヌトにC ++゜ヌスコヌドず同じ動䜜を提䟛するこずです。 いわゆる「管理リンク」は「ポむンタヌ」ずたったく同じではないこずに泚意しおください。特に、それらに察しお算術挔算を実行するこずはできたせん。詳现に぀いおは、以䞋を参照しおください。





性胜



したがっお、コヌドはうたく移怍されたしたが、パフォヌマンスも重芁です。 特にレむトレヌサヌでは、数分間フレヌムを蚈算できたす。 C ++コヌドには、 sampleCount = 2



次のように、最終的な画像品質を制埡する倉数sampleCount



が含たれおいたす。







明らかにあたり珟実的ではありたせん



しかし、 sampleCount = 2048



到達するず、すべおがより良く芋えたす







ただし、 sampleCount = 2048



から開始するのは非垞に時間がかかるため、少なくずも1分間を満たすために、他のすべおの実行は倀2



で実行されたす。 sampleCount



倉曎は、最も倖偎のコヌドルヌプの反埩回数にのみ圱響したす。説明に぀いおは、 この芁点を参照しおください。



「単玔な」回線ポヌト埌の結果



C ++ずCを実質的に比范するために、 time-windowsツヌルを䜿甚したした。これはtime



unixコマンドの移怍です。 最初の結果は次のようになりたした。



C ++VS 2017 .NET Framework4.7.2 .NET Core2.2
時間秒 47.40 80.14 78.02
コア内秒 0.140.3 0.720.9 0.630.8
ナヌザヌ空間内秒 43.8692.5 73.0691.2 70.6690.6
ペヌゞフォヌルト゚ラヌの数 1143 4818 5945
ワヌクセットKB 4232 13 624 17 052
抌し出しメモリKB 95 172 154
非プリ゚ンプティブメモリ 7 14 16
スワップファむルKB 1460 10 936 11 024


最初は、CコヌドはC ++バヌゞョンよりもわずかに遅いこずがわかりたすが、改善されおいたす以䞋を参照。



しかし、最初に、この「単玔な」行ごずのポヌトを䜿甚しおも、.NET JITが䜕をするかを芋おみたしょう。 たず、小さなヘルパヌメ゜ッドを埋め蟌むのに適しおいたす。 これは、優れたむンラむンアナラむザヌツヌルの出力で確認できたす緑色=組み蟌み。







ただし、すべおのメ゜ッドが埋め蟌たれるわけではありたせん。たずえば、耇雑さのために、 QueryDatabase(..)



スキップされたす。







.NET Just-In-TimeJITコンパむラのもう1぀の機胜は、特定のメ゜ッド呌び出しを察応するCPU呜什に倉換するこずです。 これはsqrt



シェル関数で実際に動䜜しおいるこずがわかりたす。C゜ヌスコヌドを次にMath.Sqrt



たす Math.Sqrt



呌び出しに泚意しおMath.Sqrt



。



 // intnv square root public static Vec operator !(Vec q) { return q * (1.0f / (float)Math.Sqrt(q % q)); }
      
      





.NET JITが生成するアセンブラコヌドを次に瀺したすMath.Sqrt



の呌び出しはなく、プロセッサ呜什vsqrtsdが䜿甚されたす。



 ; Assembly listing for method Program:sqrtf(float):float ; Emitting BLENDED_CODE for X64 CPU with AVX - Windows ; Tier-1 compilation ; optimized code ; rsp based frame ; partially interruptible ; Final local variable assignments ; ; V00 arg0 [V00,T00] ( 3, 3 ) float -> mm0 ;# V01 OutArgs [V01 ] ( 1, 1 ) lclBlk ( 0) [rsp+0x00] "OutgoingArgSpace" ; ; Lcl frame size = 0 G_M8216_IG01: vzeroupper G_M8216_IG02: vcvtss2sd xmm0, xmm0 vsqrtsd xmm0, xmm0 vcvtsd2ss xmm0, xmm0 G_M8216_IG03: ret ; Total bytes of code 16, prolog size 3 for method Program:sqrtf(float):float ; ============================================================
      
      





この問題を取埗するには、 これらの指瀺に埓い 、 「Disasmo」VS2019アドオンを䜿甚するか、 SharpLab.ioをご芧ください 



これらの眮換は組み蟌み関数ずも呌ばれ、以䞋のコヌドでJITがそれらを生成する方法を確認できたす。 このスニペットは、 AMD64



のみのマッピングを瀺しおいたすが、JITはX86



、 ARM



およびARM64



も察象ずしおいたす 。完党な方法はこちらです。



 bool Compiler::IsTargetIntrinsic(CorInfoIntrinsics intrinsicId) { #if defined(_TARGET_AMD64_) || (defined(_TARGET_X86_) && !defined(LEGACY_BACKEND)) switch (intrinsicId) { // AMD64/x86 has SSE2 instructions to directly compute sqrt/abs and SSE4.1 // instructions to directly compute round/ceiling/floor. // // TODO: Because the x86 backend only targets SSE for floating-point code, // it does not treat Sine, Cosine, or Round as intrinsics (JIT32 // implemented those intrinsics as x87 instructions). If this poses // a CQ problem, it may be necessary to change the implementation of // the helper calls to decrease call overhead or switch back to the // x87 instructions. This is tracked by #7097. case CORINFO_INTRINSIC_Sqrt: case CORINFO_INTRINSIC_Abs: return true; case CORINFO_INTRINSIC_Round: case CORINFO_INTRINSIC_Ceiling: case CORINFO_INTRINSIC_Floor: return compSupports(InstructionSet_SSE41); default: return false; } ... }
      
      





ご芧のように、䞀郚のメ゜ッドはSqrt



やAbs



など実装されおおり、他のメ゜ッドはC ++ランタむム関数、たずえばpowfを䜿甚しおいたす。



プロセス党䜓は、 「Math.Powが.NET Frameworkにどのように実装されおいるか」ずいう蚘事で非垞によく説明されおいたす。CoreCLRの゜ヌスでも芋るこずができたす。





単玔なパフォヌマンス改善埌の結果



玠朎なラむンごずのポヌトをすぐに改善できるかどうか疑問に思いたす。 プロファむリングの埌、2぀の倧きな倉曎を加えたした。





これらの倉曎に぀いおは、以䞋で詳しく説明したす。



むンラむン配列初期化の削陀



これが必芁な理由の詳现に぀いおは、 Andrei Akinshinによる この優れたStack Overflowの回答ず、ベンチマヌクおよびアセンブラコヌドを参照しおください。 圌は次の結論に達したす。



おわりに



  • .NETはハヌドコヌディングされたロヌカル配列をキャッシュしたすか Roslynコンパむラをメタデヌタに入れるものず同様。
  • この堎合、オヌバヌヘッドが発生したすか 残念ながら、はい呌び出しごずに、JITはメタデヌタから配列の内容をコピヌするため、静的配列に比べお䜙分な時間がかかりたす。 たた、ランタむムはオブゞェクトを遞択し、メモリにトラフィックを䜜成したす。
  • これに぀いお心配する必芁はありたすか おそらく。 これがホットな方法であり、良奜なレベルのパフォヌマンスを達成したい堎合は、静的配列を䜿甚する必芁がありたす。 これがアプリケヌションのパフォヌマンスに圱響を䞎えないコヌルドメ゜ッドである堎合、おそらく「適切な」゜ヌスコヌドを蚘述し、メ゜ッド領域に配列を配眮する必芁がありたす。


このdiffで行われた倉曎を確認できたす。



Mathの代わりにMathF関数を䜿甚する



第二に、そしお最も重芁なこずずしお、次の倉曎を行うこずでパフォヌマンスを倧幅に改善したした。



 #if NETSTANDARD2_1 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 // intnv square root public static Vec operator !(Vec q) { return q * (1.0f / MathF.Sqrt(q % q)); } #else public static Vec operator !(Vec q) { return q * (1.0f / (float)Math.Sqrt(q % q)); } #endif
      
      





.NET Standard 2.1以降、 float



共通の数孊関数の具䜓的な実装が存圚したす。 これらはSystem.MathFクラスにありたす。 このAPIずその実装の詳现に぀いおは、こちらをご芧ください。





これらの倉曎埌、CずC ++コヌドのパフォヌマンスの違いは玄10に枛少したした。



C ++VS C ++ 2017 .NET Framework4.7.2 .NET Core2.2TCオフ .NET Core2.2TCオン
時間秒 41.38 58.89 46.04 44.33
コア内秒 0.050.1 0.060.1 0.140.3 0.130.3
ナヌザヌ空間内秒 41.1999.5 58.3499.1 44.7297.1 44.0399.3
ペヌゞフォヌルト゚ラヌの数 1119 4749 5776 5661
ワヌクセットKB 4136 13,440 16,788 16,652
抌し出しメモリKB 89 172 150 150
非プリ゚ンプティブメモリ 7 13 16 16
スワップファむルKB 1428 10 904 10 960 11 044


TC-マルチレベルコンパむル、 Tiered Compilation .NET Core 3.0ではデフォルトで有効になるず思いたす 



完党を期すために、いく぀かの実行の結果を以䞋に瀺したす。



走る C ++VS C ++ 2017 .NET Framework4.7.2 .NET Core2.2TCオフ .NET Core2.2TCオン
TestRun-01 41.38 58.89 46.04 44.33
TestRun-02 41.19 57.65 46.23 45.96
TestRun-03 42.17 62.64 46.22 48.73


泚 .NET Coreず.NET Frameworkの違いは、.NET Framework 4.7.2にMathF APIがないためです。詳现に぀いおは、サポヌトチケット.Net Framework4.8のnetstandard 2.1を参照しおください。



生産性をさらに向䞊



コヌドはただ改善できるず確信しおいたす



パフォヌマンスの違いを解消したい堎合は、Cコヌドをご芧ください 。 比范のために、優れたCompiler ExplorerサヌビスのC ++アセンブラコヌドを芋るこずができたす。



最埌に、それが圹立぀堎合、「ホットパス」衚瀺を備えたVisual Studioプロファむラヌの出力を次に瀺したす䞊蚘のパフォヌマンスの改善埌。







Cは䜎レベル蚀語ですか



たたはより具䜓的に



C/ F/ VB.NETたたはBCL /ランタむム機胜のどの蚀語機胜が「䜎レベル」*プログラミングを意味したすか


*はい、「䜎レベル」は䞻芳的な甚語であるこずを理解しおいたす。



泚各C開発者は、「䜎レベル」ずは䜕かに぀いお独自の考えを持っおいたす。これらの関数は、C ++たたはRustプログラマヌによっお圓然ず芋なされたす。



私が䜜成したリストは次のずおりです。





たた、私はTwitterで叫び声を䞊げ、リストに含めるためのより倚くのオプションを埗たした。





したがっお、最終的には、Cを䜿甚するず、C ++のようなコヌドを確実に蚘述でき、ランタむムおよび基本クラスラむブラリず組み合わせお、倚くの䜎レベルの関数を提䟛できたす。



さらに読む





ナニティバヌストコンパむラ






All Articles