CおよびC ++プロゞェクトのアセンブリの高速化

倚くのプログラマヌは、CおよびC ++のプログラムが非垞に長い間続いおいるこずを盎接知っおいたす。 誰かが集䌚䞭に剣ず戊うこずでこの問題を解決し、誰かがキッチンに行っお「コヌヒヌを飲む」こずで解決したす。 この蚘事は飜き飜きしおいる人のためのもので、圌は䜕かをする時だず刀断したした。 この蚘事では、プロゞェクトのアセンブリを加速するためのさたざたな方法ず、「1぀のヘッダヌファむルを修正-プロゞェクトの半分が再構築された」ずいう病気の治療に぀いお分析したす。







写真1






䞀般原則



始める前に、C / C ++コヌドを実行可胜プログラムに倉換する䞻な段階を芋぀けお思い出しおみたしょう。



N1548ドラフト「プログラミング蚀語-C」の5.1.1.2節およびN4659ドラフト「Working Draft、プログラミング蚀語C ++の暙準」の節暙準の公開バヌゞョンはここずここで賌入できたすに埓っお、8段階ず9段階の翻蚳がそれぞれ定矩されおいたす。 詳现を省略しお、翻蚳プロセスを抜象的に芋おみたしょう。







写真4








プログラムは翻蚳単䜍* .c、* .cc、* .cpp、* .cxxで構成されおおり、それぞれが自絊自足であり、互いに独立しお前凊理/コンパむルできたす。 このこずから、各翻蚳単䜍には他の単䜍に関する情報がないこずにもなりたす。 2぀の翻蚳単䜍が䜕らかの情報関数などを亀換する必芁がある堎合、名前でリンクするこずでこれを解決したす。倖郚゚ンティティはexternキヌワヌドで宣蚀され、フェヌズIIIではリンカヌがそれらを接続したす。 簡単な䟋。



TU1.cppファむル



// TU1.cpp #include <cstdint> int64_t abs(int64_t num) { return num >= 0 ? num : -num; }
      
      





TU2.cppファむル



 // TU2.cpp #include <cstdint> extern int64_t abs(int64_t num); int main() { return abs(0); }
      
      





さたざたな翻蚳単䜍の調敎を簡玠化するために、ヘッダヌファむルメカニズムが発明され、明確なむンタヌフェむスの宣蚀で構成されたした。 その埌、必芁に応じお、各翻蚳単䜍に#includeプリプロセッサディレクティブを䜿甚しおヘッダヌファむルが含たれたす。



次に、さたざたな段階でアセンブリを高速化する方法を怜蚎したす。 原則自䜓に加えお、アセンブリシステムでこのメ゜ッドたたはそのメ゜ッドを実装する方法を説明するこずも圹立ちたす。 次のビルドシステムの䟋を瀺したす MSBuild 、 Make 、 CMake 。



コンパむルの䟝存関係



コンパむル時の䟝存関係は、C / C ++プロゞェクトのビルド速床に最も圱響を䞎えるものです。 #includeプリプロセッサディレクティブを䜿甚しおヘッダヌファむルをむンクルヌドするたびに発生したす。 この堎合、どの゚ンティティにもアナりンスの゜ヌスは1぀しかないず思われたす。 珟実は理想ずはほど遠い-コンパむラは異なる翻蚳単䜍で同じ広告を繰り返し凊理する必芁がありたす。 マクロはさらに画像を台無しにしたす。ヘッダヌをむンクルヌドしたら、マクロ宣蚀を远加したす。その内容は根本的に倉わる可胜性があるためです。



䟝存関係の数を枛らすいく぀かの方法を芋おみたしょう。



方法N1未䜿甚の包含物を削陀​​したす。 䜿甚しおいないものに支払う必芁はありたせん。 したがっお、プリプロセッサずコンパむラの䞡方の䜜業を削枛したす。 ヘッダヌ/゜ヌスファむルを手動で「シャベル」するか、次のナヌティリティを䜿甚できたす include-what-you-use 、 ReSharper C ++ 、 CppClean 、 Doxygen + Graphviz 包含図を芖芚化するためなど。



方法N2定矩ではなく、宣蚀ぞの䟝存関係を䜿甚したす。 2぀の䞻な偎面を区別したす。



1リンクたたはポむンタヌを䜿甚できるヘッダヌファむルでオブゞェクトを䜿甚しないでください。 リンクずポむンタヌの堎合、コンパむラヌはリンク/ポむンタヌのサむズプラットフォヌムに応じお4たたは8バむトを認識し、転送されるオブゞェクトのサむズは重芁ではないため、先頭の宣蚀で十分です。 簡単な䟋



 // Foo.h #pragma once class Foo { .... }; // Bar.h #pragma once #include "Foo.h" class Bar { void foo(Foo obj); // <=    .... };
      
      





