¿Por qué sería posible que Java fuera más rápido que C ++?

80

A veces, Java supera a C ++ en los puntos de referencia. Por supuesto, a veces C ++ supera.

Ver los siguientes enlaces:

Pero, ¿cómo es esto posible? Me sorprende que el bytecode interpretado pueda ser más rápido que un lenguaje compilado.

¿Alguien puede explicar? ¡Gracias!

Deets McGeets
fuente
2
Puede echar un vistazo a shootout.alioth.debian.org/u32/… para ver el tipo de problemas que se ejecutan más rápido en java / c ++ ... Vea el patrón de problemas, y no estos problemas específicos ...
c0da
2
Ver ¿Por qué Java tenía la reputación de ser lento? para muchos detalles sobre este tema.
Péter Török
11
Es contra la ley (Sección 10.101.04.2c) para producir una máquina virtual Java que realiza más rápido que un binario ejecutable producido con C ++.
Mateen Ulhaq
1
@muntoo ¿Qué demonios quieres decir? Sección 10.101.04.2c de qué?
Highland Mark
3
@HighlandMark No eres miembro del círculo. No debes saber qué hay dentro del círculo. El círculo es absoluto: las leyes del círculo reemplazan a las de la naturaleza. No puedes desafiar ni cuestionar el círculo. Yo soy el círculo y el círculo soy yo.
Mateen Ulhaq

Respuestas:

108

Primero, la mayoría de las JVM incluyen un compilador, por lo que "bytecode interpretado" es bastante raro (al menos en el código de referencia, no es tan raro en la vida real, donde el código suele ser más que unos pocos bucles triviales que se repiten con mucha frecuencia )

En segundo lugar, un buen número de los puntos de referencia involucrados parecen estar bastante sesgados (ya sea por intención o incompetencia, realmente no puedo decir). Solo por ejemplo, hace años, miré algunos de los códigos fuente vinculados desde uno de los enlaces que publicaste. Tenía un código como este:

  init0 = (int*)calloc(max_x,sizeof(int));
  init1 = (int*)calloc(max_x,sizeof(int));
  init2 = (int*)calloc(max_x,sizeof(int));
  for (x=0; x<max_x; x++) {
    init2[x] = 0;
    init1[x] = 0;
    init0[x] = 0;
  }

Como callocproporciona memoria que ya está puesta a cero, usar el forbucle para ponerlo a cero nuevamente es obviamente inútil. Esto fue seguido (si la memoria sirve) llenando la memoria con otros datos de todos modos (y sin depender de que se ponga a cero), por lo que toda la puesta a cero fue completamente innecesaria de todos modos. Reemplazar el código anterior con un simple malloc(como cualquier persona sensata hubiera usado para comenzar) mejoró la velocidad de la versión de C ++ lo suficiente como para vencer a la versión de Java (por un margen bastante amplio, si la memoria sirve).

Considere (para otro ejemplo) el methcallpunto de referencia utilizado en la entrada del blog en su último enlace. A pesar del nombre (y de cómo podrían verse las cosas), la versión C ++ de esto no mide mucho sobre la sobrecarga de llamadas al método. La parte del código que resulta ser crítica está en la clase Toggle:

class Toggle {
public:
    Toggle(bool start_state) : state(start_state) { }
    virtual ~Toggle() {  }
    bool value() {
        return(state);
    }
    virtual Toggle& activate() {
        state = !state;
        return(*this);
    }
    bool state;
};

La parte crítica resulta ser la state = !state;. Considere lo que sucede cuando cambiamos el código para codificar el estado como un en intlugar de un bool:

class Toggle {
    enum names{ bfalse = -1, btrue = 1};
    const static names values[2];
    int state;

public:
    Toggle(bool start_state) : state(values[start_state]) 
    { }
    virtual ~Toggle() {  }
    bool value() {  return state==btrue;    }

    virtual Toggle& activate() {
        state = -state;
        return(*this);
    }
};

Este cambio menor mejora la velocidad general en aproximadamente un margen de 5: 1 . Aunque el punto de referencia estaba destinado a medir el tiempo de llamada al método, en realidad, la mayor parte de lo que medía era el tiempo de conversión entre inty bool. Ciertamente estoy de acuerdo en que la ineficiencia mostrada por el original es desafortunada, pero dado que rara vez parece surgir en el código real, y la facilidad con la que se puede solucionar cuando / si surge, me cuesta pensar de ello como mucho significado.

En caso de que alguien decida volver a ejecutar los puntos de referencia involucrados, también debo agregar que hay una modificación casi igualmente trivial a la versión de Java que produce (o al menos una vez producido; no he vuelto a ejecutar las pruebas con un JVM reciente para confirmar que todavía lo hacen) una mejora bastante sustancial en la versión de Java también. La versión de Java tiene un NthToggle :: enable () que se ve así:

public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
    this.state = !this.state;
    this.counter = 0;
}
return(this);
}

Cambiar esto para llamar a la función base en lugar de manipular this.statedirectamente proporciona una mejora de velocidad bastante sustancial (aunque no lo suficiente como para mantenerse al día con la versión modificada de C ++).

Entonces, lo que terminamos es una suposición falsa sobre los códigos de bytes interpretados frente a algunos de los peores puntos de referencia que he visto. Tampoco está dando un resultado significativo.

Mi propia experiencia es que con programadores igualmente experimentados que prestan igual atención a la optimización, C ++ vencerá a Java más a menudo que no, pero (al menos entre estos dos), el lenguaje rara vez hará tanta diferencia como los programadores y el diseño. Los puntos de referencia que se citan nos dicen más sobre la (in) competencia / (des) honestidad de sus autores que sobre los idiomas que pretenden comparar.

