DLL, asignación de memoria, dirección base, uso de memoria y.NET?
memory base-address (3)
Creo que te confundes acerca de los ensamblados y dlls compartidos y el espacio de memoria del proceso.
Tanto .NET como DLL estándar de Win32 comparten el código entre los diferentes procesos que los utilizan. En el caso de .NET, esto solo es cierto para las DLL con la misma firma de versión, de modo que se pueden cargar dos versiones diferentes de la misma DLL en la memoria al mismo tiempo.
El caso es que parece que esperas que la memoria asignada por las llamadas a la biblioteca también se comparta, bueno , eso nunca sucede (casi) . Cuando una función dentro de su biblioteca asigna memoria, y supongo que eso sucede mucho con una DLL ORM, esa memoria se asigna dentro del espacio de memoria del proceso de llamada, cada proceso tiene instancias únicas de los datos.
Entonces, sí, de hecho, el código DLL se está cargando una vez y se comparte entre las personas que llaman, pero las instrucciones del código (y, por lo tanto, las asignaciones) se llevan a cabo por separado en el espacio del proceso de llamada.
Editar: Ok, veamos cómo funciona JIT con ensamblajes .NET.
Cuando hablamos de JITing el código, el proceso es relativamente simple. Internamente hay una estructura llamada Tabla de métodos virtuales que básicamente contiene la dirección virtual que se invocará durante una llamada. En .NET, JIT funciona básicamente editando esa tabla para que cada llamada redireccione al compilador JIT. De esta forma, cada vez que llamamos un método, el JIT interviene y compila el código en las instrucciones reales de la máquina (de ahí el Just In Time), una vez hecho, el JIT vuelve al VMT y sustituye la entrada anterior invocada él para señalar el código de bajo nivel generado. De esta forma, todas las llamadas subsiguientes serán redirigidas al código compilado (por lo que solo compilamos una vez). Por lo tanto, el JIT no se invoca cada vez y todas las llamadas posteriores se redireccionarán al mismo código compilado. Para las DLL, es probable que el proceso sea el mismo (aunque no puedo asegurarlo por completo).
Antes de comenzar con la pregunta real, permítanme decir que podría obtener algunos de los detalles aquí incorrectos. Si es así, por favor, arrésteme en esos casos o incluso en lugar de responder mi pregunta.
Mi pregunta es sobre DLL y .NET, básicamente. Tenemos una aplicación que está usando bastante memoria y estamos tratando de encontrar la forma de medir esto correctamente, especialmente cuando el problema ocurre principalmente en las computadoras de los clientes.
Una cosa que me impactó es que tenemos algunos ensambles .NET bastante grandes con código ORM generado.
Si estuviera usando una DLL no administrada (Win32) que tuviera una dirección base única, múltiples procesos simultáneos en la misma máquina cargarían la DLL una vez en la memoria física, y simplemente la asignaría a la memoria virtual para todas las aplicaciones. Por lo tanto, la memoria física se usaría una vez para esta DLL.
La pregunta es qué sucede con un ensamblado de .NET. Esta DLL contiene IL, y aunque esta parte de ella podría ser compartida entre las aplicaciones, ¿qué pasa con el código JITted que resulta de esta IL? ¿Es compartido? Si no, ¿cómo mido para descubrir que esto en realidad contribuye al problema o no? (Sí, lo sé, contribuirá, pero no pasaré mucho tiempo hasta que sea el mayor problema).
Además, sé que no hemos examinado la dirección base de todos los ensamblados .NET en nuestra solución, ¿es necesario que los ensamblados .NET lo hagan? Y si es así, ¿hay algunas pautas disponibles sobre cómo determinar estas direcciones?
Cualquier idea sobre esta área sería bienvenida, incluso si resulta que este no es un gran problema, o incluso no es un problema en absoluto.
Editar : Acabo de encontrar esta pregunta: ensamblados de .NET y reajuste de DLL que responde parcialmente a mi pregunta, pero aún me gustaría saber cómo el código JITted tiene en cuenta todo esto.
A partir de esa pregunta y su respuesta aceptada, parece que el código JITted se coloca en un montón, lo que significa que cada proceso cargará la imagen del conjunto binario compartido y producirá una copia JITted privada del código dentro de su propio espacio de memoria.
¿Hay alguna forma de medir esto? Si esto produce mucho código, tendremos que mirar más el código generado para descubrir si necesitamos ajustarlo.
Editar : se agregó una lista más corta de preguntas aquí:
- ¿Hay algún punto para asegurarse de que las direcciones base de los ensamblados de .NET sean únicas y no superpuestas para evitar el rebase de un dll que se utilizará principalmente para obtener el código IL para JITting?
- ¿Cómo puedo medir cuánta memoria se usa para el código JITted para averiguar si esto realmente es un problema o no?
La respuesta de @Brian Rasmussen aquí indica que JITting producirá copias por proceso del código JITted, como esperaba, pero que la rebase de los ensamblajes tendrá un efecto en cuanto a la reducción del uso de la memoria. Tendré que profundizar en las herramientas WinDbg + SoS que menciona, algo que he tenido en mi lista por un tiempo, pero ahora sospecho que no puedo posponerlo más :)
Editar : Algunos enlaces que he encontrado sobre el tema:
No estoy seguro de cuán precisa es la siguiente infomration con las versiones más nuevas de .NET y / o versiones de Windows. Es posible que MS haya solucionado algunos de los problemas de carga y uso compartido de DLL desde los primeros días de .NET. Pero creo que gran parte de lo siguiente todavía se aplica.
Con los ensamblados .NET, muchos de los beneficios del intercambio de páginas entre los procesos (y entre las sesiones del servidor Terminal) desaparece porque JIT necesita escribir el código nativo sobre la marcha: no hay ningún archivo de imagen para hacer una copia de seguridad del código nativo. De modo que cada proceso obtiene sus propias páginas de memoria separadas para el código jitted.
Esto es similar a los problemas causados por tener bases de datos DLL inadecuadas: si el sistema operativo necesita realizar reparaciones en una DLL Win32 estándar cuando se carga, las páginas de memoria para las partes reparadas no se pueden compartir.
Sin embargo, incluso si el código jitted no se puede compartir, existe una ventaja al volver a establecer las bases de datos DLL .NET porque la DLL aún se carga para los metadatos (e IL), y esas cosas se pueden compartir si no se requieren correcciones.
Es posible ayudar a compartir páginas de memoria con un ensamblado .NET usando ngen. pero eso trae consigo su propio conjunto de problemas.
Vea esta vieja publicación del blog de Jason Zander para más detalles:
http://blogs.msdn.com/jasonz/archive/2003/09/24/53574.aspx
Larry Osterman tiene un artículo decente del blog sobre el intercambio de páginas DLL y el efecto de las correcciones:
http://blogs.msdn.com/larryosterman/archive/2004/07/06/174516.aspx
Esto es para la pregunta 1)
El código jitted se coloca en un montón especial. Puede inspeccionar este montón utilizando el comando !eeheap
en WinDbg + SoS. Por lo tanto, cada proceso tendrá su propia copia del código jitted. El comando también le mostrará el tamaño total del montón de código.
Avíseme si desea detalles adicionales sobre cómo obtener esta información de WinDbg.
Esto es para la pregunta 2)
De acuerdo con el libro Expert .NET 2.0 IL Assembly, la parte .reloc
de un archivo pure-IL PE contiene solo una entrada de corrección para el código de inicio de CLR. Por lo tanto, la cantidad de correcciones necesarias para una DLL administrada durante el rebasamiento es bastante limitada.
Sin embargo, si enumera un proceso administrado determinado, notará que Microsoft ha reajustado la mayor parte (o tal vez la totalidad) de sus archivos DLL administrados. Si eso se debe ver como una razón para volver a basar o no depende de usted.