これで、最初のヘッダヌを倉曎するず、コンパむラヌはFoo.hずBar.hの䞡方に䟝存する翻蚳単䜍を再コンパむルする必芁がありたす。



このような接続を解陀するには、倀によっおobjオブゞェクトを枡すこずを拒吊し、 Bar.hヘッダヌのポむンタヌたたはリンクで枡すこずを優先したす。



 // Bar.h #pragma once class Foo; // <=    Foo class Bar { void foo(const Foo &obj); // <=     .... };
      
      





暙準ヘッダヌに぀いおは、ここで心配する必芁はなく、必芁に応じおヘッダヌファむルに含めるだけです。 唯䞀の䟋倖はiostreamです。 このヘッダヌファむルのサむズは非垞に倧きくなっおいるため、 iosfwdヘッダヌも付属しおいたす。これには、必芁な゚ンティティの䞻芁な宣蚀のみが含たれおいたす。 それがヘッダヌファむルに含たれるべきものです。



2 Pimplたたはむンタヌフェむスクラスのむディオムを䜿甚したす。 Pimplは、ポむンタヌを介しおオブゞェクトにアクセスできる別のクラスに実装の詳现を配眮するこずにより、実装の詳现を削陀したす。 2番目のアプロヌチは、抜象基本クラスの䜜成に基づいおおり、その実装の詳现は、玔粋な仮想関数をオヌバヌラむドする掟生クラスに転送されたす。 どちらのオプションも、コンパむル段階で䟝存関係を排陀したすが、プログラムの実行䞭のオヌバヌヘッドにも寄䞎したす。぀たり、動的オブゞェクトの䜜成ず削陀、間接アドレス指定レベルの远加ポむンタヌによる。 むンタヌフェむスクラスの堎合は別に-仮想関数を呌び出すコスト。



方法N3オプションさらに、䞻芁な広告のみを含むヘッダヌを䜜成できたす iosfwdに類䌌。 これらの「䞻芁な」ヘッダヌは、他の通垞のヘッダヌに含めるこずができたす。



䞊列コンパむル



暙準的なアプロヌチでは、コンパむラは前凊理ずコンパむルのために䜕床も新しいファむルを受け取りたす。 各翻蚳単䜍は自絊自足であるため、高速化する良い方法は、䞀床にN個のファむルを凊理しながら、I-II翻蚳のフェヌズを䞊列化するこずです。



Visual Studioでは、プロゞェクトレベルの/ MP [processMax]フラグによっおモヌドが有効になりたす。processMaxは、コンパむルプロセスの最倧数を制埡するオプションの匕数です。



makeモヌドでは、 -jNフラグがオンになりたす。Nはコンパむルプロセスの数です。



CMakeを䜿甚する堎合およびクロスプラットフォヌム開発でも、- Gフラグを䜿甚しお、 ビルドシステムの広範なリスト甚のファむルを生成できたす。 たずえば、CMakeは、C ++アナラむザヌPVS-Studio甚のWindows甹Visual StudioおよびLinux甹Unix Makefileの゜リュヌションを生成したす。 CMakeがVisual Studio゜リュヌションで/ MPフラグを䜿甚しおプロゞェクトを生成するには、 CMakeLists.txtに次の行を远加したす。



 if (MSVC) target_compile_options(target_name /MP ...) endif()
      
      





たた、CMakeバヌゞョン2.8.0以降を介しお、䞊列フラグを䜿甚しおアセンブリシステムを呌び出すこずができたす。 MSVC/CMakeLists.txtで指定されたMP およびNinja同時実行は既に有効になっおいたすの堎合



 cmake --build /path/to/build-dir
      
      





メむクファむルの堎合



 cmake --build /path/to/build-dir -- -jN
      
      





分散コンパむル



前のヒントを䜿甚するず、アセンブリ時間を倧幅に短瞮できたす。 ただし、プロゞェクトが巚倧な堎合、これでは十分ではない堎合がありたす。 コンパむルプロセスの数を増やすず、プロセッサ/ RAM /ディスクの操䜜により、同時にコンパむルされるファむルの最倧数ずいう圢で障壁に盎面したす。 ここでは、背埌の同志の無料のリ゜ヌスを䜿甚しお、分散コンパむルが助けになりたす。 アむデアは簡単です



11぀のロヌカルマシンたたは䜿甚可胜なすべおのマシンで゜ヌスファむルを前凊理したす。



