18

WCF を使用して http サービスを呼び出して設定を取得するライブラリを使用しています。通常、最初の呼び出しには約 100 ミリ秒かかり、その後の呼び出しには数ミリ秒しかかかりません。しかし、新しい AppDomain を作成すると、その AppDomain からの最初の WCF 呼び出しに 2.5 秒以上かかることがわかりました。

新しい AppDomain での WCF チャネルの最初の作成に時間がかかる理由について、誰かが説明や修正をしていますか?

これらはベンチマークの結果です (64 ビットのリリースでデバッガーを接続せずに実行した場合)。2 番目の数値セットでは、最初の接続が 25 倍以上長くかかることに注意してください。

Running in initial AppDomain
First Connection: 92.5018 ms
Second Connection: 2.6393 ms

Running in new AppDomain
First Connection: 2457.8653 ms
Second Connection: 4.2627 ms

これは完全な例ではありませんが、これらの数値を生成する方法のほとんどを示しています。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Running in initial AppDomain");
        new DomainRunner().Run();

        Console.WriteLine();
        Console.WriteLine("Running in new thread and AppDomain");
        DomainRunner.RunInNewAppDomain("test");

        Console.ReadLine();
    }
}

class DomainRunner : MarshalByRefObject
{
    public static void RunInNewAppDomain(string runnerName)
    {
        var newAppDomain = AppDomain.CreateDomain(runnerName);
        var runnerProxy = (DomainRunner)newAppDomain.CreateInstanceAndUnwrap(typeof(DomainRunner).Assembly.FullName, typeof(DomainRunner).FullName);

        runnerProxy.Run();
    }

    public void Run()
    {
        AppServSettings.InitSettingLevel(SettingLevel.Production);
        var test = string.Empty;

        var sw = Stopwatch.StartNew();
        test += AppServSettings.ServiceBaseUrlBatch;
        Console.WriteLine("First Connection: {0}", sw.Elapsed.TotalMilliseconds);

        sw = Stopwatch.StartNew();
        test += AppServSettings.ServiceBaseUrlBatch;
        Console.WriteLine("Second Connection: {0}", sw.Elapsed.TotalMilliseconds);
    }
}

AppServSettings.ServiceBaseUrlBatch への呼び出しは、サービスへのチャネルを作成し、単一のメソッドを呼び出しています。私はwiresharkを使用して通話を監視しましたが、サービスからの応答を得るのに数ミリ秒しかかかりません. 次のコードでチャネルを作成します。

public static ISettingsChannel GetClient()
{
    EndpointAddress address = new EndpointAddress(SETTINGS_SERVICE_URL);

    BasicHttpBinding binding = new BasicHttpBinding
    {
        MaxReceivedMessageSize = 1024,
        OpenTimeout = TimeSpan.FromSeconds(2),
        SendTimeout = TimeSpan.FromSeconds(5),
        ReceiveTimeout = TimeSpan.FromSeconds(5),
        ReaderQuotas = { MaxStringContentLength = 1024},
        UseDefaultWebProxy = false,
    };

    cf = new ChannelFactory<ISettingsChannel>(binding, address);

    return cf.CreateChannel();
}

アプリのプロファイリングから、最初のケースでは、チャネル ファクトリの構築、チャネルの作成、およびメソッドの呼び出しに 100 ミリ秒未満しかかからないことがわかります。

新しい AppDomain では、チャネル ファクトリを構築するのに 763 ミリ秒、チャネルの作成に 521 ミリ秒、インターフェイスでメソッドを呼び出すのに 1,098 ミリ秒かかりました。

