2012-04-13 14 views
16

Tengo una biblioteca que uso que usa WCF para llamar a un servicio http para obtener configuraciones. Normalmente, la primera llamada tarda unos 100 milisegundos y las llamadas siguientes tardan solo unos pocos milisegundos. Pero descubrí que cuando creo un nuevo AppDomain, la primera llamada WCF de ese AppDomain toma más de 2,5 segundos.La primera conexión WCF realizada en AppDomain nuevo es muy lenta

¿Alguien tiene una explicación o una corrección de por qué la primera creación de un canal WCF en un nuevo dominio de aplicación llevaría tanto tiempo?

Estos son los resultados de referencia (Cuando se ejecuta sin depurador asociado en la liberación en 64 bits), cuenta de cómo en el segundo conjunto de números de las primeras conexiones se hace cargo de 25x ya

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 

Esto no es un ejemplo completo pero muestra más de cómo produje estos números:

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); 
    } 
} 

La llamada a AppServSettings.ServiceBaseUrlBatch es la creación de un canal a un servicio y llamar a un solo método. He usado wireshark para ver la llamada y solo lleva milisegundos obtener una respuesta del servicio. Se crea el canal con el siguiente código:

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(); 
} 

partir de perfiles de la aplicación se muestra que en el primer caso la construcción de la fábrica de canal y la creación del canal y llamando al método tarda menos de 100 milisegundos

En el El nuevo AppDomain que construye la fábrica de canales tomó 763 milisegundos, 521 milisegundos para crear el canal, 1,098 milisegundos para llamar al método en la interfaz.

TestSettingsRepoInAppDomain.DomainRunner.Run() 2,660.00 TestSettingsRepoInAppDomain.AppServSettings.get_ServiceBaseUrlBatch() 2,543.47 Tps.Core.Settings.Retriever.GetSetting (cadena, !! 0, 0 !!, !! 0) 2,542.66 TPS. Core.Settings.Retriever.TryGetSetting (cadena, !! & 0) 2,522.03 Tps.Core.Settings.ServiceModel.WcfHelper.GetClient() 1,371.21 Tps.Core.Settings.ServiceModel.IClientChannelExtensions.CallWithRetry (System.ServiceModel.IClientChannel clase) 1,098.83

EDITAR

Después de usar perfmon con el objeto .NET CLR Cargando, puedo ver que cuando carga el segundo dominio de aplicación, está cargando más clases en la memoria que inicialmente. La primera línea plana es una pausa que puse después del primer appdomain, allí tiene 218 clases cargadas. El segundo dominio de aplicación causa que se carguen 1.944 clases en total.

Supongo que es la carga de todas estas clases que está tomando todo el tiempo, por lo que ahora la pregunta es, ¿qué clases está cargando y por qué?

enter image description here

ACTUALIZACIÓN

La respuesta resulta ser debido al hecho de que sólo un dominio de aplicación es capaz de tomar ventaja de las DLL del sistema de imágenes nativas. Entonces, la lentitud en el segundo dominio de aplicación fue tener que volver a configurar todos los System. * Dlls utilizados por wcf. El primer dominio de aplicación podría usar las versiones nativas de esos dlls, por lo que no tenía el mismo costo de inicio.

Después de investigar la LoaderOptimizationAttribute que Petar sugirió, que, efectivamente, parecía solucionar el problema, ya sea utilizando MultiDomain or MultiDomainHost resultados en el segundo dominio de aplicación para tomar la misma cantidad de tiempo que la primera vez que accede cosas una WCF

Aquí puede ver la opción por defecto, tenga en cuenta cómo en el segundo dominio de aplicación ninguno de los conjuntos dicen nativo, lo que significa que todos tenían que ser rejitted, que es lo que estaba teniendo todo el tiempo

enter image description here

Aquí es después de añadir el LoaderOptimiza ción (LoaderOptimization.MultiDomain) a Main. Se puede ver que todo lo que se carga en el dominio de aplicación compartida

enter image description here

Aquí es después LoaderOptimization usuario (LoaderOptimization.MultiDomainHost) al principal. Se puede ver que todas las DLL del sistema son compartidos, pero mis propios archivos DLL y cualquier no en el GAC se cargan por separado en cada dominio de aplicación

enter image description here

Así que por el servicio que motivó esta pregunta utilizando MultiDomainHost es la respuesta, porque tiene un tiempo de inicio rápido y puedo descargar AppDomains para eliminar los ensamblados construidos dinámicamente que usa el servicio

Respuesta

9

Puede decorar su Main con el atributo LoaderOptimization para indicar al cargador CLR cómo cargar las clases.

[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. 
+1

Gracias, agregando que de hecho reduce el tiempo en el nuevo Dominio de aplicación de 35 ms para el primero y 2 ms para el segundo, pero, ¿evitaría esto que se descarguen los ensamblajes cargados en el segundo dominio de la aplicación? Uno de los motivos por los que la aplicación utiliza AppDomains es porque es un servicio de ejecución prolongada que necesita cargar código personalizado para realizar una acción, por lo que aún tendría que poder descargar algunos ensamblajes para que el tamaño del servicio no crezca sin límites. . – BrandonAGr

+1

Esta configuración no debería evitar la descarga de ensamblados desde el segundo dominio de la aplicación. –

+0

¿Qué aplicación está produciendo las capturas de pantalla? Sysinternals cosas? –

1

¿Tiene un proxy HTTP definido en IE? (tal vez un script de configuración automática). Esto puede ser una causa.

De lo contrario, supongo que es el tiempo que lleva cargar todas las dlls. Intente separar la creación del proxy de la llamada actull al servicio, para ver qué se está tomando el tiempo.

+0

He comprobado previamente la configuración del proxy y no creo que sea eso. También dudo que sea la carga de dlls porque, de ser así, ¿por qué el primer intento de wcf no vería las mismas demoras que el nuevo AppDomain? Una cosa que noté fue que mientras creaba el nuevo AppDomain, los usos de la CPU estaban al máximo para ese núcleo durante los 2.5 segundos. – BrandonAGr

1

Encontré following article que habla de cómo solo el primer dominio de aplicación puede usar dlls de imágenes nativas, por lo que un dominio de aplicaciones hijo siempre se verá obligado a JIT un montón de cosas que el AppDomain inicial no tiene que hacer. Esto podría llevar al impacto de la performance que estoy viendo, pero ¿sería posible de alguna manera no obtener esta penalización de rendimiento?

Si hay una imagen nativa para el ensamblado, solo el primer dominio de aplicación puede usar la imagen original. El resto de AppDomains tendrá que JIT: compilar el código que puede generar un costo de CPU significativo.

Cuestiones relacionadas