[Editar: como está implícito en un lugar anterior, pero nunca lo dije tan directamente como probablemente debería haberlo hecho, los resultados que estoy citando son los que obtuve cuando probé esto ~ hace 5 años, usando implementaciones de C ++ y Java que eran actuales en ese momento . No he vuelto a ejecutar las pruebas con las implementaciones actuales. Una mirada, sin embargo, indica que el código no se ha solucionado, por lo que todo lo que habría cambiado sería la capacidad del compilador para ocultar los problemas en el código.]

Sin embargo, si ignoramos los ejemplos de Java, en realidad es posible que el código interpretado se ejecute más rápido que el código compilado (aunque difícil y algo inusual).

La forma habitual en que esto sucede es que el código que se interpreta es mucho más compacto que el código de la máquina, o se ejecuta en una CPU que tiene un caché de datos más grande que el caché de código.

En tal caso, un pequeño intérprete (p. Ej., El intérprete interno de una implementación de Forth) puede encajar completamente en el caché de código, y el programa que está interpretando se ajusta completamente en el caché de datos. El caché suele ser más rápido que la memoria principal en un factor de al menos 10, y a menudo mucho más (un factor de 100 ya no es particularmente raro).

Entonces, si el caché es más rápido que la memoria principal por un factor de N, y se requieren menos de N instrucciones de código de máquina para implementar cada código de bytes, el código de bytes debería ganar (estoy simplificando, pero creo que la idea general aún debería ser aparente).

Jerry Coffin
fuente
26
+1, ack completo. Especialmente "el lenguaje rara vez hará tanta diferencia como los programadores y el diseño": a menudo tropezará con problemas en los que puede optimizar el algoritmo, por ejemplo, mejorar big-O, lo que le dará mucho más impulso que el mejor compilador.
schnaader
1
"En caso de que alguien decida volver a ejecutar los puntos de referencia involucrados ..." ¡NO! En 2005, esas viejas tareas fueron descartadas y reemplazadas por las tareas que ahora se muestran en el juego de pruebas. Si alguien quiere volver a ejecutar algunos programas, vuelva a ejecutar los programas actuales para las tareas actuales que se muestran en la página de inicio del juego de pruebas shootout.alioth.debian.org
igouy
@igouy: Algunas personas tal vez quieran simplemente confirmar / negar los resultados de los puntos de referencia que ejecutaron, con el mínimo de correcciones necesarias para al menos darles una relación mínima con la realidad. Al mismo tiempo, básicamente tienes razón: los puntos de referencia en cuestión son tan malos que solo arreglar los errores más obvios no va a ayudar mucho.
Jerry Coffin
Y es por eso que, en 2005, fueron descartados y reemplazados por las tareas que ahora se muestran en el juego de puntos de referencia. Las personas que no conocen nada mejor vuelven a ejecutar esos viejos programas.
igouy
13
+1 No me gusta que las personas codifiquen C ++ en estilo C o Java y luego declaren que Java es superior. Descargo de responsabilidad: no llamo a ningún idioma superior, pero escribir código C ++ cutre en un estilo que podría ser perfectamente adecuado para otro idioma no hace que ambos idiomas sean comparables.
Christian Rau
112

C / C ++ hecho a mano por un experto con tiempo ilimitado será al menos tan rápido o más rápido que Java. En última instancia, Java está escrito en C / C ++, por lo que, por supuesto, puede hacer todo lo que Java hace si está dispuesto a realizar un esfuerzo de ingeniería suficiente.

Sin embargo, en la práctica, Java a menudo se ejecuta muy rápido por las siguientes razones:

  • Compilación JIT : aunque las clases Java se almacenan como bytecode, el compilador JIT compila (generalmente) el código nativo cuando se inicia el programa. Una vez compilado, es código nativo puro, por lo que, en teoría, se puede esperar que funcione tan bien como C / C ++ compilado una vez que el programa se haya ejecutado durante el tiempo suficiente (es decir, después de que se haya realizado toda la compilación JIT)
  • La recolección de basura en Java es extremadamente rápida y eficiente: el Hotspot GC es probablemente la mejor implementación integral de GC del mundo. Es el resultado de muchos años-hombre de esfuerzo experto por parte de Sun y otras compañías. Prácticamente cualquier sistema de administración de memoria complejo que ejecute usted mismo en C / C ++ será peor. Por supuesto, puede escribir esquemas de administración de memoria básica bastante rápidos / livianos en C / C ++, pero no serán tan versátiles como un sistema GC completo. Dado que la mayoría de los sistemas modernos necesitan una gestión de memoria compleja, Java tiene una gran ventaja para situaciones del mundo real.
  • Mejor orientación de la plataforma : al retrasar la compilación para el inicio de la aplicación (compilación JIT, etc.), el compilador de Java puede aprovechar el hecho de que conoce el procesador exacto en el que se está ejecutando. Esto puede permitir algunas optimizaciones muy beneficiosas que no podría hacer en el código C / C ++ precompilado que necesita apuntar a un conjunto de instrucciones de procesador de "mínimo común denominador".
  • Estadísticas de tiempo de ejecución : debido a que la compilación JIT se realiza en tiempo de ejecución, puede recopilar estadísticas mientras se ejecuta el programa que permiten mejores optimizaciones (por ejemplo, conocer la probabilidad de que se tome una rama en particular). Esto puede permitir que los compiladores JIT de Java produzcan un código mejor que los compiladores C / C ++ (que tienen que "adivinar" la rama más probable de antemano, una suposición que a menudo puede estar equivocada).
  • Muy buenas bibliotecas : el tiempo de ejecución de Java contiene una gran cantidad de bibliotecas muy bien escritas con un buen rendimiento (especialmente para aplicaciones del lado del servidor). A menudo, estos son mejores de lo que podría escribir usted mismo u obtener fácilmente para C / C ++.

