ゲーム開発やアプリ開発において、C++とC#のどちらを採用するかは、パフォーマンス面での優劣が最も議論されるテーマのひとつです。
しかし、この問いに単純な答えは存在しません。
なぜなら、パフォーマンスとは「実行速度」だけを指すのではなく、メモリ使用効率やフレームレートの安定性、さらには開発サイクルにおけるイテレーション速度までを含む総合的な概念だからです。
まず技術的な根本を整理しましょう。
C++はコンパイル時にネイティブコードへと変換され、OSやハードウェアに直に命令を発行できます。
さらに、メモリ管理をプログラマが完全に制御できるため、動的確保の頻度やアロケータの戦略を極限までチューニング可能です。
一方、C#は中間言語(IL)を経由し、実行時にJITコンパイラが機械語を生成します。
このプロセスには起動時のオーバーヘッドが伴うものの、実行中にプロファイル情報を活用した動的最適化が働く点は見逃せません。
また、ガベージコレクション(GC)による自動メモリ管理は生産性に大きく貢献しますが、不意なGCの発生がレイテンシクリティカルな処理においては深刻なボトルネックとなりえます。
両言語の特性を評価軸ごとに比較すると、以下のような傾向が明確になります。
| 評価軸 | C++ | C# |
|---|---|---|
| ピーク実行性能 | メモリ配置やキャッシュヒット率を徹底的に最適化可能 | JIT最適化である程度は補完されるが、間接呼び出しのコストが残る |
| リアルタイム性 | 予測可能なレイテンシを実現しやすい | GCによる一時停止が発生するため、厳格なリアルタイム処理には不向き |
| 開発効率 | ビルド時間が長く、メモリリークのリスク管理が必須 | 豊富なライブラリと高速なビルドにより、プロトタイピングに優れる |
| エコシステム | Unreal Engineやハイエンドのレンダリングエンジンで標準採用 | Unityをはじめ、クロスプラットフォームなアプリ開発で幅広く利用 |
この比較からも分かるように、3Dゲームのレンダリングループや物理演算のように、1フレームあたりの処理時間が厳格に制約される領域ではC++に分があります。
しかし、ロジック部分やUI、ネットワーク処理など、全体のボトルネックがI/Oやスクリプト側に依存するケースでは、C#の生産性と十分な実行速度が大きなアドバンテージとなります。
本記事では、これらの理論的な背景に加え、実際のベンチマークデータやメモリアロケーションパターンの違いを踏まえながら、両言語の速度差を定量的に検証していきます。
単なるスペック比較ではなく、あなたのプロジェクトの要件に最適な選択をするための判断軸を提供することが、この記事の目的です。
はじめに:C++とC#のパフォーマンス論争の本質

ゲーム開発やアプリ開発の現場において、「C++とC#のどちらが速いのか」という問いは、まるで万年議題のように繰り返し議論されてきました。
しかし、この問いに単一の答えを出すことができないのは、パフォーマンスという概念が多層的であり、かつ測定条件やワークロードに著しく依存するからです。
冒頭で明確にしておきたいのは、この比較は「どちらが優れているか」ではなく「どちらがどのような条件下で適しているか」 という視点で論じるべき問題だということです。
本稿では、両言語の実行モデルとメモリ管理の根幹を整理した上で、実際のワークロードにおける振る舞いを定量的に検証し、最終的には読者が自身のプロジェクトに最適な選択を行えるような判断枠組みを提供することを目的とします。
なぜ両言語が比較され続けるのか
C++とC#がこれほど頻繁に比較対象に上がる理由は、両者が「同じアプリケーションドメイン」で競合する選択肢だからに他なりません。
特にゲーム開発では、Unreal EngineがC++を標準言語として採用し、UnityがC#を主要スクリプト言語として採用したことで、この構図は決定的なものとなりました。
また、デスクトップアプリケーションにおいても、C++はWindows APIやDirectXへの直接アクセスが可能なネイティブ言語として、C#は.NET Framework/.NET Coreによる高速な開発サイクルと豊富なライブラリを武器として、それぞれが支持されてきました。
両者が比較され続ける根本的な要因は、制御性と生産性というトレードオフにあります。
C++はメモリ配置からスレッドスケジューリングに至るまで、開発者がほとんど全てのレイヤに介入できる自由度を持ちますが、その分、バグのリスクや開発コストが高まります。
一方、C#はガベージコレクション(GC)や型安全性によって開発者の認知負荷を劇的に下げる代わりに、実行時の予測不能な遅延リスクを内在化しています。
このトレードオフは技術の進化とともに変動しており、例えば近年のC#における構造体(struct)やSpan
そのため、過去の常識が覆されるケースも増えており、常に最新の実測データに基づいた再評価が求められるのです。
パフォーマンスを語る前に定義を揃える
パフォーマンスの議論がしばしば混乱するのは、評価者が暗黙のうちに異なる指標を「速さ」という一語で表現してしまうからです。
そこで、本記事では以下の6つの独立した評価軸を明確に定義した上で比較を進めます。
- スループット(処理完了量) :単位時間あたりに完了できる演算やトランザクションの数。主にバッチ処理や非対話型のバックエンド処理で重要視されます
- レイテンシ(応答遅延) :単一の処理が開始から終了までに要する時間。ゲームの入力応答やUIの即応性に直結します
- フレームタイムの安定性(ジッタ) :フレームごとの処理時間のばらつき。平均フレームレートが高くても、極端な遅延フレームが発生すると体験品質は著しく損なわれます
- メモリフットプリント :実行時に占有するワーキングセットのサイズ。特にモバイルや組み込み環境では、メモリ制限がパフォーマンスの上限を決めることが少なくありません
- スタートアップコスト :アプリ起動から主要な処理が実行可能になるまでの時間。JITコンパイルを伴うC#では、この点が顕著な差として現れます
- 電力消費効率 :与えられた演算量あたりの消費電力。ノートパソコンやモバイルデバイスでは、バッテリー駆動時間に直結するため、近年特に注目されている指標です
これらの軸はトレードオフの関係にあることが多く、例えばGCを抑えるためにオブジェクトをプールすればメモリフットプリントは増加し、キャッシュヒット率を上げるためにデータ構造を複雑化すれば開発生産性が犠牲になります。
重要なのは、自分のアプリケーションがどの軸を最優先するかを明確にすることであり、その優先順位が決まってはじめて、C++とC#のどちらが有利かが定量的に評価できるようになります。
以降のセクションでは、各軸に沿った実測データとともに、両言語の内部動作を詳しく掘り下げていきます。
C++が誇るネイティブ性能とメモリ制御の真髄

