¿Pueden los lenguajes OO modernos competir con el rendimiento de la tienda de arreglos de C ++?

40

Acabo de notar que cada lenguaje de programación OO moderno con el que estoy al menos algo familiarizado (que es básicamente Java, C # y D) permite matrices covariantes. Es decir, una matriz de cadenas es una matriz de objetos:

Object[] arr = new String[2];   // Java, C# and D allow this

Las matrices covariantes son un agujero en el sistema de tipo estático. Hacen posibles errores de tipo que no se pueden detectar en tiempo de compilación, por lo que cada escritura en una matriz debe verificarse en tiempo de ejecución:

arr[0] = "hello";        // ok
arr[1] = new Object();   // ArrayStoreException

Esto parece un terrible golpe de rendimiento si hago muchas tiendas de arreglos.

C ++ no tiene matrices covariantes, por lo que no es necesario hacer una verificación de tiempo de ejecución, lo que significa que no hay penalización de rendimiento.

¿Se realiza algún análisis para reducir la cantidad de comprobaciones de tiempo de ejecución necesarias? Por ejemplo, si digo:

arr[1] = arr[0];

se podría argumentar que la tienda no puede fallar. Estoy seguro de que hay muchas otras optimizaciones posibles en las que no he pensado.

¿Realmente los compiladores modernos hacen este tipo de optimizaciones, o tengo que vivir con el hecho de que, por ejemplo, un Quicksort siempre realiza O (n log n) comprobaciones de tiempo de ejecución innecesarias?

¿Pueden los lenguajes OO modernos evitar la sobrecarga creada al admitir matrices covariantes?

flujo libre
fuente
77
Estoy confundido por qué sugiere que C ++ es más rápido que otros lenguajes en una función que C ++ ni siquiera admite.
17
@eco: C ++ es más rápido con acceso a la matriz porque no admite matrices covariantes. Fred quiere saber si es posible que los "lenguajes OO modernos" eviten la sobrecarga de las matrices covariantes para acercarse a las velocidades de C ++.
Mooing Duck
13
"El código que compila pero arroja excepciones en tiempo de ejecución es una mala noticia"
44
@Jesse Y millones de personas escriben código confiable y altamente escalable en lenguajes dinámicos. Imo: Si no escribe casos de prueba para su código, no me importa si hay errores del compilador o no, de todos modos no voy a confiar en él.
Voo
77
@Jesse ¿Y cuándo más esperarías excepciones, pero en tiempo de ejecución? El problema no es el código que arroja excepciones en tiempo de ejecución; hay muchos casos en los que eso tiene sentido; el problema es el código que se garantiza que es incorrecto y que el compilador no atrapa estáticamente, sino que da como resultado una excepción en tiempo de ejecución
Jonathan M Davis el

Respuestas:

33

D no tiene matrices covariantes. Les permitió antes de la versión más reciente ( dmd 2.057 ), pero ese error se ha solucionado.

Una matriz en D es efectivamente solo una estructura con un puntero y una longitud:

struct A(T)
{
    T* ptr;
    size_t length;
}

La comprobación de límites se realiza normalmente al indexar una matriz, pero se elimina cuando compila con -release. Entonces, en el modo de lanzamiento, no hay una diferencia de rendimiento real entre las matrices en C / C ++ y las de D.

Jonathan M Davis
fuente
Parece no - d-programming-language.org/arrays.html dice "matriz A estática T[dim]se puede convertir implícitamente a uno de los siguientes: ... U[]... Una matriz dinámica T[]se puede convertir implícitamente a uno de los siguientes: U[]. .. donde Ues una clase base de T".
Ben Voigt
2
@BenVoigt Entonces los documentos en línea deben actualizarse. Desafortunadamente, no siempre están 100% actualizados. La conversión funcionará const U[], ya que no puede asignar el tipo incorrecto a los elementos de la matriz, pero T[]definitivamente no se convierte a U[]mientras U[]sea ​​mutable. Permitir matrices covariantes como lo hacía antes era un grave defecto de diseño que ahora se ha corregido.
Jonathan M Davis
@JonathanMDavis: Las matrices covariantes son repulsivas, pero funcionan bien para el escenario particular de código que solo escribirá en una matriz de elementos que se han leído desde esa misma matriz . ¿Cómo permitiría D que uno escriba un método que pueda ordenar matrices de tipo arbitrario?
supercat
@supercat Si desea escribir una función que clasifique tipos arbitrarios, entonces modifíquela. por ejemplo, dlang.org/phobos/std_algorithm.html#sort
Jonathan M Davis
21

Sí, una optimización crucial es esta:

sealed class Foo
{
}

En C #, esta clase no puede ser un supertipo para ningún tipo, por lo que puede evitar la verificación de una matriz de tipos Foo.

Y a la segunda pregunta, en F # las matrices co-variantes no están permitidas (pero supongo que la verificación permanecerá en el CLR a menos que se encuentre innecesario en optimizaciones en tiempo de ejecución)

let a = [| "st" |]
let b : System.Object[] = a // Fails

https://stackoverflow.com/questions/7339013/array-covariance-in-f

Un problema algo relacionado es la comprobación de límites de matriz. Esta podría ser una lectura interesante (pero antigua) sobre las optimizaciones realizadas en el CLR (la covarianza también se menciona en 1 lugar): http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds -check-elimination-in-the-clr.aspx

Lasse Espeholt
fuente
2
Scala también evita esta construcción: val a = Array("st"); val b: Array[Any] = aes ilegal. (Sin embargo, las matrices en Scala son ... magia especial ... debido a la JVM subyacente utilizada.)
pst
13

Respuesta Java:

Supongo que no has comparado el código, ¿verdad? En general, el 90% de todos los lanzamientos dinámicos en Java son gratuitos porque el JIT puede eludirlos (la selección rápida debería ser un buen ejemplo para esto) y el resto son unold/cmp/br secuencia que es absolutamente predecible (si no, bueno, ¿por qué demonios está lanzando su código? todas esas excepciones de reparto dinámico?

Hacemos la carga mucho antes de la comparación real, la bifurcación se predice correctamente en el 99.9999% (¡estadística compuesta!) De todos los casos, por lo que no detenemos la tubería (suponiendo que no golpeemos la memoria con la carga, si no bien eso se notará, pero de todos modos la carga es necesaria). Por lo tanto, el costo es de 1 ciclo de reloj SI el JIT no puede evitar la verificación en absoluto.

¿Alguna sobrecarga? Claro, pero dudo que alguna vez lo notes ...


Para ayudar a respaldar mi respuesta, consulte esta publicación de blog de Dr. Cliff Click sobre Java vs. C.

Voo
fuente
10

D no permite matrices covariantes.

void main()
{
    class Foo {}
    Object[] a = new Foo[10];
}  

/* Error: cannot implicitly convert expression (new Foo[](10LU)) of type Foo[]
to Object[] */

Como dices, sería un agujero en el sistema de tipos permitir esto.

Puede ser perdonado por el error, ya que este error solo se solucionó en la última DMD, lanzada el 13 de diciembre.

El acceso a la matriz en D es tan rápido como en C o C ++.

Peter Alexander
fuente
De acuerdo con d-programming-language.org/arrays.html "Una matriz estática T [dim] se puede convertir implícitamente en uno de los siguientes: ... U [] ... Una matriz dinámica T [] se puede convertir implícitamente a uno de los siguientes: U [] ... donde U es una clase base de T. "
Ben Voigt
1
@BenVoigt: documentos desactualizados.
BCS
1
@BenVoigt: he creado una solicitud de extracción para actualizar la documentación. Esperemos que esto se resuelva pronto. Gracias por mencionarlo.
Peter Alexander el
5

De la prueba que hice en una computadora portátil barata, la diferencia entre usar int[]y Integer[]es de aproximadamente 1.0 ns. Es probable que la diferencia se deba a la verificación adicional del tipo.

En general, los objetos solo se usan para la lógica de nivel superior cuando no todos los ns cuentan. Si necesita guardar cada ns, evitaría usar construcciones de nivel superior como Objetos. Es probable que las tareas solas sean un factor muy pequeño en cualquier programa real. Por ejemplo, crear un nuevo objeto en la misma máquina es 5 ns.

Es probable que las llamadas a compareTo sean mucho más caras, especialmente si usa un objeto complejo como String.

Peter Lawrey
fuente
2

¿Preguntaste sobre otros idiomas modernos de OO? Bueno, Delphi evita este problema por completo al stringser un primitivo, no un objeto. Entonces, una matriz de cadenas es exactamente una matriz de cadenas y nada más, y cualquier operación en ellas es tan rápida como puede ser el código nativo, sin ningún tipo de sobrecarga de verificación.

Sin embargo, las matrices de cadenas no se usan con mucha frecuencia; Los programadores de Delphi tienden a favorecer a la TStringListclase. Para una cantidad mínima de gastos generales, proporciona un conjunto de métodos de grupo de cadenas que son útiles en tantas situaciones que la clase se ha comparado con una navaja suiza. Por lo tanto, es idiomático usar un objeto de lista de cadenas en lugar de una matriz de cadenas.

En cuanto a otros objetos en general, el problema no existe porque en Delphi, como en C ++, las matrices no son covariantes, para evitar el tipo de agujeros del sistema de tipos descritos aquí.

Mason Wheeler
fuente
1

¿o tengo que vivir con el hecho de que, por ejemplo, un Quicksort siempre realiza O (n log n) comprobaciones de tiempo de ejecución innecesarias?

El rendimiento de la CPU no es monótono, lo que significa que los programas más largos pueden ser más rápidos que los más cortos (esto depende de la CPU, y es cierto para las arquitecturas comunes x86 y amd64). Por lo tanto, es posible que un programa que realiza la verificación encuadernada en matrices sea realmente más rápido que el programa deducido del anterior al eliminar estas verificaciones encuadernadas.

La razón de este comportamiento es que la verificación encuadernada modifica la alineación del código en la memoria, modificará la frecuencia de aciertos de caché, etc.

Entonces, sí, viva con el hecho de que Quicksort siempre realiza O (n log n) comprobaciones espurias y optimiza después de la creación de perfiles.

usuario40989
fuente
1

Scala es un lenguaje OO que tiene arreglos invariables, en lugar de covariantes. Está dirigido a la JVM, por lo que no hay una ganancia de rendimiento allí, pero evita una característica errónea común tanto en Java como en C # que compromete su seguridad de tipos por razones de compatibilidad con versiones anteriores.

Pillsy
fuente