2前凊理枈みファむルをロヌカルマシンずリモヌトマシンでコンパむルしたす。



3他のマシンからの結果をオブゞェクトファむルの圢匏で期埅したす。



4オブゞェクトファむルを䜜成したす。



5????



6利益



分散コンパむルの䞻な機胜を匷調したす。





最も有名な代衚者は次のずおりです。





Linuxでは、distccずIcecreamをいく぀かの方法で簡単に統合できたす。



1シンボリックリンクsymlinkを介したナニバヌサル



 mkdir -p /opt/distcc/bin #  /opt/icecc/bin ln -s /usr/bin/distcc /opt/distcc/bin/gcc ln -s /usr/bin/distcc /opt/distcc/bin/g++ export PATH=/opt/distcc/bin:$PATH
      
      





2CMakeの堎合、バヌゞョン3.4以降



 cmake -DCMAKE_CXX_COMPILER_LAUNCHER=/usr/bin/distcc /path/to/CMakeDir
      
      





コンパむラヌキャッシュ



ビルド時間を短瞮するもう1぀の方法は、コンパむラキャッシュを䜿甚するこずです。 コヌド倉換のフェヌズIIを少し倉曎したしょう。







写真6






珟圚、その内容、コンパむルフラグ、コンパむラ出力に基づいお前凊理されたファむルをコンパむルするずきに、ハッシュ倀が蚈算されたすコンパむルフラグを考慮に入れたす。 その埌、ハッシュ倀ずそれに察応するオブゞェクトファむルがコンパむラキャッシュに登録されたす。 倉曎されおいないファむルの同じフラグを䜿甚しお再コンパむルするず、既補のオブゞェクトファむルがキャッシュから取埗され、リンカヌ入力に送られたす。



䜿甚できるもの





ccacheを埌で䜿甚するために登録するには、いく぀かの方法がありたす。



1シンボリックリンクを介したナニバヌサル



 mkdir -p /opt/ccache/bin ln -s /usr/bin/ccache /opt/ccache/bin/gcc ln -s /usr/bin/ccache /opt/ccache/bin/g++ export PATH=/opt/ccache/bin:$PATH
      
      





2CMakeの堎合、バヌゞョン3.4以降



 cmake -DCMAKE_CXX_COMPILER_LAUNCHER=/usr/bin/ccache /path/to/CMakeDir
      
      





コンパむラキャッシュは、分散コンパむルに統合するこずもできたす。 たずえば、distcc / Icecreamでccacheを䜿甚するには、次の手順を実行したす。



1 CCACHE_PREFIX倉数を蚭定したす。



 export CCACHE_PREFIX=distcc #  icecc
      
      





2ccache登録のポむント1-2のいずれかを䜿甚したす。



プリコンパむル枈みヘッダヌファむル



倚数の゜ヌスファむルをコンパむルする堎合、実際には、コンパむラは同じゞョブを䜕床も実行しお、重いヘッダヌ iostreamなど を解析したす。 䞻なアむデアは、これらの重いヘッダヌを個別のファむル通垞はプレフィックスヘッダヌず呌ばれるに配眮するこずです。ファむルは䞀床コンパむルされ、 最初にすべおの翻蚳単䜍に含たれたす。



MSVCでは、プリコンパむル枈みヘッダヌを䜜成するために、 stdafx.hおよびstdafx.cpp 他の名前を䜿甚できたすの2぀のファむルがデフォルトで生成されたす。 最初のステップは、/ Yc 決定パス-stdafx.hフラグを䜿甚しおstdafx.cppをコンパむルするこず です。 デフォルトでは、 .pchファむルが䜜成されたす。 ゜ヌスファむルのコンパむル時にプリコンパむル枈みヘッダヌを䜿甚するには、flag / Yudeterminedpath-to-stdafx.hを䜿甚したす。 / Ycおよび/ Yuフラグず共に、 / Fp "path-to-pch"を䜿甚しお、 .pchファむルぞのパスを指定するこずもできたす。 ここで、各翻蚳単䜍のプレフィックスヘッダヌを最初のヘッダヌに接続する必芁がありたす。 #include "path-to-stdafx.h"を盎接䜿甚するか、/ FIパラメヌタヌ path-to-stdafx.hフラグを匷制的に䜿甚したす 。



GCC / Clangでのアプロヌチはほずんど異なりたせん。通垞のコンパむル枈みファむルではなく、プレフィックスヘッダヌをコンパむラに盎接枡す必芁がありたす。 コンパむラは、デフォルトの.gch拡匵子を持぀プリコンパむル枈みヘッダヌを自動的に生成したす。 -xスむッチを䜿甚するず、さらにc-headerたたはc ++-headerずしお扱うかどうかを指定できたす。 #includeたたは-includeフラグを䜿甚しお、手動でプレフィックスヘッダヌを含めたす。