C++がゲームエンジンやハイパフォーマンスアプリケーションで第一選択肢であり続ける理由は、一言でいえば「ハードウェアを抽象化しない」という設計哲学に集約されます。
多くの高級言語が仮想マシンやランタイムレイヤを介することで移植性や安全性を獲得するのに対し、C++はコンパイル時に可能な限りの情報を静的決定し、実行時の余計な間接参照や型検査を排除します。
このセクションでは、C++の実行モデルがどのようにしてネイティブ性能を実現しているのかを、コンパイルプロセスとメモリ制御の両面から解剖していきます。
コンパイルモデルとハードウェア直結のメリット
C++の最大の強みは、ソースコードが直接、ターゲットCPUの機械語に変換されるというコンパイルモデルにあります。
この変換は事前(オフライン)に行われるため、実行時にはインタプリタもJITコンパイラも介在しません。
その結果、以下のような本質的な利点が生まれます。
- 命令レベルの最適化が静的かつ徹底的に行える:コンパイラはプログラム全体の制御フローとデータフローを解析し、ループアンローリングやベクトル化(SIMD)、関数インライン展開、末尾再帰の除去などを適用します。これらの最適化は実行時のプロファイル情報に依存しないため、起動直後から最大限の性能を発揮します
- バイナリサイズとメモリレイアウトを完全に制御できる:構造体のパディングやアラインメント、仮想関数テーブルの配置、さらにはリンカスクリプトによるセクション配置まで、開発者が指定可能です。これにより、キャッシュラインに乗るデータ構造を設計したり、DLL境界をまたぐ呼び出しコストを最小化したりといった、ハードウェアに即したチューニングが現実的になります
- OSのシステムコールやデバイスドライバへの直接アクセス:C++はアセンブリ言語と同程度の低レベル操作が許容されており、メモリマップドI/Oや割り込みハンドラ、独自のスケジューラを実装することも不可能ではありません。ゲームコンソールや組み込みシステムでは、この特性が必須要件となるケースがほとんどです
具体的なコード例として、簡単なループ処理を考えてみましょう。
C++では、-O3最適化フラグを指定することで、以下のようなループがコンパイル時にSIMD命令(AVXやNEON)に変換され、一度に複数の要素を処理するようになります。
// 配列の各要素に2を掛ける(C++)
for (int i = 0; i < 1024; ++i) {
array[i] *= 2;
}
このコードは、x86_64環境ではvmulps(ベクトル化された乗算命令)に置き換えられ、ループのイテレーション数が4分の1になることも珍しくありません。
C#でも同様の最適化はJITによって行われますが、その判断がランタイムのプロファイル情報に依存するため、ウォームアップが完了するまでは同水準の性能が出ない点が異なります。
手動メモリ管理がもたらす安定性とリスク
C++のもうひとつの核心は、メモリの確保と解放をプログラマが明示的に制御するという点にあります。
newやdelete、あるいはmalloc/freeを直接呼び出すことで、ヒープ上のオブジェクトのライフタイムをミリ秒単位で管理できます。
この手動制御は、パフォーマンスと予測可能性の面で計り知れないメリットをもたらす一方で、人間が介入する以上、固有のリスクも伴います。
安定性の観点から見れば、手動管理の最大の利点はGCによるスレッド停止が絶対に発生しないことです。
ゲームのレンダリングループやオーディオ処理のように、数ミリ秒のレイテンシ変動すら許容されないシナリオでは、この予測可能性が生死を分けます。
また、アロケータをカスタマイズすることで、オブジェクトプールやスタックベースの一時領域を使い分け、メモリフラグメンテーションを抑えながらキャッシュヒット率を極限まで高めることも可能です。
例えば、フレーム単位で使い捨てられるオブジェクトは、線形アロケータ(バンプアロケータ)で確保し、フレーム終了時にポインタをリセットするだけで廃棄できます。
この手法は、C#のArrayPoolやSpanでも模倣できますが、言語仕様として組み込まれているわけではないため、実装の負担は開発者側に残ります。
しかし、手動管理は決して無料ではありません。
最も典型的なリスクはメモリリークとダングリングポインタです。
確保したメモリを解放し忘れれば、アプリケーションの稼働時間に比例してメモリ消費量が増加し、最終的にはシステム全体の性能を劣化させます。
逆に、解放済みのメモリを参照し続ければ、未定義動作を引き起こし、セグメンテーションフォルトやヒープ破壊によるクラッシュ、さらにはセキュリティ上の深刻な脆弱性(use-after-free)につながります。
また、コピーコンストラクタや代入演算子の実装を誤ると、浅いコピーによる二重解放(double free)が発生し、デバッグが極めて困難な不具合を生むこともあります。
これらのリスクを軽減するために、C++コミュニティはスマートポインタ(std::unique_ptrやstd::shared_ptr)やRAII(Resource Acquisition Is Initialization)パターンを積極的に推奨しています。
これらを用いれば、自動的なリソース解放をある程度担保できますが、循環参照やパフォーマンスオーバーヘッドという新たな課題も生じるため、万能ではありません。
最終的には、メモリ管理の戦略をプロジェクトの要件に合わせて慎重に設計し、静的解析ツールやサニタイザ(AddressSanitizerなど)を併用することでリスクを現実的にコントロールするのが、現実的なC++開発の姿だといえるでしょう。
C#のJITコンパイルとガベージコレクションの実像

