web-dev-qa-db-ja.com

大規模なC ++プロジェクトの分割

多くの場合と同様に、C++プロジェクトはますます大きくなり、最終的には、主にビルド​​時間が原因で、保守性が問題になり始めています。ccacheを使用しても、すべての変更には30から再構築とテストに数秒から5分(実際には「リンク」は時間がかかるフェーズのようですが、現在調査中です)。

このプロジェクトは、主に大きな科学ライブラリーと、それを利用するいくつかのアプリケーションで構成されています。たとえば、他のソフトウェアパッケージに統合するためのシミュレーションモデルや言語バインディング(python)を拡張するアプリケーションがあります。

ライブラリを小さなライブラリに分割するのは自然なことです。コードはすでに「モジュール」で構成されているため、これは問題になりません。問題は依存関係管理にあります。プロジェクト全体を大きな [〜#〜] dag [〜#〜] と見なすことができ、各ノードライブラリモジュールまたは外部ライブラリです(プロジェクトの性質上、他の科学ライブラリには多くの外部依存関係があります)。

したがって、理想的には、モジュールで作業するときは、そのモジュールと関連するテストアプリだけを再構築し、その依存関係にリンクする必要があります。これにより、開発サイクルが大幅にスピードアップします。

したがって、アイデアは各「モジュール」を独立したプロジェクトとして処理することであり、(静的)ライブラリに個別に組み込まれます。このアプローチには2つの懸念があります。

  • ライブラリーBに依存するアプリAに取り組んでいると想像してください。ライブラリーB自体はライブラリーCに依存しています。開発中に、ライブラリーCに新しい機能が必要であることがわかったので、それをコーディングします。開発を続行する前に、ライブラリC、ライブラリB、最後にライブラリAを手動で再構築する必要があります。依存チェーンが大きくなると、これは問題になる可能性があります。
  • 1つのモジュールの依存関係が変化した場合、DAG全体への影響を管理する方法(結果として変化する他のモジュール)?

私は自分のシナリオに役立つ可能性のあるいくつかのツールを調査しています。 Gitサブモジュール は1つのオプションですが、上記の私の懸念には対応していません。 Apache Ivy は、私が達成しようとしていることには適していないようです。答えには強力なビルドシステムが含まれていると思います( qmake を使用しています)。したがって、 cmake (ただし、スクリプト言語が制限されていて習得が難しいことがわかりました)、次に buck および パンツ

これが道なのかどうかまだわからないので、誰かがアドバイスや経験を共有してくれるとありがたいです。

6
matpen

あなたはあなたの質問で静的ライブラリに言及しています。 Linux用のプログラムをコーディングしていると思います(おそらく間違っています)。

可能な場合は、共有ライブラリを使用する必要があります。次に、必要なリンク時間が大幅に短縮されます(通常、起動時に十分に高速なランタイムリンクが犠牲になります)。

開発中に、ライブラリCに新しい機能が必要であることがわかったので、それをコーディングします。開発を続行する前に、ライブラリC、ライブラリB、最後にライブラリAを手動で再構築する必要があります。

これは、共有ライブラリには当てはまりません(APIが変更されない限り)。

おそらく plugins を使用するいくつかのアーキテクチャを検討し、どの 動的にロードする それらを使用するか(たとえば、POSIXでは dlopen を使用)。

「リンク」は時間がかかるフェーズのようです

これが事実であることを確認する必要があります(例: passing-timeまたは-ftime-report to g++)。次に、 ゴールドリンカー または visibility 関数 属性g++とともに使用します。

サブモジュールまたはライブラリのAPIが頻繁に変更されないようにしてください。

あまりにもsmallC++ソースファイルを避けます。 200行だけの100個のC++ソースファイルではなく、それぞれ2000行の10個のC++ソースファイルを使用することをお勧めします。 C++ソースファイルは、いくつかの(関連する)関数またはクラスを定義および実装できます。 C++にはモジュールがなく、ほとんどの標準ヘッダー(および独自のヘッダー)は非常に大きなファイルであることを思い出してください(たとえば、#include <vector>はLinuxデスクトップで約10KLOCに拡張されます)。コンパイル時間に重要なのは前処理されたフォームのサイズであり、テンプレートの展開にも多くの時間が必要です。 this も参照してください。

ところで、良い ビルドオートメーション ツールninja 、- GNU make -make -j 8などを介して並列化を有効にします...)。分散ビルドツールを使用することもできます( distccicecream 、...)

[〜#〜] gcc [〜#〜]プリコンパイル済みヘッダー の使用を検討することもできます。次に、他のすべてのファイルを含む単一のヘッダーファイルが必要になるでしょう。 PIMPLイディオム を使用することもできます。

関連する質問の this answer も参照してください。


すべての変更には、再構築とテストに30秒から5分かかります

ちなみに、5分の増分ビルド時間は私にはかなり短いようです。なぜ不平を言うのですか?私は1990年代に十分な年齢であり、当時の強力なSunワークステーションでは、ほぼ1時間のビルド時間でした(私が数年間に一人で書いた数十万行のソフトウェアの場合)。また、再構築に数時間かかるGCC自体にも、時々貢献しています。

ビルドシステムと共有ライブラリに関連する良い答えはすでにありますが、あまり頻繁に適用されていないように私は別の角度からこれに取り組みます。

私にとっては、安定したコード(これ以上の変更が必要になることはほとんどありません)を不安定なコード(ソフトウェアの拡張に伴って自然にさらに変更する必要があるコード)から分離すると便利です。これを行うことができず、インターフェイスが完全に安定している場合でも、コードベース全体が動く可能性のある部分と見なされる場合は、何か間違っていると思います。

私たちはすでに、オープンソースのサードパーティライブラリに対してこれを自然に行う傾向があります。通常は一度ビルドし、静的リンク時間を回避するためにdylibを理想的に作成し、実行時にシンボルを検索して関数を呼び出すだけです。利用可能な機能を使用するだけでソースコードを変更する必要はないため、常に何度も再構築する必要はありません。その点でコードは100%安定しています(少なくとも私たちの手では変更されない理由はありません)。

したがって、サードパーティの場合は個人的に「third_parties」サブディレクトリに配置し、新しいサードパーティのライブラリを追加するか、古いバージョンを非常にまれな新しいバージョンで置き換える場合にのみ構築します。私はそれらを毎日何度も再構築していません。

同じことがあなた自身のコードベースに適用できるはずです。自分のコードを含む「libs」サブディレクトリがあり、コードが非常に安定しており、信頼性があり、効率的であり、単体テストで徹底的にテストされ、静的分析であり、何かではないので、私は同様のことをしています将来変更する必要があります。 「libs」サブディレクトリの外側の不安定なコードから遠く離れており、毎日再構築する必要があり、繰り返し再構築されます。

したがって、コードベースをこのように分離することは、変更を常に保証する不安定な部分から変更する必要がほとんどない安定した部分をコードベースを整理するための有用な方法になる可能性があります。安定したパッケージを、できるだけ多くの安定したコードで終了することを目標に、不安定なパッケージから分離します。近い将来の変更は保証されないと自信を持って言えます。

これを行うと、コードベースの安定した部分を個別にビルドし、それらを使用して不安定な部分から分離することができます。不安定なパーツが縮小および縮小している間に、安定したライブラリのセットが拡大および拡大するため、不安定なパーツの構築にそれほど時間がかからないはずです。

ただし、これはコードの重複を意味する傾向があります。安定した画像ライブラリを作成できるようにするには、たとえば、いくつかの不安定な数学ライブラリに依存することはできません。そうでない場合、数学ライブラリへの変更により、画像ライブラリへのソース変更がなければ、再構築が保証されます。したがって、補助的な数学ライブラリから独立するために、たとえば、画像ライブラリがいくつかの数学ルーチンを複製することは、実際に役立つことが実際にあります。その場合、ロジックを少し控えめに複製することで、このようにコードを分離することで、パッケージをより安定させることができ、変更や再構築を何度も繰り返す必要がなくなります。コードが十分にテストされていて、今後数年間美しく機能し、再構築する必要がほとんどない場合、ライブラリがその独立性と安定性を達成するために必要な控えめな複製はほとんど問題になりません。

さらに、インターフェースのミニマリズムを実現するのに役立ちます。イメージライブラリが人類が想像できるすべての単一のイメージ操作を実装することを目的としている場合、その野心とモノリシックな性質により、終わりのない変更が保証されます。そのような目標を持った安定したライブラリになることは決して望めません。ライブラリが提供するものを使用してライブラリの外部で画像操作を作成する機能を備えた基本的な画像操作のみを提供することを目的としている場合、完全に安定した状態を達成できる可能性があります(変更する理由がないため、未来)。

とにかく、コードベースの分割を開始する場合は、安定した十分にテストされた部分を分割することから始めることをお勧めします。あなたは少なくとも将来の変更が必要になる可能性を予想しています。安定したパーツをビルドすると、不安定なパーツ(頻繁に再構築される)とは別のビルドプロセス(まれに適用される)が呼び出されます。

3
user204677

あなたの環境/予算が何かはわかりませんが、ここにいくつかの提案があります:

  • Visual Studio 2017特に/MPプリコンパイル済みヘッダーインクリメンタルビルド、そしておそらくC++モジュール
  • Incredibuild (個人的に使用されることはありません)
  • Octobuild (個人的に使用されることはありません)
  • Zapcc は、主流のコンパイラよりもはるかに高速です。

通常のアドバイスは、単一および複数のプロジェクトに適用されます。購入できる最高のCPU + RAM + SSD(またはRAMディスク)を実行し、オンアクセスウイルススキャンを無効にし、可能な場合はヘッダーをまれに変更します。

1
Graeme Wicksted

わかりにくい場合に備えて、プリコンパイル済みヘッダーを使用し、FCCにコンパイラーの複数のインスタンスを実行するように指示します。

リンクするには、コードをsharedライブラリーに配置すると役立ちます。一部のビルドシステムは、リンク時の最適化を使用します。これにより、ビルド時間が長くなり、オフにすることができる場合があります。

そして、あなたはどんなコンピュータを持っていますか?ビルド時間に悩まされている場合は、8コアとたくさんのRAMが簡単な解決策になります。gccからClangに切り替えることが可能であれば、それが役立つかもしれません。

0
gnasher729

私が取り組んだかなり大規模なプロジェクトでは、他のプロジェクトからの最新バージョンのlibが消費のために公開されていました。ただし、そのプロジェクトをチェックアウトしない限り、コンパイルされます。

ただし、実際に問題を修正するには、境界コンテキストまたはワークフローを修正する必要がある場合があります。モジュール境界を定義して、BにくすぐるAにくすぐるCで作業する必要性を減らすか、BとAの作業が始まる前に準備作業がCのリリースチャネルに入れられるように論理的な方法で作業を行います。

0
Martin K