TestSettingsRepoInAppDomain.DomainRunner.Run() 2,660.00 TestSettingsRepoInAppDomain.AppServSettings.get_ServiceBaseUrlBatch() 2,543.47 Tps.Core.Settings.Retriever.GetSetting(string,!!0,!!0,!!0) 2,542.66 Tps.Core.Settings.Retriever.TryGetSetting (文字列、!!0&) 2,522.03 Tps.Core.Settings.ServiceModel.WcfHelper.GetClient() 1,371.21 Tps.Core.Settings.ServiceModel.IClientChannelExtensions.CallWithRetry(クラス System.ServiceModel.IClientChannel) 1,098.83

編集

.NET CLR Loading オブジェクトで perfmon を使用すると、2 番目の AppDomain を読み込むときに、最初よりも多くのクラスがメモリに読み込まれていることがわかります。最初の平らな線は、最初の appdomain の後に挿入した一時停止です。そこには 218 個のクラスがロードされています。2 番目の AppDomain により、合計 1,944 個のクラスが読み込まれます。

これらすべてのクラスのロードに常に時間がかかっていると思いますが、問題は、ロードしているクラスとその理由です。

ここに画像の説明を入力

アップデート

その答えは、1 つの AppDomain だけがネイティブ イメージ システム dll を利用できるという事実によるものであることが判明しました。したがって、2 番目の appdomain の遅さは、wcf が使用するすべての System.* dll を rejit しなければならなかったことです。最初の appdomain は、これらの dll の事前に生成されたネイティブ バージョンを使用できたので、起動コストは同じではありませんでした。

Petar が提案したLoaderOptimizationAttributeを調査した後、MultiDomain または MultiDomainHostのいずれかを使用すると、2 番目の AppDomain が wcf 経由で最初にアクセスしたときと同じ時間がかかり、実際に問題が解決したように見えました。

ここでは、デフォルトのオプションを確認できます。2 番目の AppDomain でどのアセンブリも Native と表示されていないことに注意してください。つまり、すべてのアセンブリを rejit する必要があり、これに常に時間がかかっていました。

ここに画像の説明を入力

これは、LoaderOptimization(LoaderOptimization.MultiDomain) を Main に追加した後です。すべてが共有 AppDomain に読み込まれていることがわかります

ここに画像の説明を入力

これは、ユーザー LoaderOptimization(LoaderOptimization.MultiDomainHost) をメインにした後です。すべてのシステム dll が共有されていることがわかりますが、自分の dll と GAC にないものは、各 AppDomain に個別に読み込まれます。

ここに画像の説明を入力

したがって、MultiDomainHost を使用してこの質問を促したサービスが答えです。これは、起動時間が速く、AppDomains をアンロードして、サービスが使用する動的に構築されたアセンブリを削除できるためです。

4

3 に答える 3

9

Main をLoaderOptimization属性で修飾して、CLR ローダーにクラスのロード方法を伝えることができます。

[LoaderOptimization(LoaderOptimization.MultiDomain)]
MultiDomain - Indicates that the application will probably have many domains that use the same code, and the loader must share maximal internal resources across application domains.
于 2012-04-19T07:18:16.980 に答える
1

IE で HTTP プロキシを定義していますか? (おそらく自動構成スクリプト)。これが原因になることがあります。

それ以外の場合は、すべての dll をロードするのにかかる時間だと思います。サービスへの実際の呼び出しからプロキシの作成を切り離して、何が時間がかかっているかを確認してください。

于 2012-04-14T00:08:14.967 に答える
1

最初の AppDomain だけがネイティブ イメージ dll を使用できる方法について説明している次の記事を見つけたので、子 appdomain は常に最初の AppDomain が必要としない多くのものを JIT する必要があります。これは私が見ているパフォーマンスへの影響につながる可能性がありますが、どうにかしてこのパフォーマンスの低下を回避することは可能でしょうか?

アセンブリのネイティブ イメージがある場合、最初の AppDomain だけがネイティブ イメージを使用できます。他のすべての AppDomains は、コードを JIT コンパイルする必要があり、CPU コストが大幅に増加する可能性があります。

于 2012-04-18T21:11:51.270 に答える