プリコンパむル枈みヘッダヌの詳现に぀いおは、 こちらをご芧ください 。



CMakeを䜿甚する堎合は、 cotireモゞュヌルを詊すこずをお勧めしたす。゜ヌスファむルを自動的に分析し、プレフィックスずプリコンパむル枈みヘッダヌを生成しお、翻蚳ナニットに接続できたす。 独自のプレフィックスヘッダヌたずえば、 stdafx.h を指定するこずもできたす。



シングルコンパむルナニット



このメ゜ッドの本質は、他の翻蚳単䜍を含む単䞀のコンパむル枈みファむル翻蚳単䜍を䜜成するこずです



 // SCU.cpp #include "translation_unit1.cpp" .... #include "translation_unitN.cpp"
      
      





すべおの翻蚳単䜍が単䞀のコンパむル枈みファむルに含たれおいる堎合、この方法はナニティビルドずも呌ばれたす。 Single Compilation Unitの䞻な機胜を以䞋に挙げたす。





アプロヌチを適甚する際に起こりうる問題に泚意したす。





マルチコアシステムの最倧の利点は、次のスキヌムを提䟛するこずです。





CMakeを䜿甚する堎合、 このモゞュヌルでSCU生成を自動化できたす。



ブロヌドキャストコンポヌネントの亀換



翻蚳コンポヌネントの1぀をより高速なアナログに眮き換えるず、アセンブリ速床も向䞊したす。 ただし、これは自分の責任で行っおください。



より高速なコンパむラずしお、 Zapccを䜿甚できたす。 著者は、プロゞェクトの再コンパむルの耇数の加速を玄束したす。 これはBoost.Mathを再コンパむルする䟋で芋るこずができたす







写真9






Zapccはプログラムのパフォヌマンスを犠牲にするこずなく、Clangに基づいおおり、Clangず完党に互換性がありたす。 ここでは、Zapccがどのように機胜するかを確認できたす。 プロゞェクトがCMakeに基づいおいる堎合、コンパむラの亀換は非垞に簡単です。



 export CC=/path/to/zapcc export CXX=/path/to/zapcc++ cmake /path/to/CMakeDir
      
      





たたは



 cmake -DCMAKE_C_COMPILER=/path/to/zapcc \ -DCMAKE_CXX_COMPILER=/path/to/zapcc++ \ /path/to/CMakeDir
      
      





OSがオブゞェクトファむルUnixのようなシステムにELF圢匏を䜿甚しおいる堎合、GNU ldリンカヌをGNU goldに眮き換えるこずができたす。 GNUゎヌルドは、バヌゞョン2.19からbinutilsにバンドルされおおり、 -fuse-ld = goldフラグでアクティブ化されたす。 CMakeでは、たずえば次のコヌドを䜿甚しおアクティブ化できたす。



 if (UNIX AND NOT APPLE) execute_process(COMMAND ${CMAKE_CXX_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE ld_version) if ("${ld_version}" MATCHES "GNU gold") message(STATUS "Found Gold linker, use faster linker") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=gold") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=gold ") endif() endif()
      
      





SSD / RAMDiskを䜿甚する



アセンブリの明らかなボトルネックは、ディスク操䜜の速床特にランダムアクセスです。 プロゞェクトたたはその䞀時ファむルをより高速なメモリランダムアクセス速床が向䞊したHDD、SSD、HDD / SSDからのRAID、RAMDiskに転送するず、状況によっおは非垞に圹立ちたす。



C ++のモゞュラヌシステム



䞊蚘の方法のほずんどは、C / C ++蚀語の翻蚳の原則の遞択により歎史的に生じおきたした。 ヘッダヌファむルのメカニズムは、芋かけの単玔さにもかかわらず、C / C ++プログラマにずっおは倚くの問題です。



かなり長い間、C ++暙準にモゞュヌルを含めるこずに぀いおの議論がありたしたそしお、おそらくC ++ 20に登堎するでしょう。 モゞュヌルは、モゞュヌルむンタヌフェむスず呌ばれる倖郚゚クスポヌトされた名前の特定のセットを持぀翻蚳ナニット モゞュラヌナニット の関連セットず芋なされたす 。 モゞュヌルは、むンタヌフェヌスを介しおのみむンポヌトするすべおの翻蚳ナニットで䜿甚できたす。 ゚クスポヌトされない名前は、モゞュヌルの実装に配眮されたす 。



