Windows ランタイム コンポーネントと相互運用性の最適化
相互運用性のパフォーマンスの問題を回避しながら、ネイティブ型とマネージド型の間で Windows ランタイム コンポーネントと相互運用機能を使う Windows アプリを作成します。
Windows ランタイム コンポーネントとの相互運用性のベスト プラクティス
Windows ランタイム コンポーネントを使うとアプリのパフォーマンスに大きな影響を与える場合があるので、注意してください。 このセクションでは、Windows ランタイム コンポーネントを使用するアプリで高パフォーマンスを実現する方法について説明します。
概要
相互運用性はアプリのパフォーマンスに大きな影響を与えますが、その存在を意識せずに利用していることも多いでしょう。 Windows ランタイムは、開発者に代わって多数の相互運用性を処理し、生産性の向上と、他の言語で記述されたコードの再利用を実現します。 Windows ランタイムの機能を活用することをお勧めしますが、一方でそれがパフォーマンスに与える影響を認識しておく必要があります。 このセクションでは、相互運用性がアプリのパフォーマンスに与える影響を軽減するためにできることについて説明します。
Windows ランタイムには、ユニバーサル Windows プラットフォーム アプリの作成に使用できるすべての言語からアクセスできる型ライブラリが用意されています。 C# または Microsoft Visual Basic で、Windows ランタイムの型を .NET オブジェクトと同様に使用することができます。 Windows ランタイム コンポーネントにアクセスするために、プラットフォーム呼び出しメソッドを呼び出す必要はありません。 これによってアプリの作成作業が大幅に簡素化されますが、そのために相互運用性の利用が想像以上に発生していることを認識することが重要です。 Windows ランタイム コンポーネントが C# または Visual Basic 以外の言語で記述されている場合、コンポーネントを使うためには相互運用性の境界を越えることが必要になります。 相互運用性の境界を越えると、アプリのパフォーマンスに影響する場合があります。
ユニバーサル Windows プラットフォーム アプリを C# または Visual Basic で開発する際、最もよく使用される API は、Windows ランタイム API と UWP アプリ用 .NET API の 2 つです。 一般に、Windows によって提供される型は、"Windows." および .NET 型で始まる名前空間の Windows ランタイム上に構築され、"System." で始まる名前空間に含められます。ただし、例外があります。 UWP アプリ用 .NET に含まれる型は、使う際に相互運用性を必要としません。 Windows ランタイムを使っている領域のパフォーマンスが低い場合は、代わりに UWP アプリ用 .NET を使うことで良好なパフォーマンスを得られることがあります。
注 Windows 10 と共に提供される Windows ランタイム コンポーネントの大部分は C++ で実装されているため、これらを C# または Visual Basic から使う場合は、相互運用性の境界を越える必要があります。 通常の場合と同様に、アプリのパフォーマンスに Windows ランタイム コンポーネントが影響を与えているかどうかを調査した後で、コードの変更に注力してください。
このトピックでは、"Windows ランタイム コンポーネント" とは、C# または Visual Basic 以外の言語で記述された Windows ランタイム コンポーネントを意味します。
Windows ランタイム コンポーネントのプロパティにアクセスする、またはメソッドを呼び出す場合は、常に相互運用性のコストが発生します。 実際、Windows ランタイム コンポーネントの作成は .NET オブジェクトの作成よりも高コストになります。 その理由は、Windows ランタイムでは、アプリの言語からコンポーネントの言語に移行するためのコードを実行する必要があるためです。 またコンポーネントにデータを渡す場合も、マネージ型とアンマネージ型の間でデータの変換が必要です。
Windows ランタイム コンポーネントとを効率的に使用する
パフォーマンスを高める必要がある場合は、現在のコードにおける Windows ランタイム コンポーネントの使用効率を可能な限り高めることができます。 このセクションでは、Windows ランタイム コンポーネントを使用する場合のパフォーマンスを向上させるためのヒントについて説明します。
短時間で大量の呼び出しが行われることで、パフォーマンスに対する影響が顕著に表れます。 ビジネス ロジックおよびその他のマネージド コードからの Windows ランタイム コンポーネントへの呼び出しをカプセル化している適切に設計されたアプリケーションでは、相互運用性の大きなコストが発生することはありません。 Windows ランタイム コンポーネントを使うことでアプリのパフォーマンスに影響が発生していることがテストによってわかった場合、このセクションのヒントを活用してパフォーマンスを向上してください。
UWP アプリ用 .NET で提供される型の使用を検討する
あるタスクを、Windows ランタイム型または UWP アプリ用 .NET で提供される型のどちらを使っても達成できる場合があります。 こうした場合、.NET の型と Windows ランタイム型を組み合わせず、 どちらか一方に揃えることをお勧めします。 たとえば、xml のストリームを解析するには、Windows.Data.Xml.Dom.XmlDocument 型 (Windows ランタイム型) または System.Xml.XmlReader 型 (.NET の型) のどちらも使えます。 同じテクノロジの API をストリームとして使いましょう。 たとえば、MemoryStream から xml を読み取る場合は両方が同じ .NET の型になるので、System.Xml.XmlReader 型を使います。 ファイルから読み取る場合は、Windows.Data.Xml.Dom.XmlDocument 型を使います。これはファイル API と XmlDocument が両方ともネイティブ Windows ランタイム コンポーネントに実装されているためです。
Windows ランタイム オブジェクトを .NET 型にコピーする
Windows ランタイム コンポーネントが Windows ランタイム オブジェクトを返す場合、返されるオブジェクトを .NET オブジェクトにコピーすると便利な場合があります。 これが特に重要になる 2 つの場面は、コレクションとストリームを使うときです。
コレクションを返す Windows ランタイム API を呼び出して、その後そのコレクションを保存し何度もアクセスする場合、このコレクションを .NET コレクションにコピーし、それ以降は .NET バージョンを使うと便利です。
Windows ランタイム コンポーネントへの呼び出しの結果を後で使用するためにキャッシュする
Windows ランタイム型に繰り返しアクセスする代わりに、値をローカル変数に保存することでパフォーマンスを向上できる場合があります。 ループ内で値を使用する場合、この方法は特に有効です。 ローカル変数を使うことでアプリのパフォーマンスが向上できるかどうか、アプリを調べてください。 キャッシュされた値を使用することでアプリが高速化されるのは、相互運用性に費やされる時間が短縮されるためです。
Windows ランタイム コンポーネントへの呼び出しを結合する
UWP オブジェクトの呼び出しは必要最小限にしてタスクを実行するようにしましょう。 たとえば、通常、ストリームのデータは少量を複数同時に読み取るよりも一度に大量に読み取ることをお勧めします。
処理量が少なく呼び出し数が多くなる API の代わりに、処理をできる限り少ない呼び出しにバンドルする API を使います。 たとえば、既定のコンストラクターを呼び出してプロパティを 1 つずつ割り当てるのではなく、複数のプロパティを初期化する複数のコンストラクターを一度呼び出して、オブジェクトを作成することをお勧めします。
Windows ランタイム コンポーネントを作成する
C++ または JavaScript で記述されたアプリで使用できる Windows ランタイム コンポーネントを作成する場合、高いパフォーマンスを実現できるようにコンポーネントを設計する必要があります。 高パフォーマンスなアプリを実現するためのすべての推奨事項は、高パフォーマンスなコンポーネントを実現する際にも当てはまります。 コンポーネントを測定してトラフィックが大きいパターンを持つ API を特定し、それらの領域については、少ない呼び出しでユーザーが作業を実行できるようにする API を提供することを検討してください。
マネージ コードで相互運用性を使うときのアプリの速さの維持
Windows ランタイムでは、ネイティブ コードとマネージド コードの間で簡単に相互運用性を利用できますが、注意しないとパフォーマンスが低下する場合があります。 ここでは、マネージ UWP アプリで相互運用機能を使う場合に高いパフォーマンスを実現する方法について説明します。
Windows ランタイムでは、各言語に Windows ランタイム API のプロジェクションが用意されているため、開発者は XAML と任意の言語でアプリを記述できます。 アプリを C# または Visual Basic で記述する場合は、この便利さの一方で、相互運用性のコストが高くなります。これは、Windows ランタイム API は通常ネイティブ コードで実装され、C# または Visual Basic から Windows ランタイムを呼び出すときには、マネージド スタック フレームからネイティブ スタック フレームへの CLR の移行と、ネイティブ コードでアクセスできる表現への関数パラメーターのマーシャリングが必要になるためです。 このオーバーヘッドは、ほとんどのアプリでは無視できます。 ただし、アプリの重要なパスで Windows ランタイム API の呼び出しを多数 (数十万回から数百万回) 実行する場合は、このコストが無視できなくなる可能性があります。 一般に、言語間の移行に費やされる時間は、コードの他の部分の実行に対して小さくする必要があります。 この概念を示したのが次の図です。
「Windows アプリ用 .NET」に一覧の型では、C# または Visual Basic から使ったときにこの相互運用性コストの問題は発生しません。 一般に、"Windows." で始まる名前空間の型は Windows によって提供される Windows ランタイム API セットに属し、"System." で始まる名前空間の型は .NET 型に属します。 Windows ランタイム型の対する単純な使用でも、割り当てやプロパティへのアクセスで相互運用性のコストが発生する点に注意してください。
相互運用性のコストを最適化するためには、アプリの実行時間の大部分が相互運用に費やされているかどうかの評価と判断が必要です。 Visual Studio でアプリのパフォーマンスを分析する際は、 [関数] ビューを使い、Windows ランタイムのメソッド呼び出しに費やされている包括時間を調べることで、相互運用性コストの上限を簡単に把握できます。
相互運用のオーバーヘッドによってアプリが低速になる場合は、実行頻度の高いコード パスでの Windows ランタイム API の呼び出しを減らすことで、パフォーマンスを向上できます。 たとえば、UIElements の位置とサイズを継続的に照会することで大量の物理的計算を実行しているゲーム エンジンは、UIElements から必要な情報をローカル変数に格納し、それらのキャッシュされた値に対して計算を行い、計算実行後に最終結果を UIElements にもう一度割り当てることによって、多くの時間を節約できます。 別の例として、C# または Visual Basic のコードから頻繁にアクセスされるコレクションがある場合は、System.Collections 名前空間からのコレクションではなく、Windows.Foundation.Collections 名前空間からのコレクションを使ったほうが、より効率的です。 Windows ランタイム コンポーネントの呼び出しを結合することも検討に値します。これは、たとえば、Windows.Storage.BulkAccess API を使用して実現できます。
UWP コンポーネントを構築する
C++ または JavaScript で記述されたアプリに使う Windows ランタイム コンポーネントを作成する場合は、高パフォーマンスを実現できるようにコンポーネントを設計します。 コンポーネントの API サーフェスによって、相互運用性の境界が決まるだけでなく、このトピックで取り上げた事柄をコンポーネントのユーザーがどの程度考慮しなければならないかも変わってきます。 コンポーネントを第三者に配布する場合は、この点が特に重要となります。
高パフォーマンスなアプリを実現するためのすべての推奨事項は、高パフォーマンスなコンポーネントを実現する際にも当てはまります。 コンポーネントを測定してトラフィックが大きいパターンを持つ API を特定し、それらの領域については、少ない呼び出しでユーザーが作業を実行できるようにする API を提供することを検討してください。 Windows ランタイムは、それを利用するアプリが相互運用性の境界を何度も行き来しなくても済むように、その設計には膨大な労力が費やされています。