April 2012
Volume 27 Number 04
CLR - .NET 4.5 におけるパフォーマンス向上の概要
Ashwin Kamath | April 2012
この記事では、Microsoft .NET Framework 4.5 のプレリリース版を取り上げており、記載しているプレリリース版関連の情報はすべて変更されることがあります。
Microsoft .NET Framework チームは、開発者にとってパフォーマンスの向上が、少なくとも新しいランタイム機能やライブラリ API を追加するのと同じくらい重要であることを常に認識しています。.NET Framework 4.5 では、あらゆるアプリケーション シナリオにメリットがあるように、パフォーマンスの向上に関して多くの作業を行いました。さらに、.NET 4.5 は .NET 4 の更新版なので、.NET 4 アプリケーションでも既存の .NET 4 機能の多くでパフォーマンスが向上します。
開発者が、アプリケーションのエクスペリエンスを向上するのに非常に重要なのが、起動時間 (msdn.microsoft.com/magazine/cc337892)、メモリ使用量 (msdn.microsoft.com/magazine/dd882521)、スループット、および応答性の高さです。Microsoft .NET Framework チームは、アプリケーションさまざまなのシナリオについて、こうしたメトリックスの向上目標を設定し、その目標を満たすか上回るために変更方法を設計してきました。ここでは、.NET Framework 4.5 で行った主なパフォーマンス向上について大まかに説明します。
CLR
今回のリリースでは、パフォーマンスを向上するために複数のプロセッサ コアの活用に重点を置き、ガベージ コレクターの待機時間の短縮や、ネイティブ イメージのコード品質の向上に取り組みました。以下に、パフォーマンスを向上した主要機能の一部を示します。
マルチコア JIT (Just-in-Time): 基盤となるハードウェアの進化を絶えず監視し、チップ ベンダーと連携してハードウェアを活かし、最高のパフォーマンスを実現します。具体的には、マルチコア チップが使用できるようになって以来、パフォーマンス ラボでマルチコア チップを使用して、ハードウェアの特定の変更を活かすように適切な変更を加えてきました。ただし、当初は、こうした変更によってメリットを得られる開発者はごくわずかです。
現在では、複数のコアを必要とする新しい機能をすぐに幅広く使用できるように、ほぼすべての PC に少なくとも 2 つのコアが搭載されています。.NET 4.5 の開発当初は、エクスペリエンス全体の速度を上げるために、(具体的にはアプリケーション起動の一環として) JIT コンパイルのタスクを複数のプロセッサ コアで共有することが理にかなっているかどうかを判断しようと考えました。その調査の中で、十分に管理されたアプリケーションには、JIT コンパイルされるメソッド数に最小しきい値があり、この共有は有意義なものであることがわかりました。
機能は JIT コンパイルされるメソッドによって動作します。このメソッドは、主にバックグラウンド スレッドで実行され、マルチコア コンピューターでは、もう 1 つのコアで並列に実行されます。理想的には、2 つ目のコアがアプリケーションのメインの流れに先行し、必要になる前に大部分のメソッドを JIT コンパイルしてしまうことです。こうして事前にコンパイルするメソッドを特定するため、この機能では実行されるメソッドを追跡するプロファイル データを生成します。その後、次回実行時にこのプロファイル データによって機能の実行がガイドされます。プロファイル データを必ず生成することが、この機能を操作する基本的な方法です。
ランタイムのこの機能を使えば最小限のコードを追加するだけで、クライアント アプリケーションと Web サイト両方の起動時間を大幅に短縮できます。具体的には、System.Runtime 名前空間の ProfileOptimization クラスにある 2 つの静的メソッドを直接呼び出します。詳細については、MSDN ドキュメントを参照してください。この機能は、ASP.NET 4.5 アプリケーションと Silverlight 5 アプリケーションでは既定で有効になります。
ネイティブ イメージの最適化: いくつかのリリースでは、ネイティブ イメージの生成 (NGen: Native Image Generation) というツールを使用して、コードをネイティブ イメージにプリコンパイルできます。プリコンパイルされたネイティブ イメージは、通常、JIT コンパイルを使用するよりもアプリケーションの起動が大幅に速くなります。今回のリリースでは、ネイティブ イメージのレイアウトを最適化し、さらにパフォーマンスを向上できる Managed Profile Guided Optimization (MPGO) という補助ツールを導入しています。MPGO は、概念的には先ほど説明したマルチコア JIT と非常によく似た、プロファイルによるガイド付き最適化テクノロジを使用します。アプリケーションのプロファイル データには、代表的なシナリオまたは一連のシナリオが含まれています。これらのシナリオを使用すると、ネイティブ イメージのレイアウトを再変更することで、起動時に必要なメソッドとその他のデータ構造をネイティブ イメージの一部に集中的に配置できます。これにより、起動時間が短縮され、ワーキング セット (アプリケーションのメモリ使用量) が減少します。社内のテストやエクスペリエンスでは、MPGO によってメリットを得られるのは、通常、大規模マネージ アプリケーション (大規模 GUI アプリケーションなど) であることが確認されました。そのため、大規模マネージ アプリケーションでは MPGO を使用することをお勧めします。
MPGO ツールは、中間言語 (IL) DLL のプロファイル データを生成し、そのプロファイルをリソースとして IL DLL に追加します。NGen ツールを使用してプロファイリング後に IL DLL をプリコンパイルすると、プロファイル データが存在するため、NGen ツール によりさらなる最適化が行われます。このプロセス フローを図 1 に示します。
図 1 MPGO ツールを使用したプロセス フロー
大きなオブジェクト ヒープ (LOH) のアロケーター: 多くの .NET 開発者が、LOH 断片化の問題に関する解決策や、LOH を強制的に最適化する方法を求めていました。LOH の動作の詳細については、Maoni Stephens による 2008 年 6 月号の「CLR 徹底解剖」コラム (msdn.microsoft.com/magazine/cc534993) を参照してください。この記事を要約すると、サイズが 85,000 バイト以上のオブジェクトはすべて LOH 上に配置されます。現時点では、LOH は最適化されません。LOH の最適化には、ガベージ コレクターが大きなオブジェクトを移動することで処理の負荷が高くなることから、長い時間を要します。LOH 上のオブジェクトのガベージ コレクションが行われると、コレクションの対象にならなかった複数のオブジェクトの間に空き領域が生まれます。その結果、断片化の問題が発生します。
もう少し説明を加えると、CLR は、使用されていないオブジェクトの空きリストを作成し、大きなオブジェクトの割り当て要求を満たすために、使用されていないオブジェクトを後で再利用できるようにします。使用されていないオブジェクトが隣接して複数存在する場合は、1 つの空きオブジェクトに結合されます。最終的に、プログラムは、LOH に新たなオブジェクトを割り当てる際に、大きな有効オブジェクトの間にある空きメモリの断片が小さすぎるという状況に陥ることがあります。そして最適化という選択肢がないため、すぐに問題にぶつかります。これがアプリケーションが応答しなくなる原因になり、最終的にメモリ不足例外が発生します。
.NET 4.5 では、断片化した LOH メモリを効率的に使用できるよう、いくつか変更が加えられています。特に変更を加えたのが、空きリストの管理方法です。この変更は、ワークステーションとサーバー ガベージ コレクション (GC) の両方に適用されます。LOH オブジェクトに関する 85,000 バイトの制限は変わりません。
サーバー用バックグラウンド GC: .NET 4 では、ワークステーション GC 用にバックグラウンド GC を可能にしました。それ以来、ヒープ サイズの上限が数ギガバイトから数十ギガバイトのコンピューターをよく目にするようになりました。.NET Framework チームが所有するような最適化された並列ガベージ コレクターでも、こうした大きなヒープはコレクションに数秒かかり、その間アプリケーションのスレッドをブロックします。サーバー用バックグラウンド GC では、サーバーのガベージ コレクターに同時実行ガベージ コレクションのサポートを取り入れています。これにより、アプリケーションのスループットを高い状態に維持しながら、長時間のブロックを伴うガベージ コレクションを最小限に抑えることができます。
サーバー GC を使用している場合、サーバーのバックグラウンド GC が自動的に開始されるため、この新機能を使用するのに必要なことは何もありません。クライアント GC 用でもサーバー GC 用でも、バックグラウンド GC の高度な特性は同じで、次のような特性があります。
- 完全 GC (世代 2) のみバックグラウンドで行うことができる。
- バックグラウンド GC は最適化を行わない。
- フォアグラウンド GC (世代 0/世代 1 の GC) は、バックグラウンド GC 中に行うことが可能で、サーバー GC は専用のサーバー GC スレッドで行われる。
- 完全なブロッキング GC も専用のサーバー GC スレッドで行われる。
非同期プログラミング
新しい非同期プログラミング モデルが Visual Studio Async CTP の一部として導入され、.NET 4.5 では重要な部分になっています。.NET 4.5 のこの新しい言語機能を使用すると、生産性の高い方法で非同期コードを記述することが可能になります。この新モデルを実現するのは、"async" と "await" という、C# と Visual Basic の新しい 2 つの言語キーワードです。.NET 4.5 も、この新しいキーワードを使った非同期アプリケーションをサポートするよう更新を行いました。
MSDN の Visual Studio Asynchronous Programming ポータル (msdn.microsoft.com/vstudio/async、英語) は、新しい言語機能やサポートに関するサンプル、ホワイト ペーパー、およびフォーラムの優れたリソースです。
並列コンピューティング ライブラリ
.NET 4.5 の並列コンピューティング ライブラリ (PCL) では、既存の API を拡張する多くの改良を行いました。
高速かつ軽量なタスク: System.Threading.Tasks.Task クラスと Task<TResult> クラスは、メモリの使用量を減らして主要シナリオでの実行が高速になるよう最適化しました。特に、Task の作成と継続のスケジュール設定に関連する処理のパフォーマンスが、最大 60% の向上しています。
並列 PLINQ クエリ実行の増加: PLINQ は、クエリを並列処理することで、シーケンシャル実行よりも PLINQ による悪影響が大きくなる (動作が遅くなる) と認識すると、シーケンシャル実行にフォールバックします。こうした決定はデータに基づく推測によるもので、毎回正しいとは限りません。また、.NET 4.5 では、PLINQ が問題なく並列処理できると認識するクエリのクラスがさらに多くなります。
高速同時実行コレクション: 特定のシナリオでの処理速度を上げるため、System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue> に多数の変更を加えました。
この変更に関する詳細については、並列コンピューティング プラットフォーム チームのブログ (blogs.msdn.com/b/pfxteam、英語) を参照してください。
ADO.NET
Null ビット圧縮行のサポート: Null データは、SQL Server 2008 のスパース列機能を利用するユーザーに特によく使用されます。スパース列機能を使用するユーザーは、多数の Null 列を含む結果セットを生成する可能性が高くなります。このようなシナリオには、行の Null ビット圧縮 (SQLNBCROW トークン、または単に NBCROW) が導入されます。これにより、NULL 値が格納された複数の列をビット マスクに圧縮することで、サーバーから多数の列と一緒に送信される結果セット行の使用領域を削減できます。したがって、データ内に Null 値の列が多くある場合、表形式データ ストリーム (TDS) プロトコルのデータ圧縮に大いに役立ちます。
Entity Framework
LINQ クエリの自動コンパイル: 現在 LINQ to Entities クエリを記述すると、Entity Framework は、C# または Visual Basic のコンパイラによって生成された式ツリーを確認して、SQL に変換 (コンパイル) します (図 2 参照)。
図 2 SQL に変換される LINQ to Entities クエリ
ただし、式ツリーを SQL にコンパイルする際、特にクエリが複雑な場合にはややオーバーヘッドが生じます。以前のバージョンの Entity Framework では、LINQ クエリを実行するたびにパフォーマンスが低下しないように、CompiledQuery クラスを使用する必要がありました。
この新バージョンの Entity Framework は、LINQ クエリの自動コンパイルという新機能をサポートしているため、自動実行する各 LINQ to Entities クエリがコンパイルされ、Entity Framework のクエリ プラン キャッシュに格納されます。次にクエリを実行するときは必ず、Entity Framework がクエリをキャッシュから見つけるため、すべてのコンパイル プロセスを再度実行する必要はありません。詳細については、bit.ly/iCaM2b (英語) を参照してください。
Windows Communication Foundation と Windows Workflow Foundation
Windows Communication Foundation (WCF) チームと Windows Workflow Foundation (WF) チームも、今回のリリースでは次のようなさまざまなパフォーマンスの向上を実現しました。
- TCP のアクティブ化におけるスケーラビリティの向上: TCP のアクティブ化に関しては、多くの同時接続ユーザーが継続的な再接続によって要求を送信すると、TCP ポート共有サービスがうまく拡張されない、という問題が数多く寄せられました。.NET 4.5 では、この問題を解決しています。
- WCF HTTP/TCP 用組み込み GZip 圧縮のサポート: この新しい圧縮では、圧縮率が最大 5 倍になると想定しています。
- WCF にとってメモリの使用率が高い場合のホストのリサイクル: メモリの使用率が高い (調整可能な設定) 場合に、LRU ロジック (最近使用されていないメモリを探すロジック) を使用して WCF サービスをリサイクルします。
- WCF 向けの HTTP 非同期ストリーミングのサポート: この機能を .NET 4.5 に実装することで、同期ストリーミングと同じスループットをはるかに優れたスケーラビリティで実現しました。
- WCF TCP 向けの世代 0 の断片化の改善
- 大きなオブジェクト向けに最適化した WCF 用の BufferManager: 大きなオブジェクトに対しては、より優れたバッファー プール機能を実装して世代 2 GC のコストが高くなるのを防いでいます。
- 式のキャッシュによる WF 検証の強化: WF の読み込みと実行のコア シナリオに関して、最大 3 倍の改善が見込まれています。
- WCF/WF に対するエンドツーエンドの Event Tracing for Windows (ETW) の実装: これはパフォーマンス向上機能ではありませんが、開発者がパフォーマンスを調査する際に役立ちます。
詳細については、Workflow チームのブログ (blogs.msdn.com/b/workflowteam、英語) と MSDN ライブラリの記事 (https://msdn.microsoft.com/ja-jp/library/dd456789(v=vs.110).aspx) を参照してください。
ASP.NET
共有ホスティングを行う場合のサイトの集積度 ("1 サイト当たりのメモリ消費量" とも定義されます) とサイトのコールド起動時間の向上は、ASP.NET チームにとって .NET 4.5 のパフォーマンスに関する 2 大目標になっています。
共有ホスティングのシナリオでは、多くのサイトが同じコンピューターを共有します。通常、このような環境ではトラフィックは少なくなります。数社のホスティング企業から提供されたデータでは、1 秒当たりの要求はほとんどの場合 1 rps を下回っており、不定期に最大で 2 rps またはそれ以上になることが示されています。これは、おそらく多くのワーカー プロセスが、長時間 (IIS 7 以上の既定では 20 分) アイドル状態になると終了していることを示しています。したがって、起動時間は非常に重要です。ASP.NET の起動時間とは、ワーカー プロセスがダウンした場合、または Web サイトが既にコンパイル済みである場合に、Web サイトが要求を受け取ってから応答するまでの時間です。
今回のリリースでは、共有ホスティングのシナリオで起動時間を短縮するためにいくつかの機能を実装しました。実装した機能を以下に示します。
- Bin アセンブリの格納 (共通アセンブリの共有): ASP.NET のシャドウ コピー機能では、(使用中のアセンブリが CLR によってロックされるために必要となる) AppDomain のアンロードを行うことなく、アプリケーション ドメインで使用しているアセンブリを更新できます。これは、アプリケーション アセンブリを個別の場所 (CLR が決める既定の場所またはユーザーが指定した場所) にコピーし、その場所からアセンブリを読み込むことで実現します。こうすることで、シャドウ コピーがロックされていても元のアセンブリを更新することができます。ASP.NET では、既定により Bin フォルダー アセンブリに対してこの機能を有効にしているため、サイトを起動している間 DLL が絶えず更新されます。
- ASP.NET では、Web サイトの Bin フォルダーを特殊なフォルダーとして認識します。このフォルダーは、ASP.NET アプリケーションから参照し、サイトのさまざまなページで共有する必要のあるカスタム ASP.NET コントロールやコンポーネントなどのコードのコンパイル済みアセンブリ (DLL) を格納します。Bin フォルダーのコンパイル済みアセンブリは、Web アプリケーション内のあらゆる場所で自動的に参照されます。ASP.NET では、Web サイトで使用するために、Bin フォルダーにある最新バージョンの特定の DLL も検出します。ASP.NET サイトで使用するようにあらかじめパッケージ化されたアプリケーションは、通常、グローバル アセンブリ キャッシュではなく Bin フォルダーにインストールされます。
- ASP.NET チームと CLR チームの調査により、多くのサイトが同じサーバー上に存在し、同じサーバー アプリケーションを使用する場合、シャドウ コピーの DLL がまったく同じものになりやすいことがわかりました。これらのファイルはディスクから読み取られてメモリに読み込まれるため、起動時間やメモリ使用量を増加する、不要な読み込みの原因になります。.NET Framework チームは CLR から参照するシンボリック リンクの使用に関して取り組み、共通ファイルの識別機能を実装して、それらのファイルを (シンボリック リンクによって示される) 特殊な場所に格納しました。ASP.NET では、Bin DLL を格納するシャドウ コピーが自動的に構成されます。これで、共有ホストでは、パフォーマンスのメリットが最大限に引き出されるため、ASP.NET ガイドラインに従ってコンピューターを設定できます。
- マルチコア JIT: 上記の「CLR」を参照してください。ASP.NET チームは、マルチコア JIT 機能を使用して、プロセッサ コア間に JIT コンパイルを分散することで起動時間を短縮しています。この機能は ASP.NET の既定で有効になっているため、この機能を利用するのに何か操作を行う必要はありません。この機能を無効にするには、web.config ファイルの次の設定を使用します。
<configuration>
<!-- ... -->
<system.web>
<compilation profileGuidedOptimizations="None" />
<!-- ... -->
- Prefetcher: Windows の Prefetcher テクノロジは、アプリケーション起動中のページ切り替えにかかるディスク読み取りコストの削減に非常に効果を発揮します。Prefetcher は、現在 Windows Server でも有効になっています (しかし既定ではありません)。高密度の Web ホスティングで Prefetcher を有効にするには、コマンド ラインで次の一連のコマンドを実行します。
sc config sysmain start=auto
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters" /v EnablePrefetcher /t REG_DWORD /d 2 /f
reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Prefetcher" /v MaxPrefetchFiles /t REG_DWORD /d 8192 /f
net start sysmain
- これで、web.config ファイルを更新して ASP.NET で使用できます。
<configuration>
<!-- ... -->
<system.web>
<compilation enablePrefetchOptimization
="true" />
<!-- ... -->
- 高密度の Web ホスティング用の GC のチューニング: GC は、サイトのメモリ使用量に影響を与える場合がありますが、調整することでより優れたパフォーマンスを実現できます。GC を調整または構成すると、CPU パフォーマンスを改善したり (コレクションの頻度が減少します)、メモリ使用量を減らしたりすることができます (つまり、コレクションの頻度が高いほどメモリが早く開放されます)。GC のチューニングを有効にするには、サイト当たりのメモリ使用量 (ワーキング セット) を削減するために、Windows\Microsoft.NET\Framework\v4.0.30319 フォルダーにある Aspnet.config ファイルの HighDensityWebHosting 設定を使用します。
<configuration>
<!-- ... -->
<runtime>
<performanceScenario
value="HighDensityWebHosting" />
<!-- ... -->
ASP.NET のパフォーマンス向上に関する詳細については、ホワイト ペーパー「次期バージョンの ASP.NET の概要 (英語)」(bit.ly/A66I7R) を参照してください。
フィードバックのお願い
ここで紹介したものがすべてではありません。ここでは、範囲を主要機能に限定するために省略しましたが、パフォーマンスを向上する細かい変更点はまだあります。それとは別に、NET Framework パフォーマンス チームは、Windows 8 でマネージ Metro スタイル アプリに特化したパフォーマンス向上にも取り組んでいます。NET Framework 4.5 と Visual Studio 11 Beta for Windows 8 をダウンロードおよび試用したら、今後のリリースに関するフィードバックやご意見をお寄せください。
用語集
共有ホスティング: "共有 Web ホスティング" とも呼びます。高密度の Web ホスティングでは、(数千とは言わないまでも) 数百の Web サイトを同じサーバーで実行できます。ハードウェア コストを共有することで、各サイトを低コストで管理することが可能になります。この手法によって、Web サイトの管理担当者がエントリを行う障壁が大幅に軽減されました。
コールド起動: コールド起動とは、メモリに存在していないアプリケーションを起動するのにかかる時間です。システムを再起動した後にアプリケーションを起動するとこの状態になります。大規模アプリケーションの場合、必要なページ (コード、静的データ、レジストリなど) がメモリに存在せず、ページをメモリに格納するのに負荷の大きいディスク アクセスが必要になるため、コールド起動の実行には数秒かかる場合があります。
ウォーム起動: ウォーム起動は、既にメモリに存在するアプリケーションを起動するのにかかる時間です。たとえば、アプリケーションを数秒早く起動すると、ほとんどのページは既にメモリに読み込まれていて、OS がそれらのページを再利用する可能性が高くなります。これにより、負荷の大きいディスク アクセス時間を短縮できます。これが、アプリケーションが 2 回目の起動時にはるかに速く起動する理由です (.NET の一部が既にメモリに読み込まれているため、2 つ目の .NET アプリケーションが 1 つ目の .NET アプリケーションよりも速く起動する理由でもあります)。
ネイティブ イメージの生成 (NGen): NGen は、実行前に、中間言語 (IL) 実行可能ファイルのプリコンパイル プロセスを参照してマシン コードを生成します。これによるパフォーマンス上の主なメリットは 2 つあります。まず、コードを実行時にコンパイルする必要がなくなり、アプリケーションの起動時間が短縮されます。また、コード ページを複数のプロセスで共有できるので、より効率的にメモリを使用できます。ネイティブ イメージを作成して、作成したイメージをローカル コンピューター上のネイティブ イメージ キャッシュ (NIC) にインストールする、NGen.exe というツールもあります。このランタイムでは、利用可能なときにネイティブ イメージを読み込みます。
プロファイルによるガイド付き最適化: プロファイルによるガイド付き最適化は、ネイティブ アプリケーションとマネージ アプリケーションの起動時間と実行時間を向上できることが実証されています。Windows がネイティブ アセンブリ向けのプロファイルによるガイド付き最適化を実行するツールセットとインフラストラクチャを提供し、CLR がマネージ アセンブリ向けのプロファイルによるガイド付き最適化 (Managed Profile Guided Optimization (MPGO)) を実行するツールセットとインフラストラクチャを提供しています。Microsoft の多くのチームが、アプリケーションのパフォーマンスを向上するためにこれらのテクノロジを使用します。たとえば、CLR は、ネイティブ アセンブリ向けのプロファイルによるガイド付き最適化 (C++ のプロファイルによるガイド付き最適化) とマネージ アセンブリ向けのプロファイルによるガイド付き最適化 (MPGO)を実行します。
ガベージ コレクター: .NET ランタイムは自動メモリ管理をサポートしています。.NET ランタイムは、マネージ プログラムによって行われたすべてのメモリ割り当てを追跡し、ガベージ コレクターを定期的に呼び出します。ガベージ コレクターは、使用されていないメモリを検出し、新しい割り当ての際に再利用します。ガベージ コレクターが行う重要な最適化は、ヒープ全体を毎回検索せずに、ヒープを 3 つの世代 (世代 0、世代 1、および世代 2) に分割するという処理です。ガベージ コレクターに関する詳細については、2009 年 6 月号の「CLR 徹底解剖」コラム (msdn.microsoft.com/magazine/dd882521) を参照してください。
最適化: ガベージ コレクションのコンテキストで、ヒープがあまりにも断片化した状態になると、ガベージ コレクターは有効なオブジェクトを移動して互いに隣接させることでヒープを最適化します。ヒープを最適化する第一の目的は、より多くのオブジェクトを割り当てられるよう、使用可能なメモリ ブロックを増やすことです。
Ashwin Kamath は、.NET の CLR チームでプログラム マネージャーを務めており、.NET Framework 4.5 のパフォーマンスと信頼性に関する機能を担当しました。現在は、Windows Phone 開発プラットフォームの診断機能に携わっています。
この記事のレビューに協力してくれた技術スタッフの Surupa Biswas、Eric Dettinger、Wenlong Dong、Layla Driscoll、Dave Hiniker、Piyush Joshi、Ashok Kamath、Richard Lander、Vance Morrison、Subramanian Ramaswamy、Jose Reyes、Danny Shih、および Bill Wert に心より感謝いたします。