Al mismo tiempo, C / C ++ también tiene algunas ventajas:

  • Más tiempo para realizar optimizaciones avanzadas : la compilación C / C ++ se realiza una vez y, por lo tanto, puede dedicar un tiempo considerable a realizar optimizaciones avanzadas si la configura para hacerlo. No hay una razón teórica por la que Java no pueda hacer lo mismo, pero en la práctica desea que Java compile código JIT con relativa rapidez, por lo que el compilador JIT tiende a centrarse en optimizaciones "más simples".
  • Instrucciones que no se pueden expresar en bytecode : si bien el bytecode de Java es de uso general, todavía hay algunas cosas que puede hacer en un nivel bajo que no puede hacer en bytecode (¡la aritmética de puntero no verificada es un buen ejemplo!). Al (ab) usar este tipo de trucos, puede obtener algunas ventajas de rendimiento
  • Menos restricciones de "seguridad" : Java realiza un trabajo adicional para garantizar que los programas sean seguros y confiables. Los ejemplos son las comprobaciones de límites en las matrices, ciertas garantías de concurrencia, comprobaciones de puntero nulo, seguridad de tipos en las conversiones, etc.

En general:

  • Java y C / C ++ pueden alcanzar velocidades similares
  • C / C ++ probablemente tenga una ligera ventaja en circunstancias extremas (no es sorprendente que los desarrolladores de juegos AAA aún lo prefieran, por ejemplo)
  • En la práctica, dependerá de cómo los diferentes factores enumerados anteriormente se equilibren para su aplicación particular.