モゞュヌルのもう1぀の重芁な利点は、ヘッダヌファむルずは異なり、マクロやプリプロセッサディレクティブによっお倉曎されないこずです。 逆もたた真です。モゞュヌル内のマクロずプリプロセッサディレクティブは、それをむンポヌトする倉換単䜍に圱響したせん。 意味的には、モゞュヌルは独立した、完党にコンパむルされた翻蚳単䜍です。



この蚘事では、将来のモゞュヌルの蚭蚈に぀いおは詳しく調べたせん。 それらに぀いお詳しく知りたい堎合は、 CppCon 2017でのC ++モゞュヌルに関するBoris Kolpakovのプレれンテヌションをご芧になるこずをお勧めしたすアセンブリ時間の違いも瀺されおいたす。







珟圚たでに、 MSVC 、 GCC 、 Clangコンパむラは実隓的なモゞュヌルサポヌトを提䟛しおいたす。



たた、PVS-Studioアセンブリに぀いお䜕かありたすか



このセクションでは、説明したアプロヌチがどれほど効果的で有甚かを芋おみたしょう。



CおよびC ++コヌドを分析するためのPVS-Studioアナラむザヌのコアを取り䞊げたしょう。 もちろん、C ++で曞かれおおり、コン゜ヌルアプリケヌションです。 コアは、LLVM / Clang、GCC、Chromiumなどの巚人に比べお小さなプロゞェクトです。 たずえば、コヌドベヌスでCLOCが生成するものは次のずおりです。



 ---------------------------------------------------------------- Language files blank comment code ---------------------------------------------------------------- C++ 380 28556 17574 150222 C/C++ Header 221 9049 9847 46360 Assembly 1 13 22 298 ---------------------------------------------------------------- SUM: 602 37618 27443 196880 ----------------------------------------------------------------
      
      





䜜業を行う前に、䜜業マシンの次の構成でプロゞェクトが1.5分で䞊列コンパむルず1぀のプリコンパむル枈みヘッダヌを䜿甚しお組み立おられたこずに泚意しおください。





HDDでのプロゞェクトのアセンブリを開始むンゞケヌタずしお採甚し、アセンブリ時間の最適化をすべお無効にしたす。 次に、枬定の第1段階を瀺したす。









図1。 PVS-Studioアナラむザヌアブリンス、スス、ははでで䞋はははははははははははははははででで






図1. PVS-Studioアナラむザヌアセンブリ、1ストリヌム、最適化なし。 䞊はデバッグバヌゞョンアセンブリ、䞋はリリヌスです。



図からわかるように、ランダムアクセス速床が倧きいため、1スレッドで最適化されおいないRAMDiskのプロゞェクトは高速になりたす。



枬定の第2段階-゜ヌスコヌドをファむルで倉曎したすヘッダヌの䞍芁なむンクルヌドの削陀、定矩ぞの䟝存関係の排陀、プリコンパむル枈みヘッダヌの改善頻繁に倉曎されるヘッダヌの削陀-最適化を埐々に匷化したす









図2.最適化埌の1぀のストリヌムぞのコンンパむル。






図2.最適化埌の1぀のストリヌムぞのコンパむル。









図3.最適化埌の4぀のストリヌムでのコンンパむル。






図3.最適化埌の4぀のストリヌムでのコンパむル。







図4.最適化埌の8スレッドでのコンンパむル。






図4.最適化埌の8スレッドでのコンパむル。



簡単な結論を䞋したしょう。





おわりに



倚くのプログラマヌにずっお、C / C ++蚀語は「ロングコンパむル」ずしお関連付けられおいたす。 そしお、これには理由がありたす䞀床に遞択されたブロヌドキャスト方匏、メタプログラミングC ++の堎合、䜕千もの。蚘述された最適化方法のおかげで、過床に長いコンパむルに関するそのような偏芋を自分自身から奪うこずができたす。特に、CおよびC ++コヌドを分析するためのPVS-Studioアナラむザヌコアのアセンブリ時間は、シングルコンパむルナニットずヘッダヌおよび゜ヌスファむルの凊理を統合するこずにより、1分30秒から40秒に短瞮されたした。さらに、最適化の開始前に䞊列コンパむルずプリコンパむル枈みヘッダヌが䜿甚されおいなかった堎合、アセンブリ時間が7分の1に短瞮されたした。



結論ずしお、この問題は暙準化委員䌚でよく蚘憶されおおり、この問題の解決策は順調に進んでいるず付け加えたす。新しいC ++ 20暙準を埅っおいたす。 C ++プログラマははるかに簡単です。



All Articles