[pyar] [OT] sobre un "segundo" lenguaje...

Claudio Freire klaussfreire en gmail.com
Mie Mar 5 00:34:57 ART 2014


2014-03-04 23:51 GMT-03:00 Fernando Pelliccioni <fpelliccioni en gmail.com>:
> 2014-03-04 20:27 GMT-03:00 Claudio Freire <klaussfreire en gmail.com>:
>> Acá[0] tenés algunos ejemplos (para citar de nuevo the benchmark game).
>>
>
> Perdón, me perdí, ¿ejemplos con respecto a que cosa?
> Tengo pendiente chequear el código de algunos lenguajes y cuál es el
> mecanismo de medición.
> Después les digo.

Casos donde Java corre más rápido que C++

Ahí hay uno, aunque sólo uno. Es muy probable que la diferencia fuera
mucho mayor si Java pudiera evitar el overhead de memoria (que como
bien notaste, afecta el runtime).

Claro, hay otros casos donde funciona mucho más lento, y como bien
dijo alguien (vos?), no es muy predecible cuándo el JIT va a funcionar
bien y cuándo no. Bue, en realidad es bastante predecible a grandes
rasgos (no a lo preciso).

>> > Algunas optimizaciones son costosas y la diferencia entre un compilador
>> > estático y un JIT es que el usuario está ahí, esperando a que la
>> > optimización se lleve a cabo.
>>
>> Muchas de esas optimizaciones que mencionás involucran probar
>> propiedades del programa estáticamente, mirando sólo al código. Los
>> JIT no necesitan eso. Los JIT insertan "guardas" (testeos para
>> verificar que las asunciones se siguen cumpliendo) y listo, el resto
>> del código puede proceder como si se hubiera hecho todo ese análisis.
>>
>
> No creo haber nombrado ninguna técnica de optimización en particular de los
> JIT's.
> Pero ninguna es Zero-cost, ya que se hace en runtime.
> En ejecuciones largas, quizás pueda ser beneficioso, pero, uno también puede
> obtener los mismos beneficios usando PGO y evitar optimizar en runtime.

Sí, bue, estoy asumiendo. Igual, si leés la tesis que dio origen a
PyPy, vas a notar que las optimizaciones de los JIT son muy baratas en
comparación, en cuanto a análisis. Casi no hay. Es simplemente
instrumentación del bytecode para registrar cómo se ejecuta, y luego
optimizar el código más ejecutado asumiendo que va a seguir
cumpliéndose lo que vino cumpliéndose. Las guardas simplemente
verifican que así sea, y si deja de serlo, se pasa a ejecución de
bytecode nuevamente (hasta conseguir otro camino optimizado).

Es una idea elegante y muy efectiva.

>> Ese artículo tan popular sobre JavaScript y por qué Java nunca va a
>> funcionar bien para celulares está lleno de falacias. Es cierto que
>> las implementaciones todavía no pueden correr perfectamente en
>> celulares, pero es "todavía".
>>
>
> ¿Cuáles son las falacias? Las podemos analizar en detalle.
> Hoy en día es una realidad y las aplicaciones escritas en ciertos lenguajes,
> en celulares "se arrastran".

Me parece perfecto, sólo que no lo tengo a mano, tendría que re-leerlo.

OTOMH, decía "tiene y siempre va a tener overehead", lo cual es cierto
sólo superficialmente: el overhead no tiene por qué ser ni
considerable ni relevante. Es más, el overhead de JIT puede hasta
recuperarse con las optimizaciones de JIT.

>> Las técnicas clásicas de GC implementadas en las máquinas virtuales
>> modernas, por ejemplo, no requieren pausas. Son incrementales o
>> concurrentes, las primeras generan pausas pequeñas, las segundas no
>> generan pausa alguna. En multi-threading, usan guarderías, una
>> generación inicial local al thread, lo que hace que funcione mucho
>> (pero *mucho*) mejor que, por ejemplo, C++ con new o malloc.
>>
>
> En los GC incrementales las pausas no son pequeñas. (y las pausas pequeñas
> son pausas al fin y al cabo)

Cierto. Yo mismo lo estoy sufriendo en una app que necesita casi
real-time (tiene requerimientos de latencia muy estrictos), y CPython
mismo me genera pausas molestas.

> Y los GC concurrentes no están libres de pausas, las pausas se reducen con
> respecto al anterior, pero las hay en caso de necesitar Full-GC.