mikera
fuente
99
Anuncio "más tiempo para optimizaciones en C ++": ese es uno de los ajustes que hace la VM de Oracle cuando elige la VM del servidor: acepta un costo de inicio más alto para permitir un mayor rendimiento a largo plazo. Sin embargo, la VM del cliente está ajustada para un tiempo de inicio óptimo. Entonces esa distinción incluso existe dentro de Java.
Joachim Sauer
8
-1: Un compilador de C ++ puede tomar mucho más tiempo (horas, literalmente, para una biblioteca grande) para crear un binario muy optimizado. El compilador JIT de Java no puede tomar tanto tiempo, incluso la versión "servidor". Dudo seriamente que el compilador Java JIT pueda realizar la optimización de todo el programa de la manera en que lo hace el compilador MS C ++.
quant_dev
20
@quant_dev: claro, pero ¿no es eso exactamente lo que dije en mi respuesta como una ventaja de C ++ (más tiempo para hacer una optimización avanzada)? Entonces, ¿por qué el -1?
mikera
13
La recolección de basura no es una ventaja de velocidad para Java. Es solo una ventaja de velocidad si eres un programador de C ++ que no sabe lo que estás haciendo. Si todo lo que está comprobando es qué tan rápido puede asignar, entonces sí, el recolector de basura ganará. Sin embargo, el rendimiento general del programa aún se puede mejorar administrando manualmente la memoria.
Billy ONeal
44
... Pero con C ++, en teoría siempre se podría poner una "capa similar a JIT" que optimiza las ramificaciones de manera similar en tiempo de ejecución, mientras mantiene la velocidad sin procesar de un programa de C ++. (Teóricamente. :()
Mateen Ulhaq
19

El tiempo de ejecución de Java no interpreta bytecode. Más bien, usa lo que se llama Just In Time Compilation . Básicamente, a medida que se ejecuta el programa, toma bytecode y lo convierte en código nativo optimizado para la CPU en particular.

Gran maestro B
fuente
En la práctica, sí. En principio, depende: las primeras máquinas virtuales Java usaban intérpretes de bytecode, y probablemente aún pueda encontrar máquinas virtuales de interpretación de bytecode si busca lo suficiente.
Steve314
10
@ Steve314: pero las máquinas virtuales puramente interpretativas no serán las que superen a C ++, por lo que no son realmente relevantes para esta pregunta.
Joachim Sauer
El compilador JIT también puede optimizar dinámicamente para el uso específico del código, lo que no es posible con el código que está compilado estáticamente.
Starblue
2
@starblue, bueno, es algo posible con una compilación estática; vea la optimización guiada por perfil.
SK-logic
19

En igualdad de condiciones, se podría decir: no, Java nunca debería ser más rápido . Siempre puede implementar Java en C ++ desde cero y, por lo tanto, obtener al menos el mismo rendimiento. En la práctica, sin embargo:

  • JIT compila el código en la máquina del usuario final, lo que le permite optimizar la CPU exacta que está ejecutando. Si bien hay una sobrecarga aquí para la compilación, bien puede valer la pena para las aplicaciones intensivas. A menudo, los programas de la vida real no se compilan para la CPU que está utilizando.
  • El compilador de Java puede ser mejor para optimizar automáticamente las cosas que un compilador de C ++. O puede que no, pero en el mundo real, las cosas no siempre son perfectas.
  • El comportamiento del rendimiento puede variar debido a otros factores, como la recolección de basura. En C ++, normalmente se llama al destructor inmediatamente cuando se hace con un objeto. En Java, simplemente libera la referencia, retrasando la destrucción real. Este es otro ejemplo de una diferencia que no está ni aquí ni allá, en términos de rendimiento. Por supuesto, puede argumentar que podría implementar GC en C ++ y terminar con esto, pero la realidad es que pocas personas quieren / quieren / pueden.

Como comentario aparte, esto me recuerda el debate sobre C en los años 80/90. Todos se preguntaban "¿puede C ser tan rápido como el ensamblaje?". Básicamente, la respuesta fue: no en papel, pero en realidad el compilador de C creó un código más eficiente que el 90% de los programadores de ensamblaje (bueno, una vez que maduró un poco).

Daniel B
fuente
2
Con respecto a GC, no es solo que GC pueda retrasar la destrucción de objetos (lo que no debería importar a largo plazo); El hecho es que con los GC modernos, la asignación / desasignación de objetos de corta duración es extremadamente barata en Java en comparación con C ++.
Péter Török
@ PéterTörök sí, tienes razón, buen punto.
Daniel B
99
@ PéterTörök Pero en C ++, los objetos de corta duración a menudo se colocan en la pila, lo que a su vez es mucho más rápido que cualquier montón de GC-ed que Java pueda usar.
quant_dev
@quant_dev, olvidó otro efecto GC significativo: la compactación. Así que no estaría tan seguro de qué manera es más rápido.
SK-logic
3
@DonalFellows ¿Qué te hace pensar que tengo que preocuparme por la administración de memoria en C ++? La mayoría de las veces no lo hago. Hay patrones simples que debe aplicar, que son diferentes de Java, pero eso es todo.
quant_dev
10

Pero la asignación es solo la mitad de la administración de la memoria; la desasignación es la otra mitad. Resulta que para la mayoría de los objetos, el costo directo de recolección de basura es - cero. Esto se debe a que un recolector de copias no necesita visitar o copiar objetos muertos, solo los vivos. Por lo tanto, los objetos que se convierten en basura poco después de la asignación no contribuyen con la carga de trabajo al ciclo de recolección.

...

Las JVM son sorprendentemente buenas para descubrir cosas que solíamos suponer que solo el desarrollador podía saber. Al permitir que la JVM elija entre la asignación de la pila y la asignación del montón en una base de caso por caso, podemos obtener los beneficios de rendimiento de la asignación de la pila sin hacer que el programador agonice sobre si asignar en la pila o en el montón.

http://www.ibm.com/developerworks/java/library/j-jtp09275/index.html

Landei
fuente
Esto es solo una pequeña parte de toda la imagen, pero no obstante es bastante relevante.
Joachim Sauer
2
Me gusta cómo es la sustancia de esto: Java es para novatos, confía en el GC mágico, lo sabe mejor.
Morg
1
@Morg: O puede leerlo de esa manera: Java es para las personas a las que les gusta hacer las cosas en lugar de perder el tiempo con pequeños cambios y la gestión manual de la memoria.
Landei
44
@Landei Creo que su comentario tendría mucha más credibilidad si se hubiera escrito en Java una base de código ampliamente utilizada y duradera. En mi mundo, los sistemas operativos reales están escritos en C, postgreSQL está escrito en C, al igual que las herramientas más importantes que realmente serían difíciles de reescribir. Java fue (y esa es incluso la versión oficial) para permitir que personas menos calificadas programen en rebaños y sin embargo alcancen resultados tangibles.
Morg
1
@ Morgan Me parece muy extraño cómo pareces concentrarte solo en los sistemas operativos. Esto simplemente no puede ser una buena medida, por varias razones. Primero, los requisitos de los SO son crucialmente diferentes de la mayoría de los otros softwares; segundo, tiene el principio del pulgar de Panda (¿quién quiere reescribir un SO completo en otro idioma, quién quiere escribir su propio SO si hay alternativas que funcionan e incluso gratuitas?) y el tercer software utiliza las características del sistema operativo, por lo que no es necesario escribir un controlador de disco, administrador de tareas, etc. Si no puede proporcionar algunos argumentos mejores (no basados ​​completamente en sistemas operativos), suena como un enemigo.
Landei
5

Si bien un programa Java completamente optimizado rara vez superará a un programa C ++ completamente optimizado, las diferencias en cosas como la administración de memoria pueden hacer que muchos algoritmos se implementen idiomáticamente en Java más rápido que los mismos algoritmos implementados idiomáticamente en C ++.

Como señaló @Jerry Coffin, hay muchos casos en que los cambios simples pueden hacer que el código sea mucho más rápido, pero a menudo puede tomar demasiados ajustes impuros en un idioma u otro para que la mejora del rendimiento valga la pena. Eso es probablemente lo que verías en un buen punto de referencia que muestra que Java funciona mejor que C ++.

Además, aunque generalmente no es tan significativo, hay algunas optimizaciones de rendimiento que un lenguaje JIT como Java puede hacer que C ++ no puede. El tiempo de ejecución de Java puede incluir mejoras después de compilar el código, lo que significa que el JIT puede producir código optimizado para aprovechar las nuevas (o al menos diferentes) características de la CPU. Por esta razón, un binario Java de 10 años podría superar a un binario C ++ de 10 años.

Por último, la seguridad de tipo completo en la imagen más grande puede, en casos muy raros, ofrecer mejoras de rendimiento extremas. Singularity , un sistema operativo experimental escrito casi por completo en un lenguaje basado en C #, tiene una comunicación entre procesos y multitarea mucho más rápida debido al hecho de que no hay necesidad de límites de proceso de hardware o costosos cambios de contexto.

Rei Miyasaka
fuente
5

Publicado por Tim Holloway en JavaRanch:

Aquí hay un ejemplo primitivo: cuando las máquinas operaban en ciclos matemáticamente determinados, una instrucción de rama generalmente tenía 2 tiempos diferentes. Uno para cuando se tomó la rama, uno para cuando no se tomó la rama. Por lo general, el caso sin sucursal era más rápido. Obviamente, esto significaba que podía optimizar la lógica basándose en el conocimiento de qué caso era más común (sujeto a la restricción de que lo que "sabemos" no siempre es lo que realmente es el caso).

La recompilación de JIT lleva esto un paso más allá. Supervisa el uso real en tiempo real y cambia la lógica en función de cuál es el caso más común. Y voltéelo nuevamente si la carga de trabajo cambia. El código compilado estáticamente no puede hacer esto. Así es como Java a veces puede superar el ensamblaje sintonizado a mano / código C / C ++.

Fuente: http://www.coderanch.com/t/547458/Performance/java/Ahead-Time-vs-Just-time

Thiago Negri
fuente
3
Y una vez más, esto está mal / incompleto. Los compiladores estáticos con optimación guiada por perfil pueden reconocer esto.
Konrad Rudolph
2
Konrad, ¿los compiladores estáticos pueden voltear la lógica en función de la carga de trabajo actual? Según tengo entendido, los compiladores estáticos generan código una vez y se mantiene igual para siempre.
Thiago Negri
2
Carga de trabajo actual, no. Pero carga de trabajo típica . La optimización guiada por perfil analiza cómo su programa se ejecuta bajo la carga típica y optimiza los puntos calientes en consecuencia, tal como lo hace el HotSpot JIT.
Konrad Rudolph
4

Esto se debe a que el paso final que genera el código de la máquina ocurre de manera transparente dentro de la JVM cuando se ejecuta su programa Java, en lugar de ser explícito al construir su programa C ++.

Debe tener en cuenta el hecho de que las JVM modernas pasan mucho tiempo compilando el código de bytes sobre la marcha al código de máquina nativo para hacerlo lo más rápido posible. Esto permite que la JVM realice todo tipo de trucos de compilación que pueden ser aún mejores al conocer los datos de creación de perfiles del programa que se está ejecutando.

Algo así como alinear automáticamente a un getter, de modo que no sea necesario un JUMP-RETURN para obtener un valor, acelera las cosas.

Sin embargo, lo que realmente ha permitido programas rápidos es una mejor limpieza posterior. El mecanismo de recolección de basura en Java es más rápido que el manual sin malloc en C. Muchas implementaciones modernas sin malloc usan un recolector de basura debajo.


fuente
Tenga en cuenta que estas cosas integradas hacen que el inicio de la JVM sea más grande y más lento hasta que el mejor código tenga la posibilidad de ponerse al día.
1
"Muchas implementaciones modernas libres de malloc usan un recolector de basura debajo". De Verdad? Me gustaria saber mas; ¿Tienes alguna referencia?
Sean McMillan
Gracias. Estaba tratando de encontrar una manera de decir que la JVM ya no contiene simplemente un compilador justo a tiempo compilando un código ejecutable, sino un compilador de hot spot que perfila el código en ejecución y se optimiza aún más como resultado. Un compilador único como C ++ lucha por igualar eso.
Highland Mark
@SeanMcMillan, hace un tiempo vi un análisis sobre el rendimiento de las implementaciones sin malloc donde se mencionó que el más rápido usaba un recolector de basura debajo. No recuerdo dónde lo leí.
¿Fue el GC conservador BDW?
Demi
4

Respuesta corta: no lo es. Olvídalo, el tema es tan antiguo como el fuego o la rueda. Java o .NET no es y no será más rápido que C / C ++. Es lo suficientemente rápido para la mayoría de las tareas en las que no necesita pensar en la optimización en absoluto. Al igual que los formularios y el procesamiento SQL, pero ahí es donde termina.

Para los puntos de referencia, o pequeñas aplicaciones escritas por desarrolladores incompetentes, sí, el resultado final será que Java / .NET probablemente estará cerca y tal vez incluso más rápido.

En realidad, cosas simples como asignar memoria en la pila o simplemente usar memzones simplemente matarán Java / .NET en el acto.

El mundo recolectado de basura está utilizando una especie de memzone con toda la contabilidad. Agregue memzone a C y C será más rápido allí mismo en el acto. Especialmente para esos puntos de referencia de Java "C" código de alto rendimiento ", que van así:

for(...)
{
alloc_memory//Allocating heap in a loop is verrry good, in't it?
zero_memory//Extra zeroing, we really need it in our performance code
do_stuff//something like memory[i]++
realloc//This is lovely speedup
strlen//loop through all memory, because storing string length is soo getting old
free//Java will do that outside out timing loop, but oh well, we're comparing apples to oranges here
}//loop 100000 times

Intente usar variables basadas en pila en C / C ++ (o ubicación nueva), se traducen en sub esp, 0xff, es una sola instrucción x86, supere eso con Java, no puede ...

La mayoría de las veces veo esos bancos en los que se compara Java contra C ++, lo que me hace pensar, ¿con qué? Estrategias de asignación de memoria incorrectas, contenedores de crecimiento propio sin reservas, múltiples novedades. Esto ni siquiera se acerca al código C / C ++ orientado al rendimiento.

También una buena lectura: https://days2011.scala-lang.org/sites/days2011/files/ws3-1-Hundt.pdf

Descifrador
fuente
1
Incorrecto. Completamente equivocado. No podrá superar a un GC de compactación con su gestión de memoria manual. El recuento de referencias ingenuo nunca será mejor que una marca adecuada. Tan pronto como se trata de una gestión de memoria complicada, C ++ es un retraso.
SK-logic
3
@ SK-Logic: Incorrecto, con memzones o asignación de pila NO hay asignación de memoria o desasignación en absoluto. Tienes un bloque de memoria y simplemente le escribes. Marque el bloque como libre con una variable volátil como la protección de concurrencia InterlockedExchange, etc., y el siguiente subproceso simplemente volcará sus datos al bloque preasignado sin tener que ir al sistema operativo para la memoria, si ve que es gratis. Con stack es aún más fácil, con la única excepción de que no puede volcar 50MB en la pila. Y la vida útil de ese objeto solo está dentro de {}.
Codificador
2
@ SK-logic: los compiladores son primero la corrección, el rendimiento en segundo lugar. Los motores de búsqueda, los motores de bases de datos, los sistemas de negociación en tiempo real, los juegos son lo que consideraría crítico para el rendimiento. Y la mayoría de ellos se basan en estructuras planas. Y de cualquier manera, los compiladores se escriben principalmente en C / C ++. Con asignadores personalizados, supongo. Por otra parte, no veo problemas con el uso de elementos de árbol o lista sobre rammap. Solo usa la colocación nueva. No hay mucha complejidad en eso.
Codificador
3
@ SK-logic: no es mucho más rápido, todas las aplicaciones .NET / Java que he visto, siempre resultaron ser más lentas y un verdadero cerdo. Cada reescritura de la aplicación administrada en el código SANE C / C ++ resultó en una aplicación más limpia y ligera. Las aplicaciones administradas siempre son pesadas. Vea VS2010 vs 2008. Las mismas estructuras de datos, pero VS2010 es un HOG. Las aplicaciones C / C ++ escritas correctamente generalmente se inician en milisegundos y no se atascan en las pantallas de inicio, mientras que también consumen mucha menos memoria. El único inconveniente es que debe codificar teniendo en cuenta el hardware, y muchas personas no saben cómo es hoy en día. Solo los puntos de referencia donde se manejan tienen una oportunidad.
Codificador
2
Su evidencia anecdótica no cuenta. Los puntos de referencia adecuados muestran la verdadera diferencia. Es especialmente extraño que se refiera a las aplicaciones GUI, vinculadas a las bibliotecas de GUI voluminosas y subóptimas. Y, lo que es más importante: en teoría, el límite de rendimiento es mucho mayor para un GC implementado correctamente.
SK-logic
2

La realidad es que ambos son simplemente ensambladores de alto nivel que hacen exactamente lo que el programador les dice, exactamente cómo el programador les dice que lo hagan en el orden exacto que el programador les dice. Las diferencias de rendimiento son tan pequeñas que no son importantes para todos los fines prácticos.

El lenguaje no es "lento", el programador escribió un programa lento. Muy raramente, un programa escrito de la mejor manera en un idioma supera a un programa que hace lo mismo usando la mejor manera del idioma alternativo, a menos que el autor del estudio esté dispuesto a moler su hacha particular.

Obviamente, si va a un caso marginal raro como los sistemas embebidos en tiempo real, la elección del idioma puede marcar la diferencia, pero ¿con qué frecuencia es este el caso? y de esos casos, con qué frecuencia la elección correcta no es obvia a ciegas.

Mattnz
fuente
2
En teoría, una VM JITting "ideal" debe superar el código compilado estáticamente, ajustando sus optimizaciones a la información de perfiles recopilada dinámicamente. En la práctica, los compiladores JIT aún no son tan inteligentes, pero al menos son capaces de producir un código de calidad similar a sus pares estáticos más grandes y más lentos.
SK-logic
2

Vea los siguientes enlaces ... Pero, ¿cómo es esto posible? Me sorprende que el bytecode interpretado pueda ser más rápido que un lenguaje compilado.

  1. ¿Esas publicaciones en el blog proporcionan evidencia confiable?
  2. ¿Estas publicaciones de blog proporcionan evidencia definitiva?
  3. ¿Estas publicaciones de blog incluso proporcionan evidencia sobre el "bytecode interpretado"?

Keith Lea te dice que hay "defectos obvios", pero no hace nada con respecto a esos "defectos obvios". En 2005, esas viejas tareas fueron descartadas y reemplazadas por las tareas que ahora se muestran en el juego de puntos de referencia .

Keith Lea le dice que "tomó el código de referencia para C ++ y Java del desactualizado Great Computer Language Shootout y ejecutó las pruebas", pero en realidad solo muestra medidas para 14 de 25 de esas pruebas desactualizadas .

Keith Lea ahora te dice que no estaba tratando de probar nada con la publicación del blog siete años antes, pero en ese entonces dijo "Estaba harto de escuchar a la gente decir que Java era lento, cuando sé que es bastante rápido ...", lo que sugiere en aquel entonces había algo que intentaba probar.

Christian Felde te dice: "No creé el código, solo volví a ejecutar las pruebas". como si eso lo absolviera de cualquier responsabilidad por su decisión de publicitar mediciones de las tareas y programas que Keith Lea seleccionó.

¿Las mediciones de incluso 25 pequeños programas proporcionan evidencia definitiva?

Esas medidas son para programas ejecutados como Java "modo mixto" Java no interpretado - "Recuerda cómo funciona HotSpot". Puede averiguar fácilmente qué tan bien Java ejecuta el "bytecode interpretado", porque puede obligar a Java a interpretar solo el bytecode, simplemente cronometrando que algunos programas Java se ejecuten con y sin la opción -Xint.

igouy
fuente
-1

Me divierte lo generalizada que es esta extraña noción de "bytecode interpretado". ¿Alguna vez han oído hablar de la compilación JIT? Su argumento no se puede aplicar a Java.

Pero, dejando a un lado JVM, hay casos en los que un código de subproceso directo o incluso una interpretación trivial de bytecode puede superar fácilmente a un código nativo altamente optimizado. La explicación es bastante simple: el código de bytes puede ser bastante compacto y se ajustará a su pequeño caché cuando una versión de código nativo del mismo algoritmo termine teniendo varios errores de caché para una sola iteración.

SK-logic
fuente
La omnipresencia de la interpretación tal vez se deba a personas con conocimientos de informática. La máquina virtual java es una máquina que acepta el código de bytes de Java y lo ejecuta en una máquina / no / capaz de ejecutar código de bytes de Java de forma nativa, sin ser capaz de escribir un programa nativo funcionalmente equivalente. Ergo es un intérprete. Puede dar a sus técnicas de almacenamiento en caché cualquier nombre que se le ocurra, JIT o de otro modo, pero es un intérprete según la definición de intérprete.
thiton
@thiton, es probable que tu propio CS-fu sea un poco débil. JVM no hace ningún tipo de interpretación (por puntos calientes) - como alguien que se atreven a hablar de CS, que tiene que saber qué significa, y cómo una semántica operacional de una interpretación es diferente de la ejecución de un código nativo. Pero, probablemente, simplemente no conoces lo suficiente de CS para distinguir la compilación de la interpretación.
SK-logic
2
Umm, pero el bytecode, para poder ejecutarse, debe convertirse a código nativo; no puede alimentar el bytecode de Java a la CPU. Entonces el argumento de tamaño no es válido.
quant_dev
@quant_dev, por supuesto: dije que este caso no tiene ninguna relación con JVM. Necesitaría un motor de bytecode mucho más simple para que ese efecto funcione.
SK-logic
-1

Dejando a un lado JIT, GC, etc., C ++ puede ser muy, muy fácilmente, mucho más lento que Java. Esto no aparecerá en los puntos de referencia, pero la misma aplicación escrita por el desarrollador de Java y un desarrollador de C ++ puede ser mucho más rápida en Java.

  • Sobrecarga del operador. Cada operador simple como "+" o "=" puede llamar a cientos de líneas de código para realizar controles de seguridad, operaciones de disco, registro, seguimiento y creación de perfiles. Y son tan fáciles de usar que una vez que sobrecarga los operadores, los usa de forma natural y copiosa sin darse cuenta de cómo se acumula el uso.
  • Plantillas. Estos no afectan tanto la velocidad como la memoria. El uso descuidado de las plantillas generará millones de líneas de código (alternativas a la plantilla básica) sin que usted se dé cuenta. Pero luego los tiempos de carga binarios, el uso de memoria, el uso de intercambio, todo eso también actúa contra los puntos de referencia. Y el uso de RAM va por las nubes.

En cuanto a los patrones de herencia avanzados, estos son bastante similares: C ++ tiene algunos que Java no tiene y viceversa, pero todos ellos introducen una sobrecarga similar y significativa. Por lo tanto, no hay una ventaja especial de C ++ en la programación de objetos pesados.

Una advertencia más: GC puede ser más rápido o más lento que administrar asignaciones manualmente. Si asigna muchos objetos pequeños, en el entorno GC generalmente se asigna una porción de memoria y se envían partes de ella según sea necesario para nuevos objetos. En administrado: cada objeto = asignación separada lleva un tiempo considerable. OTOH, si mallocas () mucha memoria a la vez y luego solo asignas partes de ella a tus objetos manualmente, o usas pocas instancias más grandes de objetos, puedes llegar mucho más rápido.

SF.
fuente
44
No estoy de acuerdo con ambos puntos. Si usa operadores o métodos es irrelevante. Dices que proliferarán. Tonterías: no más que los métodos; usted necesita llamarlos o no. Y las plantillas no generan más código que escribir a mano ese código en particular nuevamente para uso múltiple. Puede haber más código que con el despacho de tiempo de ejecución (funciones virtuales) pero esto también será irrelevante: el rendimiento de las líneas de caché de instrucciones es lo más importante en bucles cerrados y aquí solo se usará una instancia de plantilla, por lo que no hay presión de memoria relevante debido a las plantillas.
Konrad Rudolph
La mentalidad habitual es que los métodos son caros, los operadores son baratos. Utiliza métodos cuando debe hacerlo, operadores cuando desea ahorrar tiempo y optimizar. No es una cuestión técnica, sino psicológica: no es que los operadores sean "más pesados", son mucho más fáciles de usar y se usan con mucha más frecuencia. (además, puede sobrecargar un operador de uso común en un código preexistente, haciendo que haga lo mismo que el original, más un extra, y de repente todo el código se ralentizará significativamente.
SF.
Supongo que este hecho psicológico es real, e incluso si lo es, no tiene la opción : si necesita una funcionalidad, la usa, ya sea encapsulada en un operador o un método. La psicología es irrelevante para su elección de la semántica.
Konrad Rudolph
1
Pregunta capciosa. No dudaría en nada de esto, lo mediría, actuaría entonces . He Nunca tuvimos un problema con esa táctica.
Konrad Rudolph
1
@KonradRudolph: Todo esto es cierto cuando se trata de claridad y facilidad para escribir el código, lo que lo hace libre de errores y fácil de mantener. Sin embargo, el punto sobre la eficiencia de la implementación del algoritmo sigue en pie: si está a punto de escribir obj.fetchFromDatabase("key")tres veces dentro de cinco líneas de código para la misma clave, pensará dos veces si buscar ese valor una vez y almacenarlo en una variable local. Si escribe obj->"key"con ->una sobrecarga para actuar como recuperación de la base de datos, es mucho más propenso a dejarlo pasar porque el costo de la operación no es aparente.
SF.
-2

De alguna manera, Stack Exchange no toma mis otros puntos de pila, así que desafortunadamente no hay respuesta ...

Sin embargo, la segunda respuesta más votada aquí está llena de información errónea en mi humilde opinión.

Una aplicación enrollada a mano por un experto en C / C ++ SIEMPRE va a ser mucho más rápida que una aplicación Java, punto. No hay "tan rápido como Java o más rápido". es más rápido, precisamente por los elementos que cita a continuación:

Compilación JIT : ¿Realmente espera que un optimizador automático tenga la inteligencia de un programador experto y vea el vínculo entre la intención y el código que la CPU realmente ejecutará? Además, todo el JIT que haces es tiempo perdido en comparación con un programa ya compilado.

Garbage Collection es una herramienta que simplemente desasigna recursos que un programador habría olvidado desasignar, de una manera más o menos eficiente.

Evidentemente, esto solo puede ser más lento de lo que un programador experto en C (elegiste el término) haría para manejar su memoria (y no, no hay fugas en las aplicaciones escritas correctamente).

Una aplicación C de rendimiento optimizado conoce la CPU en la que se está ejecutando, ha sido compilada en ella, de lo contrario, eso significa que usted no dio todos los pasos para el rendimiento, ¿verdad?

Estadísticas de tiempo de ejecución Esto está más allá de mi conocimiento, pero sospecho que un experto en C tiene más que suficiente conocimiento de predicción de sucursales para volver a ser más astuto que la optimización automatizada.

Muy buenas bibliotecas Hay muchas funciones no muy optimizadas fácilmente disponibles a través de bibliotecas en Java, y lo mismo es cierto en cualquier lenguaje, sin embargo, las bibliotecas más optimizadas están escritas en C, especialmente para el cálculo.

La JVM es una capa de abstracción, lo que implica cosas buenas, muchas de las cuales están arriba, y también implica que la solución general es más lenta por diseño.

En general:

Java nunca puede alcanzar la velocidad de C / C ++ debido a la forma en que funciona en una JVM con mucha protección, características y herramientas.

C ++ tiene una clara ventaja clara en software optimizado, ya sea para computación o juegos, y es común ver que las implementaciones de C ++ ganen concursos de codificación hasta el punto de que las mejores implementaciones de Java solo se pueden ver en la segunda página.

En la práctica, C ++ no es un juguete y no le permitirá escapar de muchos errores que la mayoría de los lenguajes modernos pueden manejar, sin embargo, al ser más simple y menos seguro, es inherentemente más rápido.

Y como conclusión, me gustaría decir que la mayoría de las personas no dan dos centavos por esto, que al final la optimización es un deporte reservado solo a unos pocos desarrolladores afortunados y que, excepto en los casos en que el rendimiento realmente es una preocupación (es decir, multiplicar el hardware por 10 no lo ayudará, o representará al menos unos pocos millones), la mayoría de los gerentes preferirán una aplicación no optimizada y una tonelada de hardware.

Morg
fuente
De nuevo. La recolección de basura no es solo una "herramienta que desasigna". GC puede compactar sus estructuras. GC puede manejar sus referencias débiles y ayudarlo a equilibrar su almacenamiento en caché de esta manera. El GC de varias etapas hace que la asignación de un montón sea mucho más barata que la voluminosa, lenta newo malloc(). En general, puede ser mucho más rápido que cualquier administración manual de memoria, ya que no podría reubicar objetos manualmente. Entonces, todo su razonamiento es claramente erróneo y parcial. Su conocimiento de los algoritmos GC y los métodos de optimización JIT es demasiado limitado.
SK-logic
44
Esta respuesta está llena de ideas erróneas sobre lo que pueden hacer los optimizadores modernos. El código optimizado a mano no tiene ninguna posibilidad contra eso. Pero entonces, C ++ también tiene un compilador optimizador.
Konrad Rudolph
Gracias por su comentario SK-logic, pero como lo dice, el GC puede ser mucho más rápido en general, estamos hablando de lo que será más rápido en un caso particular, y parece que la mayoría de la gente está de acuerdo en que cualquier cosa que el GC pueda Qué puede hacer un programador, y aún mejor. Por supuesto, puede reubicar objetos manualmente cuando tiene acceso directo a la memoria. Jajaja. Mi conocimiento de las partes internas de JVM es seguro limitado y espero que los jefes de Java me muestren la luz, no solo me digan basura aleatoria sobre que el GC puede hacer cosas que uno no puede hacer manualmente (risas ... incluso el GC tiene que use las instrucciones de la CPU;)).
Morg
Konrad, estoy de acuerdo, subestimo en gran medida los optimizadores modernos ... sin embargo, me parece interesante que consideres que el código optimizado a mano es inferior al código optimizado automáticamente. ¿Qué esperas que el compilador vea exactamente que un humano no puede?
Morg
1
Derecha . siga presionando -1, eso no cambiará el hecho de que C ++ es más rápido que Java. Puede que no sepa mucho sobre los compiladores modernos, pero eso no hace ninguna diferencia en el punto principal, que es correcto y contradice la respuesta más votada aquí. ¿Por qué otra razón sería C ++ una prioridad para nVidia en sus GPU para HPC? ¿Por qué si no todos los juegos se escribirían en C ++, por qué todos los motores de DB se escribirían en C?
Morg
-4

He visto al menos dos mmo impresionantes hechos en Java, decir que no es lo suficientemente rápido para los juegos es un nombre inapropiado. El hecho de que los diseñadores de juegos prefieran C ++ más que otros lenguajes dice que no solo está relacionado con Java, solo significa que los programadores nunca han incursionado en ningún otro lenguaje / paradigma de programación. Cualquier cosa en cualquier lenguaje tan avanzado como C / C ++ o incluso Java puede producir código que técnicamente podría cumplir o vencer el argumento de la velocidad. Todo eso bien dicho se reduce a lo que los programadores saben, con qué equipos trabajan más y lo más importante por qué usan dichas herramientas. Dado que estamos abordando el aspecto de desarrollo de juegos de la programación, entonces debe haber más en el argumento. En pocas palabras ' Todo se trata de dinero y tiempo para un negocio empeñado en usar herramientas que cumplan con el control de calidad y en el mundo real no tiene peso en las razones xx para elegir C ++ en lugar de Java o cualquier otro lenguaje. Es solo una decisión de producción en masa. En el nivel más básico de algoritmos informáticos, todos con los que estamos jugando son unos y ceros, el argumento de la velocidad es uno de los argumentos más tontos jamás aplicados a los juegos. Si desea aumentar la velocidad de esa manera, elimine los lenguajes de programación por completo y trabaje con el ensamblaje que posiblemente sea la mejor ventaja con diferencia.

Meh
fuente
2
Este muro de texto no parece agregar nada que no haya sido mencionado en las otras respuestas. Por favor, editar su respuesta sea más fácil de leer, y por favor asegúrese de que su dirección de respuesta no cuestiones planteadas por la otra respuesta. De lo contrario, considere eliminar su respuesta, ya que solo agrega ruido en este momento.