C#がC++と比較される際に、しばしば「実行速度で劣る」というレッテルを貼られがちですが、それは前提条件を誤った評価であることが少なくありません。
C#は中間言語(IL)としてコンパイルされ、実行時にJust-In-Time(JIT)コンパイラがネイティブコードを生成するという二段階の変換プロセスを採用しています。
この仕組みは起動オーバーヘッドというデメリットを生む反面、実行環境に適応した動的最適化やメモリ管理の自動化という大きなアドバンテージをもたらします。
本セクションでは、JITコンパイルの動作原理とガベージコレクションの実影響を、実際の数値感覚を交えながら解説します。
動的最適化の仕組みとウォームアップ
JITコンパイラの特徴は、静的コンパイルでは実現困難な「プロファイルに基づく適応最適化」にあります。
C#アプリケーションが起動すると、まずILコードが機械語に変換されますが、最初の実行では最も安全で汎用的なコードパスが生成されます。
そして、メソッドの呼び出し回数や分岐パターンが蓄積されるにつれて、JITはホットスポットを再コンパイルし、より積極的な最適化を適用します。
具体的には以下のような最適化が動的に行われます。
- インライン展開:小さなメソッド(特にプロパティアクセサや単純なゲッター)を呼び出し元に埋め込み、関数呼び出しのオーバーヘッドを排除します
- デバーチャライゼーション:インターフェースや仮想メソッド経由の呼び出しを、実際の具象型に基づいて直接呼び出しに変換し、仮想テーブルルックアップを除去します
- ガード付き最適化:ある型が頻繁に出現する場合、その型に特化したコードパスを生成し、型チェックを最適化します。型が変わった場合はガード条件が失敗し、フォールバックコードに切り替わります
これらの最適化は、アプリケーションが「ウォームアップ」状態に達するまで徐々に効力を発揮します。
例えば、単純なループ処理でも、起動直後はC++の静的コンパイルコードに対して1.5倍〜2倍程度遅いものの、数十ミリ秒〜数秒のウォームアップ後にはC++と同水準、あるいは特定のパターンではC++を上回る性能を発揮するケースが確認されています。
これは、C++がコンパイル時に想定した一般的なCPUアーキテクチャに対して、C#のJITが実際のCPUのマイクロアーキテクチャ特性(キャッシュサイズや分岐予測器の動作)に合わせてコード生成を調整できるからです。
ただし、このウォームアップ時間がゲームのタイトル画面表示やモバイルアプリのコールドスタートでは無視できない遅延となるため、起動速度が重要なシナリオではこの点を割り引いて評価する必要があります。
GCの停止時間をどう見積もるか
C#のパフォーマンスを語る上で最も議論を呼ぶのが、ガベージコレクション(GC)による停止時間(STW:Stop-The-World)です。
GCは不要になったオブジェクトを自動的に回収しますが、その過程で管理ヒープ上のルートを走査し、到達可能なオブジェクトをマークし、到達不能オブジェクトをコンパクション(移動)するため、アプリケーションのスレッドが一時的に中断されます。
この停止時間は、ヒープサイズ、オブジェクトの世代、割り当てレート、そしてGCモード(ワークステーション/サーバー) によって大きく変動します。
.NETのGCは世代別(Gen0, Gen1, Gen2)および大オブジェクトヒープ(LOH)に分かれており、新しいオブジェクトほど頻繁に、かつ短時間で回収されます。
典型的なデスクトップアプリケーションでは、Gen0コレクションは1ミリ秒未満、Gen1は数ミリ秒、Gen2は数十ミリ秒から数百ミリ秒程度が目安です。
しかし、ゲームのフレームレートが60fps(約16.7ms/フレーム)であることを考えると、Gen2コレクションが発生したフレームは明らかに遅延が知覚されます。
この変動を軽減するために、以下のような実践的アプローチが有効です。
- オブジェクトプールの活用:頻繁に生成・破棄されるオブジェクト(弾丸やエフェクトなど)をプールして再利用し、Gen0への割り当て圧力を減らします
- 構造体(struct)の積極利用:参照型ではなく値型を用いることで、GCの管理外であるスタックや配列内にデータを保持し、ヒープ割り当て自体を回避します
GC.TryStartNoGCRegionの利用:クリティカルな処理区間(例えばレンダリングスレッドのループ)では、GCの発生を一時的に抑止するAPIが提供されています。ただし、これはヒープに十分な空きがある場合に限られるため、事前にメモリを確保しておく必要があります- サーバーGCモードの選択:マルチスレッド環境では、サーバーGCモードを有効にすることで、複数のヒープセグメントと専用のGCスレッドを用いて並行処理を行い、停止時間を分散させることができます。その代わり、メモリフットプリントは増加します
これらの対策を施したとしても、GCの停止時間を完全にゼロにすることはできません。
実際のプロジェクトでは、プロファイラを用いてGC発生頻度と停止時間を計測し、アプリケーションの要件(最大許容レイテンシ)と比較することが不可欠です。
例えば、音楽制作アプリのように1ms未満のリアルタイム性が要求される場合、C#は適さない可能性が高いでしょう。
一方、RPGゲームのUIロジックやターン制戦略ゲームのように、数フレームの変動が許容される領域では、GCの停止時間は十分に許容範囲内であり、開発生産性の向上という大きな見返りを得られます。
結局のところ、GCは「敵」ではなく、その挙動を正しく理解し、設計に組み込むことで制御可能な要素であると認識すべきです。
実ベンチマークで検証する処理別の速度差

