¿Pueden los idiomas OO modernos competir con el rendimiento de la tienda matriz de C ++?

40

Acabo de darme cuenta de que todos los lenguajes de programación OO modernos con los que estoy al menos algo familiarizado (que es básicamente solo Java, C # y D) permiten 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 de Covariant son un agujero en el sistema de tipo estático. Posibilitan errores de tipo que no pueden detectarse 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 golpe de rendimiento terrible si hago muchas tiendas de arreglos.

C ++ no tiene matrices covariantes, por lo que no hay necesidad de realizar dicha verificación en tiempo de ejecución, lo que significa que no hay una penalización de rendimiento.

¿Se realiza algún análisis para reducir la cantidad de verificaciones 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 que no he pensado.

¿Los compiladores modernos realmente hacen este tipo de optimizaciones, o tengo que vivir con el hecho de que, por ejemplo, un Quicksort siempre hace O (n log n) verificaciones de tiempo de ejecución innecesarias?

¿Pueden los idiomas OO modernos evitar la sobrecarga creada al admitir matrices de co-variante?

    
pregunta fredoverflow 17.01.2012 - 22:48

8 respuestas

33

D no tiene matrices covariantes. Se les permitió antes del lanzamiento más reciente ( dmd 2.057 ), pero 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 cuando se indexa una matriz, pero se elimina al compilar con -release . Por lo tanto, en el modo de lanzamiento, no hay una diferencia de rendimiento real entre los arreglos en C / C ++ y los de D.

    
respondido por el Jonathan M Davis 17.01.2012 - 23:19
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 puedes evitar la verificación de una matriz de tipo Foo .

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

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

enlace

Un problema un tanto relacionado es la matriz de comprobación de límites. Esta podría ser una lectura interesante (pero antigua) sobre las optimizaciones realizadas en el CLR (también se menciona la covarianza 1 lugar): enlace

    
respondido por el Lasse Espeholt 17.01.2012 - 22:51
13

respuesta de Java:

Supongo que en realidad no has evaluado el código, ¿verdad? En general, el 90% de todos los lanzamientos dinámicos en Java son gratuitos porque el JIT puede sortearlos (la orden rápida debería ser un buen ejemplo para esto) y el resto son una secuencia ld/cmp/br que es absolutamente predecible (si no, ¿por qué diablos es?) ¿Tu código lanzando todas esas excepciones dinámicas?

Hacemos la carga mucho antes que la comparación real, la rama se predice correctamente en el 99.9999% (¡estadísticas compuestas!) de todos los casos, por lo que no bloqueamos la tubería (asumiendo que no golpeamos la memoria con el carga, si no es así, será notable, pero entonces la carga es necesaria de todos modos). Por lo tanto, el costo es 1 ciclo de reloj SI el JIT no puede evitar el cheque en absoluto.

Algunos gastos generales? Claro, pero dudo que alguna vez lo note ..

Para ayudar a respaldar mi respuesta, consulte este Dr. Poste de blog Cliff Click que discute el rendimiento de Java vs. C

    
respondido por el Voo 17.01.2012 - 23:19
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.

Se le puede perdonar el error, ya que este error solo se solucionó en el El último DMD, lanzado el 13 de diciembre.

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

    
respondido por el Peter Alexander 17.01.2012 - 23:19
5

De la prueba que he hecho en una computadora portátil barata, la diferencia entre usar int[] y Integer[] es 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 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 asignaciones solas sean un factor muy pequeño en cualquier programa real. p.ej. 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 utiliza un objeto complejo como String.

    
respondido por el Peter Lawrey 18.01.2012 - 00:11
2

¿Preguntó por otros idiomas OO modernos? Bueno, Delphi evita este problema por completo haciendo que string sea una primitiva, no un objeto. Por lo tanto, 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 el código nativo, sin sobrecarga de comprobación de tipo.

Sin embargo, los arreglos de cadenas no se usan muy a menudo; Los programadores de Delphi tienden a favorecer la clase TStringList . 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 ha sido comparada con una navaja suiza. Así que es idiomático utilizar 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 orificios del sistema que se describen aquí.

    
respondido por el Mason Wheeler 18.01.2012 - 00:01
1
  ¿

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

El rendimiento de la CPU no es monotónico, 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 realice la comprobación de límites en arreglos sea realmente más rápido que el programa deducido del anterior al eliminar estas comprobaciones de límites.

El motivo de este comportamiento es que la comprobación de límites modifica la alineación del código en la memoria, modificará la frecuencia de los accesos de caché, etc.

Así que sí, conviva con el hecho de que Quicksort siempre realiza comprobaciones falsas con O (n log n) y optimiza después del perfilado.

    
respondido por el user40989 09.01.2014 - 13:01
1

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

    
respondido por el Pillsy 09.01.2014 - 16:02

Lea otras preguntas en las etiquetas