No hay razón teórica para requerir full-gc. Eso en general sucede
cuando el proceso de GC no da a basto para limpiar la memoria
suficientemente rápido, problema que tiene Java (y los GC del estilo
de Java), pues permiten que la aplicación cree objetos mucho más
rápido de lo que puede limpiarlos el GC. Pero hay mucha literatura al
respecto que sería aplicable, no me sorprendería que fuese
desapareciendo ese problema (no en Java, Java ya está casi frizado en
ese respecto, pero otras implementaciones más modernas como PyPy
pueden llegar a evitar ese problema).

> Igualmente, más allá de que el usuario note una pausa o no, ¿todo este
> análisis es sin costo?, y reitero, ¿qué pasa con los sistemas que requieren
> determinismo?

El costo no necesariamente es prohibitivo, y a eso voy. Depende de la
aplicación, es posible que Java o PyPy sea mucho mejor que C++. Por
ejemplo, cuando lo que importa es el thoughput, o cuando el
requerimiento de latencia sea permisivo.

Por ejemplo, en mi app, que necesita latencias aseguradas, aún así
PyPy me viene de maravilla. Las pausas son manejables, y estoy
completamente seguro (porque he programado mucho C++) que implementar
el mismo sistema en C++ me hubiera llevado mucho más tiempo, sin
conseguir mucha más performance.

> Me agrego otra tarea, hacer un programita que provoque una pausa en un GC
> Java Moderno y medirla.

Java es un paseo por el parque. Te recomendaría Python (es más difícil
hacerlo pausarse, pero posible).

> Me queda una pregunta, ¿cómo es eso de "generación inicial local al thread"?
> ¿qué es lo que es local al thread?

Se llaman guarderías[0]. En el link, hacen una diferencia entre las
guarderías normales (nursery) y las thread-local (TLA). Es el mismo
concepto, llevado a más detalles. La idea está igual.


>> Es común, cuando se quiere performance, en C++, empezar a eliminar el
>> uso de malloc y a codear nuestro propio "allocator". En sí, el manejo
>> de memoria sin GC no está libre de overhead, como cualquiera que haya
>> programado en C++ algún sistema de alta performance  debería saber.
>>
>
> En C y C++ muchas veces ni siquiera es necesario "allocar" memoria en el
> free-storage (heap).

En Java también[0].

> Pero en el caso de ser necesario, como dijiste, están los Allocators, y
> también hay implementaciones de malloc mucho más eficientes, como por
> ejemplo:
> jemalloc (usada por defecto en FreeBSD)[1] y TCMalloc de Google[2], entre
> otras.
> Uno puede reemplazar malloc por alguna de estas. ( y también new, que por
> defecto usa malloc )
> Tarea nro 3: medir la eficiencia en "allocación" de memoria dinámica de Java
> con s,u moderno GC contra C++ con mallocs modernos.

Va más allá de eso. Ningún allocator genérico funciona a veces, y
tenés que ir a pools o freelists. Para eso la stdlib tiene los
allocators configurables en cada colección. La performance de malloc
es tan poco aceptable, que incluso CPython no usa malloc puro. No hay
VM respetable que use malloc puro. Es simplemente muy lento para hacer
todo con malloc.

La falta de facilidades básicas para administrar la memoria de maneras
más inteligentes fue justamente central en c++0x. C++, aunque es
genial y lo banco, está muy por detrás de cualquier lenguaje dinámico
en cuanto a administración de memoria. Lo groso es que te da libertad
absoluta. Pero lo malo, es que tenés que trabajar con el equivalente
informático del martillo y cincel.

> El conteo de referencias tiene el problema de las referencias circulares,
> pero es solucionado, en C++, usando Weak pointers. Igualmente no es muy
> común casos como este.

Hay casos que las referencias débiles no te solucionan. Yo en Python
mismo las uso mucho, porque no me gusta abusar del colector de ciclos.
Pero no siempre son la solución. En esos casos, en C++ se complica
bastante.

> Igual, de nuevo, todo esto es heap allocation, y en C++ no estamos obligado
> a usarlo.

Bue, eso es exageración. Es como decir que no estamos obligados a usar
un monitor. Y, claro, no hace falta. Pero no tenerlo y que te pisotee
un camión Scania no es muy diferente.

> C++ realmente no está libre de problemas, para nada, pero uno que no tiene
> es la eficiencia.

Me gustaría que pruebes MySQL workbench.

Cuando lo levanté, hubiera jurado que estaba hecho en Java. Porque
tarda día y medio en levantar[*]. Pero no... es C++.

[0] http://docs.oracle.com/cd/E13150_01/jrockit_jvm/jrockit/geninfo/diagnos/garbage_collect.html
[*] Exagero, pero casi


More information about the pyar mailing list