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?
Respuestas:
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:
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.fuente
T[dim]
se puede convertir implícitamente a uno de los siguientes: ...U[]
... Una matriz dinámicaT[]
se puede convertir implícitamente a uno de los siguientes:U[]
. .. dondeU
es una clase base deT
".const U[]
, ya que no puede asignar el tipo incorrecto a los elementos de la matriz, peroT[]
definitivamente no se convierte aU[]
mientrasU[]
sea mutable. Permitir matrices covariantes como lo hacía antes era un grave defecto de diseño que ahora se ha corregido.Sí, una optimización crucial es esta:
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)
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
fuente
val a = Array("st"); val b: Array[Any] = a
es ilegal. (Sin embargo, las matrices en Scala son ... magia especial ... debido a la JVM subyacente utilizada.)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 uno
ld/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.
fuente
D no permite matrices covariantes.
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 ++.
fuente
De la prueba que hice en una computadora portátil barata, la diferencia entre usar
int[]
yInteger[]
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.
fuente
¿Preguntaste sobre otros idiomas modernos de OO? Bueno, Delphi evita este problema por completo al
string
ser 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
TStringList
clase. 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í.
fuente
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.
fuente
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.
fuente