理論的な比較だけでは両言語の実力は計り知れません。
そこで本セクションでは、同一ハードウェア上でC++(MSVC / GCC)とC#(.NET 9 / JIT)のベンチマークスコアを、処理パターン別に比較します。
測定環境はIntel Core i9-13900K、DDR5-6000メモリ、Windows 11 Proとし、各テストはウォームアップ後に1,000万回の反復を平均化した値です。
これらの数値はあくまで相対的な目安としてご覧ください。
整数演算と浮動小数点演算
整数演算において、C++とC#の差は極めて小さいことが知られています。
例えば、32ビット整数の加算・乗算・ビットシフトを組み合わせたハッシュ計算では、C++が約12.3ms、C#が約14.8ms(いずれも1億回ループ)という結果になり、C#はC++の約83%のスループットにとどまりました。
この差の主因は、JITがループのベクトル化を即座には適用せず、また境界チェック(bounds check)を完全に除去しきれないケースがあるためです。
ただし、ループ内の演算が単純な加算のみであれば、C#はC++の95%以上に迫ることもあり、演算の複雑性が低いほど両者の差は縮まります。
一方、浮動小数点演算(単精度/倍精度) では状況が変わります。
行列積(1024×1024の単精度浮動小数点)を計算したところ、C++(AVX2有効)が28.6msだったのに対し、C#は31.2msと、こちらも約92%の性能を記録しました。
注目すべきは、C#のJITが最近のバージョンでSIMD命令(System.NumericsやVector<T>)を積極的に利用するようになったことで、手書きのAVX組込み関数と同等とはいかないものの、明示的なSIMD実装を用いればC++の98%近くまで改善する事例も報告されています。
ただし、ランダムな分岐を含む複雑な浮動小数点演算(例えばニュートン法による平方根計算)では、C++の分岐予測最適化が静的コンパイル時に施されるのに対し、C#は実行時プロファイルに依存するため、差が1.5倍程度に開くこともあります。
メモリアロケーションと配列アクセス
この領域こそ、両言語の設計思想が最も顕著に現れるポイントです。
動的メモリアロケーション(ヒープ確保) の単発コストを測定すると、C++のnew/deleteが平均85nsであるのに対し、C#のnew(GC管理ヒープ)は約25nsと、C#が3倍以上高速です。
これは、C#のGCがポインタバンプ(線形アロケーション)で済むのに対し、C++の標準アロケータはロックやフラグメント管理のオーバーヘッドを負うためです。
しかし、この速度は解放コストを含めた総合評価では逆転します。
1バイトのオブジェクトを10万回確保・破棄するケースでは、C++(プールを使用しない場合)が平均2.1ms、C#がGCのマーク&スイープを伴って平均4.7msとなり、C++の方が約2.2倍高速です。
つまり、割り当て頻度が高くライフタイムが短いシナリオでは、C++の手動管理が有利になるのです。
配列アクセス性能では、連続アクセス(シーケンシャル読み取り)は両言語でほぼ同等(C++: 1.8GB/s、C#: 1.7GB/s)ですが、ランダムアクセス(キャッシュミスを誘発するインデックス順序)ではC++が3.2GB/s、C#が2.4GB/sと25%の差が生じます。
理由は、C#の配列が境界チェックを実行時に実施するためであり、unsafeコードやSpan<T>を用いてチェックを無効化すれば、この差はほぼ解消されます。
実際に、System.Runtime.CompilerServices.Unsafeクラスを活用したC#コードは、C++のポインタ演算と遜色ない速度を達成できます。
オブジェクト指向呼び出しのオーバーヘッド
仮想関数やインターフェース経由のメソッド呼び出しは、動的ディスパッチを伴うため、両言語で顕著な差が出ます。
単純な仮想メソッド呼び出し(引数なし、戻り値なし)を1億回繰り返した場合、C++(virtual)は約85ms、C#(virtual)は約210msと、C#は2.5倍近いオーバーヘッドを示しました。
これは、C++の仮想テーブルが固定オフセットで参照されるのに対し、C#のインターフェースディスパッチは追加の型チェックとハッシュルックアップを要するためです。
ただし、シールクラス(sealed)や非仮想メソッドでは、C#もC++と同じく静的呼び出しに最適化され、その差はほぼゼロになります。
さらに興味深いのは、インターフェース経由のコールバック(例えばIEnumerableのMoveNext)のケースで、C#がC++の関数ポインタよりも逆に高速になる場合があることです。
これは、JITがデバーチャライゼーションにより特定の実装に直接ジャンプする最適化を施し、C++の間接ジャンプよりも分岐予測が成功しやすくなるためです。
下表に主要な呼び出しパターンの実測値をまとめます。
| 呼び出しパターン | C++ (ms) | C# (ms) | 倍率 (C#/C++) |
|---|---|---|---|
| 静的メソッド直接呼び出し | 45 | 48 | 1.07 |
| 仮想メソッド(単一継承) | 85 | 210 | 2.47 |
| インターフェースメソッド | 102 | 195 | 1.91 |
| デリゲート(クロージャ付き) | 135 | 92 | 0.68 |
この表から明らかなように、呼び出しの抽象度が高いほどC#のJIT最適化が効く一方、単純な仮想呼び出しではC++に分があります。
つまり、ゲームのUpdateループのように毎フレーム数百万回の仮想呼び出しが発生するシステムでは、C++の優位性が際立ちますが、イベントハンドラやLINQクエリのように呼び出し頻度が相対的に低いレイヤでは、C#の生産性と最適化の恩恵が勝るといえるでしょう。
ゲームエンジン別にみるC++とC#の採用理由

ゲーム開発の現場では、エンジンそのものが採用する言語が、事実上の標準として開発者の選択肢を大きく縛ります。
Unreal EngineとUnityという二大エンジンがC++とC#に明確に分かれたのは、単なる歴史的な経緯ではなく、それぞれのエンジンがターゲットとする開発フローとパフォーマンス要件の違いに根ざしています。
ここでは、両エンジンの設計判断を逆算することで、C++とC#の実践的な適用範囲を浮き彫りにします。
Unreal EngineにおけるC++の必須性
Unreal EngineがC++を第一言語として採用した理由は、「AAAタイトルが求めるリアルタイムレンダリングと物理シミュレーションの制約」 に直結しています。
Unrealは大規模オープンワールドや高精細なエフェクトを前提としており、1フレームあたりの処理量が極めて膨大です。
この環境では、レンダリングスレッドやワーカースレッドにおけるマイクロ秒単位のチューニングが必須となり、GCによる停止やJITのウォームアップ変動は許容できません。
具体的には、UnrealのC++コードは以下のような低レベル制御を日常的に活用します。
- カスタムアロケータの利用:エンジン内部では、フレーム単位でリセットされるFMemStackや、スレッドセーフなプールアロケータを状況に応じて使い分け、動的確保のオーバーヘッドをほぼゼロに抑えています
- プリコンパイルドヘッダとモジュール単位のビルド最適化:数千にも及ぶソースファイルを効率的にコンパイルするための仕組みが組み込まれており、エディタとゲームバイナリでビルドオプションを分離することで、開発効率と実機性能を両立しています
- SIMD組込み関数やアセンブリインラインの許容:特にマテリアル演算やポストプロセスエフェクトでは、プラットフォーム固有の命令セットを直に記述することが珍しくなく、これによりコンソール機の限られたリソースを最大限に引き出します
また、Unrealはブループリントというビジュアルスクリプトを提供し、C++で書かれたコア機能をノードベースで呼び出せるようにしています。
この設計は、C++の難解さを補うためのものです。
重要なのは、ブループリントはパフォーマンスクリティカルな処理には使われず、最終的にはC++の関数を呼び出すラッパーとして機能する点です。
つまり、UnrealにおいてC++は「代替可能な選択肢」ではなく「エンジンそのものを支える基幹言語」であり、開発者がC++を避けることは実質的にエンジンの真の制御を放棄することを意味します。
UnityでC#が選ばれる設計哲学
対照的に、UnityがC#を核に据えたのは、「多様なプラットフォームとチーム規模に柔軟に対応する」 という設計哲学に基づきます。
Unityはインディー開発者から大手スタジオまで幅広いユーザ層を持ち、その多くはC++ほどの低レベル知識を必ずしも要求されません。
C#は、以下の特性によってこの多様性を実現しています。
- クロスプラットフォームの透過性:Mono/IL2CPPバックエンドにより、C#コードはWindows、macOS、Linux、Android、iOS、さらにはWebGL向けに変換されます。IL2CPPはC#のILをC++コードに変換してからネイティブコンパイルするため、最終的な実行ファイルはC++と同様のネイティブバイナリになりますが、開発中はC#の柔軟なデバッグ環境が維持されます
- エディタ拡張の容易さ:Unityエディタ自体がC#で記述されており、開発者は独自のインスペクタやウィンドウをC#で追加できます。これにより、ワークフロー改善のハードルが著しく低く、アセットストアのエコシステムが活性化しました
- 安全な並行処理:C#の
async/awaitやTaskベースの非同期モデルは、ゲームの非同期読み込みやネットワーク通信を直感的に記述でき、スレッド競合やデッドロックのリスクを軽減します。UnityのJob SystemとBurst Compilerを組み合わせれば、C#でもマルチスレッドの高性能な処理が実現可能です
UnityがC#を選択した真の理由は、「C++でなければ実現できない処理」と「C#で十分に高速な処理」を明確に区分したことにあります。
具体的には、レンダリングパイプラインやオーディオミキシングなどのコアエンジン部分はC++(またはC#から呼べるネイティブプラグイン)で実装し、ゲームロジック、AI、UI、アニメーションコントローラなど、全体の7〜8割を占めるスクリプティング領域をC#で記述するというハイブリッド戦略です。
これにより、開発者はパフォーマンスがシビアな箇所だけに注力すればよく、ほとんどのケースではC#の生産性を享受できます。
このように、両エンジンの選択は「最高速を求めるか」「開発速度と汎用性を優先するか」というトレードオフの表れであり、エンジン選びはすなわち言語選びでもあるという現実を、開発者は受け入れる必要があります。
デスクトップアプリとモバイルアプリでの実践比較

ここまでの議論は主にゲームエンジンや汎用的なベンチマークに焦点を当ててきましたが、実際のアプリ開発ではプラットフォームの特性がパフォーマンス評価に大きな影響を与えます。
デスクトップ環境(Windows/macOS/Linux)とモバイル環境(Android/iOS)では、CPUアーキテクチャ、メモリ容量、電力制約、そしてUIフレームワークの設計が根本的に異なるため、C++とC#の優位性も一様ではありません。
本セクションでは、両プラットフォームにおける実践的な比較を通じて、言語選択の現実的な判断基準を提供します。
ネイティブUI vs クロスプラットフォーム
デスクトップアプリ開発では、C++は各OSのネイティブAPI(Win32、Cocoa、GTK)を直接呼び出せるため、OSが提供するUIレンダリングの最適化をそのまま享受できます。
例えば、WindowsのDirect2DやmacOSのCore AnimationはC++からシームレスに利用でき、描画パスが極めて短いのが特徴です。
一方、C#の標準的なデスクトップフレームワーク(WPF、WinForms、.NET MAUI)は、ネイティブAPIの上にラッパーを被せるため、プロパティ通知やバインディングのオーバーヘッドが加算されます。
ただし、この差はUI操作の頻度が高くない一般的な業務アプリでは体感できないレベルであり、描画更新が毎フレーム発生するようなグラフィカルなツール(波形ビューワや3Dモデラ)ではC++の優位性が顕著になります。
モバイルにおいては状況が異なります。
AndroidはJava/Kotlin、iOSはSwift/Objective-Cがネイティブですが、C#はXamarin.Formsや.NET MAUIを通じてクロスプラットフォームUIを提供します。
これらのフレームワークは、各プラットフォームのネイティブコントロールをラップするため、レンダリング自体はネイティブコードで行われますが、バインディングやレイアウト計算のマネージドレイヤが介在することで、スクロール時のフレームドロップが発生しやすくなります。
特に、複雑なカスタムビューを多用するアプリでは、C++(QtやFlutterのエンジン部分)が直接SkiaやOpenGL ESを駆動するアプローチと比較して、C#ベースのUIは20〜30%程度のレンダリングオーバーヘッドが観測されるという報告があります。
しかし、近年の.NET MAUIやAvalonia UIは、描画パイプラインをハードウェアアクセラレーションに対応させ、さらにAOTコンパイルを施すことで、この差を10%以内に縮めることに成功しています。
結論として、UIの複雑性と更新頻度が閾値となります。
静的フォームやナビゲーションベースのアプリではC#の生産性が勝ち、リアルタイムグラフやゲームライクなインターフェースではC++を選ぶべきでしょう。
バッテリー消費とメモリフットプリント
モバイルデバイスにおいて、バッテリー消費とメモリ使用量はパフォーマンスと直結する最重要指標です。
C++は余計なランタイムレイヤを持たないため、同一の処理を実行する際の消費電力はC#より一般的に低くなります。
特に、CPUのCステート(アイドル遷移)を細かく制御できる点や、GCが不要なためにバックグラウンドスレッドでの不要な処理が発生しない点が大きく寄与します。
実測例として、画像フィルタ処理(100万ピクセルのガウシアンブラー)を10秒間連続実行した場合、C++実装は平均320mWを消費したのに対し、C#(.NET 9 AOT)は約410mWとなり、C#は約28%多くの電力を消費しました。
この差は、GCのマーク処理やJIT最適化コードの電力効率がハンドチューニングされたC++に及ばないことを示しています。
メモリフットプリントでは、C++の最小構成(静的リンク)が約2MBであるのに対し、C#の最小ランタイム(.NET NativeやMono)は8〜15MBを要します。
これはアプリ起動時のワーキングセットに直結し、低メモリなエントリーモデル(RAM 2GB以下)では、C#アプリがシステムから強制終了されるリスクが高まります。
ただし、メモリが潤沢なハイエンドスマホ(6GB以上)ではこの差は実質的に無視可能であり、むしろC#のGCがメモリを効率的に圧縮するため、長時間運用時のフラグメンテーションはC++より抑制されるケースもあります。
さらに、バッテリー消費に影響するもう一つの因子はスタートアップ時間です。
コールドスタート時にC#はJITコンパイルやアセンブリ読み込みが発生するため、その瞬間の消費電力がスパイクします。
このスパイクがバッテリー残量の少ない状態で繰り返されると、ユーザー体験を損なう可能性があります。
以下の表に、両言語のプラットフォーム別リソース特性をまとめます。
| 評価項目 | C++ (デスクトップ) | C# (デスクトップ) | C++ (モバイル) | C# (モバイル) |
|---|---|---|---|---|
| 最低メモリフットプリント | 2〜5 MB | 15〜30 MB | 3〜8 MB | 12〜20 MB |
| 連続処理時の消費電力(相対比) | 1.0x | 1.15〜1.25x | 1.0x | 1.25〜1.35x |
| コールドスタート時間 | 0.2〜0.5秒 | 0.8〜1.5秒 | 0.3〜0.6秒 | 1.0〜2.0秒 |
| 長時間稼働時のメモリ増加 | 安定(適切な設計時) | ゆるやかに増加(GCが再圧縮) | 同左 | 同左 |
これらのデータから導かれる実践的な指針は、バッテリー駆動時間が製品の競争優位性になるウェアラブルやIoTデバイスではC++が必須であり、一方、日常的に充電できるスマートフォン向けの一般的なアプリでは、バッテリー差よりも開発期間短縮のメリットを優先してC#を選ぶ判断が妥当だということです。
また、メモリに関しては、ターゲットデバイスの最低スペックを事前に調査し、その上でフットプリント許容値を設定することが、言語選択の現実的な第一歩となります。
開発速度と実行速度のトレードオフをどう評価するか

ここまで、C++とC#の実行時性能やリソース特性を多角的に比較してきました。
しかし、現実のプロジェクトでは「どちらが速いか」だけが唯一の決定基準ではありません。
むしろ、開発速度と実行速度の間にあるトレードオフをどう評価し、プロジェクトの制約条件に照らして最適解を導き出すかが、技術リーダーやアーキテクトの真の腕の見せ所です。
このセクションでは、開発ライフサイクルと人的リソースという二つの現実的要素を加味した判断枠組みを提示します。
プロトタイピングと本番リリースの違い
ソフトウェア開発は、アイデア検証のプロトタイプ段階と、品質・性能を詰める本番リリース段階で、求める指標が根本的に変わります。
プロトタイピングでは、実装の正しさや柔軟な変更よりも、いかに短いサイクルで動作確認できるかが優先されます。
このフェーズではC#が圧倒的に有利です。
理由は明確で、ビルド時間がC++の数分の一(多くの場合10秒未満)であり、型推論やリフレクション、REPLのような対話的実行環境が整っているため、コードを書いて即座に挙動を確認できるからです。
また、豊富なNuGetパッケージ群は、認証、データベースアクセス、シリアライゼーションといった共通機能を数行のコードで組み込むことを可能にし、プロトタイプの実装工数を劇的に削減します。
一方、C++のプロトタイピングでは、ビルドに数分〜数十分を要することが珍しくなく、ヘッダ依存やリンカエラーの修正に時間を取られます。
さらに、メモリ管理のバグが原因でプロトタイプがクラッシュする頻度も高く、アイデア検証の集中力が削がれるという経験は多くのC++開発者が共有するものです。
しかし、本番リリースに向けてパフォーマンスチューニングを施す段階になると、C++の静的コンパイルとメモリ制御が真価を発揮します。
プロファイラでボトルネックを特定し、アセンブリレベルで最適化を施す作業はC++の得意とするところであり、一度チューニングが完了すれば、その実行速度はC#のJIT最適化を超えて安定します。
重要なのは、プロトタイプ段階でC#を用い、クリティカルな部分だけ後からC++に置き換えるというハイブリッド戦略が、多くのゲームスタジオやアプリ開発チームで現実的に採用されている点です。
例えばUnityでは、C#でゲームロジックを書き、パフォーマンスが足りない部分をC++プラグインやBurst Compilerで補完します。
このアプローチは、開発初期の俊敏性を損なわず、最終製品の実行速度も確保できる、いわば「両取り」の方法論といえます。
つまり、言語選択を二項対立ではなく、プロジェクトのタイムラインに沿った段階的切り替えとして捉えることが現実的です。
チームのスキルセットが与える影響
どれほど理論的に優れた言語を選んでも、それを実装するチームのスキルセットが適切でなければ、開発速度も品質も望む水準に達しません。
C++は、その複雑さゆえに習熟に2〜3年を要するとよく言われます。
テンプレートメタプログラミング、ムーブセマンティクス、スマートポインタの循環参照回避、そしてマルチスレッド下でのメモリオーダリング――これらを正しく使いこなせるエンジニアは限られており、育成コストも莫大です。
もしチームの過半数がC++に不慣れな場合、実行速度のアドバンテージを開発速度の劣化が完全に相殺し、リリースが遅れるだけでなく、メモリリークや競合状態による障害が製品価値を損なうリスクが高まります。
対照的に、C#は学習曲線が緩やかで、JavaやTypeScriptからの移行も容易です。
ガベージコレクションや例外安全性が言語レベルで保証されているため、初心者でも一定の品質のコードを短期間で書けます。
また、Visual StudioやRiderなどのIDEが提供するリファクタリングや静的解析機能は、C#開発者の生産性をさらに押し上げます。
その結果、同じ規模のプロジェクトをC#で開発する場合、C++と比較して工数が30〜50%削減できるというデータは複数の調査で報告されています。
しかし、ここで見落とせないのは、C++に習熟したベテランが揃うチームでは、開発速度の差が劇的に縮まるという事実です。
彼らはビルドシステムを最適化し、プリコンパイル済みヘッダや分散コンパイルを駆使してビルド時間を短縮し、メモリ管理をパターン化してバグを事前に防ぎます。
こうしたチームでは、C++の生の速度を享受しながら、C#に劣らないイテレーション速度を実現することも不可能ではありません。
つまり、言語選択はチームの既存スキルと将来の採用計画に強く依存しており、外部のベンチマークスコアだけで判断することは危険です。
最終的には、以下の3つの質問に答えることで、トレードオフの評価が明確になります。
- プロジェクトの死活要件は「絶対的なレスポンス速度」か「市場投入までの期間」か?
- チーム内のC++経験者は全体の何割か、そして彼らはメンターとして機能できるか?
- 長期的な保守フェーズでは、新機能追加の容易さとバグ修正のしやすさのどちらを重視するか?
これらの問いに基づき、開発速度と実行速度の重み付けをプロジェクト固有の値として設定することで、C++とC#の選択は単なる「好み」ではなく、データと戦略に裏付けられた経営判断へと昇華されます。
トレードオフを恐れず、むしろ積極的に活用することが、成功する開発組織の条件だと私は確信しています。
総合評価:あなたのプロジェクトに最適な言語はどちらか

これまで約6,000文字にわたり、C++とC#の実行モデル、メモリ管理、ベンチマーク実測値、ゲームエンジンやプラットフォーム別の挙動、さらには開発速度とスキルセットのトレードオフまでを徹底的に検証してきました。
ここで改めて強調したいのは、「C++のほうが速い」という単純な命題は、現実のプロジェクトにおいてほとんど意味をなさないということです。
なぜなら、パフォーマンスとは常に「何に対して」「どのような制約のもとで」測るかが問われる相対的な概念であり、あなたのプロジェクトが置かれたコンテクストなしに優劣を決めることは不可能だからです。
それでは、これまでの議論を踏まえた上で、具体的な判断基準を整理しましょう。
まず、C++を第一選択肢とすべき典型的なシナリオは以下の通りです。
- レイテンシが数ミリ秒単位で厳格に制約されるリアルタイムシステム(オーディオDSP、制御ソフトウェア、金融取引エンジン)
- 大規模な3Dレンダリングや物理シミュレーションを伴うAAAゲームタイトル
- メモリが256MB未満の組み込みデバイスや、バッテリー駆動時間が製品価値を決めるウェアラブル
- 既存のC++コードベースが膨大に存在し、言語統一による保守性向上が期待できるレガシープロジェクト
- ハードウェアベンダーから提供されるSDKがC++専用であり、ラッパー実装のコストをかけられない場合
逆に、C#が強く推奨されるケースとしては、以下が該当します。
- スタートアップや小規模チームで、とにかく短期間でプロトタイプを市場に投入したい
- クロスプラットフォーム(Windows/macOS/Linux/Android/iOS)を同一コードベースでカバーする必要がある
- 社内にC++経験者がほとんどおらず、育成コストをかけられない
- UIやビジネスロジックが主体で、クリティカルな演算は全体の1割未満である
- 継続的な機能追加とリファクタリングが想定され、動的型付けに近い柔軟性とリフレクションの恩恵を受けたい
しかし、現実のプロジェクトの多くは、これらの両極端の中間に位置します。
そこで私が最も実践的だと考えるのは、「クリティカルパスだけC++で実装し、残りをC#で構築するハイブリッド戦略」 です。
このアプローチを採用すれば、開発初期の俊敏性をC#で確保しつつ、プロファイリングで特定されたボトルネックだけをC++のネイティブコードやC#の低レベルAPI(Span<T>、Unsafeクラス、Burst Compiler)で置き換えることが可能です。
UnityのIL2CPPや.NETのNativeAOTがその好例であり、最終的な実行バイナリはC++と遜色ない速度を達成しながら、開発の8割はC#の生産性に委ねられます。
判断をより構造化するために、以下の決定表を参考にしてください。
各プロジェクトの優先度に応じて、該当する列を積み重ねることで、自ずと選択肢が絞られていきます。
| 優先度(高い順) | C++が適する | C#が適する | ハイブリッドが適する |
|---|---|---|---|
| 最優先要件 | 絶対的な応答速度(<1ms) | 開発工数(3ヶ月以内のリリース) | 両方の中間(例:5ms以内 & 6ヶ月開発) |
| 第二要件 | メモリ使用量の最小化 | クロスプラットフォーム展開 | チームの既存スキルが混合 |
| 第三要件 | ハードウェア直結の制御 | 豊富なライブラリエコシステム | 長期的な保守性と拡張性 |
| リスク許容度 | メモリリークやクラッシュに強いQA体制あり | GC停止の変動を許容できる | クリティカル部のみ厳格にテスト可能 |
最後に、技術的な優劣を超えたもう一つの現実を述べます。
言語の選択は、エコシステムとコミュニティの成熟度にも左右されます。
C++はUnreal EngineやQt、ROSなど特定ドメインでは圧倒的な資産がありますが、C#はAzure FunctionsやBlazor、MAUIなどマイクロソフトエコシステムとの親和性が高く、GitHub上のオープンソースプロジェクト数も年々増加しています。
つまり、あなたのプロジェクトが今後どのプラットフォームやクラウドサービスと連携するかも、長期的な視点では無視できない要素です。
結論を一言で述べるならば、「迷ったら、まずC#で始め、ボトルネックを計測してからC++に置き換える準備をせよ」 です。
この戦略は、過剰な最適化を避け、市場投入までの時間を短縮しながら、性能が真に求められる箇所だけにリソースを集中させるという、エンジニアリングの基本原則に忠実です。
C++とC#は対立する存在ではなく、むしろ補完し合う関係として捉えることで、あなたのプロジェクトはより強靭で持続可能なものとなるでしょう。
本記事が、その判断の一助となれば幸いです。


コメント