miércoles, febrero 20, 2008

Firefox usará el asignador de memoria experimental de FreeBSD

Leo en (cómo no...) en programming.reddit que Firefox 3 beta 3 utiliza el asignador de memoria dinámica experimental de FreeBSD (jemalloc) en lugar del asignador de la plataforma de ejecución. Al parecer ha dado buenos resultados en cuanto velocidad y reducción de la fragmentación en los test de rendimiento para las tres plataformas mayoritarias (Windows, Mac OS X y Linux)

Para el que esté interesado en estos temas hay disponible un artículo muy interesante sobre jemalloc: A Scalable Concurrent malloc(3) Implementation for FreeBSD (pdf) en el que se explica su implementación, que coge ideas entre otros de hoard, y ciertamente tiene muy buena pinta. Hablé de hoard hace poco en Problemas de memoria (y algunas soluciones).

Firefox usará el asignador de memoria de FreeBSD en barrapunto

miércoles, febrero 13, 2008

Notas sobre errores fatales y portabilidad

Es escasa y dispersa la información que he encontrado acerca de la gestión de errores fatales en distintos sistemas operativos y lo que afecta al funcionamiento de los programas sobre ellos. Así que voy a juntar lo que he ido recopilando en una sola entrada. Puede ser un poco batiburrillo, pero igual le es de utilidad a alguien... (Para mi seguro, que así no pierdo los enlaces). Ordenadas de general a específico[1]:


  • En ANSI C existe signal que permite cambiar la gestión este tipo eventos, entre los que se encuentran SIGSEGV, violación de segmento y SIGFPE, error aritmético. No obstante, por desgracia, su comportamiento en programas con más de un thread no está especificado. Además por si fuese poco ANSI tampoco especifica el handler por defecto, es decir, qué es lo que pasa si salta una señal de ese tipo.

  • De hecho, de los que he probado, en windows con la librería de C del VisualStudio al menos, las señales funcionan por thread y en linux es global por proceso...

  • En POSIX si que se define el comportamiento por defecto de las señales. En concreto SIGSEGV y SIGFPE abortan el proceso completo . Se pueden ver los comportamientos por defecto de las distintas señales en POSIX en la documentación de <signal.h> del OpenGroup. Además, POSIX(R) recomienda pasarse a sigaction (que sólo es POSIX(R), no ANSI).

  • Windows NT no genera señal de violación de segmento, pero se le puede instalar handler

  • En win32 un error fatal se gestiona como una excepción estructurada (SHE) que en C++ se mapea a un excepción normal[2]. Además, una excepción estructurada no capturada, por defecto, supone la muerte del thread en el que se produce, pero no del proceso global .

  • Existe una forma de sobrescribir este comportamiento por defecto y es SetUnhandledExceptionFilter. Recomiendan en Nynaeve[3] no hacer cosas demasiado complicadas en el manejador, como parece lógico.

  • En un artículo de DeveloperWorks comentan un modo de convertir señales en excepciones pero que es un hack completamente erróneo: C++ exception-handling tricks for Linux porque da a entender que se puede replicar el comportamiento por defecto de Windows. Sin embargo, como se puede leer en "Program Error Signals" de la documentación de la glibc:
    La acción por defecto de todas estas señales es terminar el proceso. Si se bloquean o ignoran estas señales o se establece un handler que retorna normalmente, tu programa cascará espantosamente en el momento en que suceda la señal no ser que(*) haya sido generada a través de raise o kill en lugar de un error real.
    cosa que he podido comprobar con el código de DW tratando de ignorar una violación de segmento.

  • En resumen, lo más juicioso (y homogéneo entre plataformas) sería parar el proceso completo en caso de uno de estos errores y tratar de informar lo más completamente posible del error. En caso de win32 habría que forzar el fin del proceso, ya que no es la acción por defecto[4].


[1]El orden es de mayor a menor, de general a específico. Debería ser obvio para un programador: a igualdad de condiciones, se debería usar lo que funcione en el estándar más aceptado y amplio ¿por qué limitar el ámbito de aplicación? Primero, buscar en ANSI, y si la funcionalidad no está elegir conscientemente una solución más específica. En el fondo para mi no es más que una consecuencia de la ley de Postel sobre el el código: "Cuanto más estricto (más estándar) es lo que escribes, más aumentarás la interoperabilidad". Puede parecer una obviedad, pero en esas pequeñas microdecisiones de los programadores no es nada extraño usar extensiones del estándar sin necesidad. Y en caso de necesidad se puede echar mano de librerías libres, muchas de ellas con un alto nivel de portabilidad.

[2]Pongo en cursiva normal porque entra la duda si en esos casos, de tan bajo nivel, una excepción, que se confunde con excepciones de de otro tipo, es lo más clarificador para el usuario-programador, máxime cuando el C++ estándar no posee excepciones para gestionar estos casos...

[3]Un blog que he descubierto hace poco... interesante. Además sobre el mismo tema: Beware of custom unhandled exception filters in DLLs y You might be using unhandled exception filters without even knowing it

[4]Cabe preguntarse porque se permite la ejecución del proceso en un caso tan excepcional... habida cuenta además de que una excepción no capturada de de C++ provoca, por defecto el fin del proceso... En este caso está sin duda justificado el Fail Fast: lo que hay que hacer es arreglar el programa, no dejarlo seguir...

(*)Me despisté al traducir. Gracias a Javier Noval por leerse los enlaces y hacerme notar el error ;)

Notas sobre errores fatales y portabilidad en barrapunto

lunes, febrero 04, 2008

(Otros) problemas con la memoria y (otras) soluciones

Alguien en programming.reddit hizo una pregunta de esas difíciles: ¿Cómo manejar en un programa en C los casos en los que la memoria se agota? Y digo que es una de esas preguntas difíciles porque depende del tipo de software que estás haciendo, de su criticidad y del nivel requerido de robustez. Ya se sabe, el difícil compromiso de la gestión de errores críticos.

Y como se ve en las respuestas de los redditenses no sólo depende de nuestro sistema, sino de detalles de implementación de más abajo, como suele ser usual en los casos no del todo raros en los que las abstracciones que usamos comienzan a flaquear. En este caso se habla del comportamiento de Linux (el kernel) a la hora de tratar la falta de memoria, que por defecto usa una estrategia optimista que deja reservar (pero no usar, claro) más memoria de la disponible. No obstante este comportamiento se puede cambiar a uno un poco más controlable a través del parámetro overcommit_memory, que se introdujo no hace tanto... El comportamiento por defecto es llamar al OOM Killer que es un método tan drástico como poco predecible. Todo esto esta muy bien explicado en When Linux Runs Out of Memory que vi referenciado por aquí en tiempos de mayor intensidad técnica :)

Además en las respuestas se apunta a un libro online de los que vienen bien cuando las condiciones son más extremas de lo usual: Small Memory Software. Patterns for systems with limited memory. Apuntado en éste mi del.icio.us particular.

(El título es "(Otros) problemas con la memoria y (otras) soluciones " porque no hace mucho escribí Problemas de memoria (y algunas soluciones), sobre el cuello de botella que supone la gestión de memoria sobre todo en sistemas multicore)

"(Otros) problemas con la memoria y (otras) soluciones" en barrapunto