tecnicas site realizarse page optimizacion off hacer google definición define debe como auditoria antes java c++ performance caching optimization

java - site - seo off page definición



¿Es una optimización sensata comprobar si una variable tiene un valor específico antes de escribir ese valor? (8)

if (var != X) var = X;

¿Es sensible o no? ¿El compilador siempre optimizará la instrucción if? ¿Hay algún caso de uso que se beneficiaría de la declaración if?

¿Qué pasa si var es una variable volátil?

Estoy interesado en las respuestas de C ++ y Java, ya que las variables volátiles tienen una semántica diferente en ambos idiomas. Además, la compilación JIT de Java puede marcar la diferencia.

La instrucción if introduce bifurcación y lectura adicional que no sucedería si siempre sobrescribimos var con X, entonces es malo. Por otro lado, si var == X entonces usando esta optimización, solo hacemos una lectura y no realizamos una escritura, lo que podría tener algunos efectos en la memoria caché. Claramente, hay algunas concesiones aquí. Me gustaría saber cómo se ve en la práctica. ¿Alguien ha hecho alguna prueba sobre esto?

EDITAR:

Me interesa sobre todo cómo se ve en un entorno multiprocesador. En una situación trivial, no parece tener mucho sentido verificar primero la variable. Pero cuando la coherencia de la memoria caché debe mantenerse entre procesadores / núcleos, la verificación adicional puede ser realmente beneficiosa. Me pregunto qué tan grande impacto puede tener. Además, ¿no debería el procesador hacer tal optimización en sí mismo? Si var == X asignarle una vez más el valor X no debería ''ensuciar'' la caché. ¿Pero podemos confiar en esto?


¿Es una optimización sensata comprobar si una variable tiene un valor específico antes de escribir ese valor?

¿Hay algún caso de uso que se beneficiaría de la declaración if?

Es cuando la asignación es significativamente más costosa que una comparación de desigualdad que devuelve false .

Un ejemplo sería un gran * std::set , que puede requerir muchas asignaciones de montón para duplicar.

** para alguna definición de "grande" *

¿El compilador siempre optimizará la instrucción if?

Es un "no" bastante seguro, ya que la mayoría de las preguntas contienen tanto "optimizar" como "siempre".

El estándar de C ++ hace una mención rara de optimizaciones, pero nunca exige una.

¿Qué pasa si var es una variable volátil?

Entonces puede realizar el if , aunque volatile no logra lo que la mayoría de la gente asume.


En C ++, asignar una variable SIMPLE (es decir, un entero normal o una variable flotante) es definitivamente más rápido que comprobar si ya tiene ese valor y luego establecerlo si no tiene el valor. Me sorprendería mucho que esto no fuera cierto en Java también, pero no sé lo complicadas o simples que son las cosas en Java. He escrito unas pocas líneas y no he estudiado realmente cómo el código byte y el bytecode JIT trabajos.

Claramente, si la variable es muy fácil de verificar, pero complicada de configurar, que podría ser el caso para las clases y otras cosas similares, entonces puede haber un valor. El caso típico donde encontraría esto sería en algún código donde el "valor" es algún tipo de índice o hash, pero si no coincide, se requiere mucho trabajo. Un ejemplo sería en un cambio de tarea:

if (current_process != new_process_to_run) current_process == new_process_to_run;

Porque aquí, un "proceso" es un objeto complejo para alterar, pero el != Se puede hacer en la identificación del proceso.

Si el objeto es simple o complejo, el compilador casi seguramente no entenderá lo que está tratando de hacer aquí, por lo que probablemente no lo optimizará, pero los compiladores son más inteligentes de lo que piensa A VECES y más estúpidos en otros momentos, por lo que No apostaría de ninguna manera.

volatile siempre debe obligar al compilador a leer y escribir valores en la variable, ya sea que "piense" que es necesario o no, por lo que definitivamente LEERÁ la variable y ESCRIBIRÁ la variable. Por supuesto, si la variable es volatile , probablemente signifique que puede cambiar o representar algún hardware, por lo que debe ser EXTRA cuidadoso con la forma de tratarlo usted también ... Una lectura extra de una tarjeta PCI-X podría incurrir en varios ciclos de bus (¡los ciclos del bus son un orden de magnitud más lento que la velocidad del procesador!), lo que probablemente afecte el rendimiento mucho más. Pero luego escribir en un registro de hardware puede (por ejemplo) hacer que el hardware haga algo inesperado, y verificar que tenemos ese valor primero PUEDE hacerlo más rápido, porque "alguna operación comienza de nuevo", o algo así.


En Objective-C tiene la situación en la que la asignación de una dirección de objeto a una variable de puntero puede requerir que el objeto sea "retenido" (el recuento de referencias se incrementa). En tal caso, tiene sentido ver si el valor asignado es el mismo que el valor actualmente en la variable del puntero, para evitar tener que hacer las operaciones de incremento / decremento relativamente costosas.

Otros idiomas que usan recuento de referencias probablemente tengan escenarios similares.

Pero al asignar, por ejemplo, un int o un boolean a una variable simple (fuera del escenario de caché multiprocesador mencionado en otro lugar), la prueba rara vez se merece. La velocidad de una tienda en la mayoría de los procesadores es al menos tan rápida como la carga / prueba / bifurcación.


En general, la respuesta es no. Dado que si tiene un tipo de datos simple, el compilador podría realizar cualquier optimización necesaria. Y en el caso de los tipos con operador pesado = es responsabilidad del operador = elegir la forma óptima de asignar un nuevo valor.


En java, la respuesta siempre es no. Todas las asignaciones que puedes hacer en Java son primitivas. En C ++, la respuesta sigue siendo que casi siempre no; si copiar es mucho más caro que una verificación de igualdad, la clase en cuestión debería hacer esa comprobación de igualdad en sí misma.


Hay situaciones en las que incluso una asignación trivial de, digamos, una variable pointersized puede ser más costosa que una lectura y una bifurcación (especialmente si es predecible).

¿Por qué? Multithreading. Si varios subprocesos solo leen el mismo valor, todos pueden compartir ese valor en sus cachés. Pero tan pronto como escribe en él, tiene que invalidar la línea de caché y obtener el nuevo valor la próxima vez que quiera leerlo o tiene que obtener el valor actualizado para mantener la coherencia de su caché. Ambas situaciones generan más tráfico entre los núcleos y agregan latencia a las lecturas.

Si la rama es bastante impredecible, es probable que sea aún más lenta.


Sí, definitivamente hay casos en que esto es sensato, y como usted sugiere, las variables volátiles son uno de esos casos, ¡incluso para el acceso de un único subproceso!

Las escrituras volátiles son caras, tanto desde el hardware como desde una perspectiva de compilador / JIT. En el nivel del hardware, estas escrituras pueden ser 10x-100x más caras que una escritura normal, ya que los búferes de escritura deben ser eliminados (en x86, los detalles variarán según la plataforma). En el nivel compilador / JIT, las escrituras volátiles inhiben muchas optimizaciones comunes.

La especulación, sin embargo, solo puede llevarlo tan lejos, la prueba siempre está en la evaluación comparativa. Aquí hay un microbenchmark que prueba tus dos estrategias. La idea básica es copiar valores de una matriz a otra (prácticamente System.arraycopy), con dos variantes: una que se copia incondicionalmente y otra que comprueba si los valores son diferentes primero.

Aquí están las rutinas de copia para el caso simple y no volátil (fuente completa here ):

// no check for (int i=0; i < ARRAY_LENGTH; i++) { target[i] = source[i]; } // check, then set if unequal for (int i=0; i < ARRAY_LENGTH; i++) { int x = source[i]; if (target[i] != x) { target[i] = x; } }

Los resultados que usan el código anterior para copiar una longitud de matriz de 1000, usando Caliper como mi arnés microbenchmark, son:

benchmark arrayType ns linear runtime CopyNoCheck SAME 470 = CopyNoCheck DIFFERENT 460 = CopyCheck SAME 1378 === CopyCheck DIFFERENT 1856 ====

Esto también incluye aproximadamente 150ns de sobrecarga por ejecución para restablecer la matriz objetivo cada vez. Omitir el cheque es mucho más rápido: alrededor de 0,47 ns por elemento (o alrededor de 0,32 ns por elemento después de que eliminemos la sobrecarga de configuración, por lo que exactamente exactamente 1 ciclo en mi caja).

La comprobación es aproximadamente 3 veces más lenta cuando las matrices son iguales y 4 veces más lentas que las diferentes. Me sorprende lo malo que es el cheque, dado que está perfectamente predicho. Sospecho que el culpable es en gran medida el JIT, con un cuerpo de bucle mucho más complejo, puede desenrollarse menos veces y otras optimizaciones pueden no aplicarse.

Cambiemos al caso volátil. Aquí, he utilizado AtomicIntegerArray como mis matrices de elementos volátiles, ya que Java no tiene ningún tipo de matriz nativa con elementos volátiles. Internamente, esta clase solo está escribiendo directamente en la matriz usando sun.misc.Unsafe , que permite escrituras volátiles. El conjunto generado es sustancialmente similar al acceso a una matriz normal, aparte del aspecto volátil (y posiblemente la eliminación de verificación de rango, que puede no ser efectiva en el caso de AIA).

Aquí está el código:

// no check for (int i=0; i < ARRAY_LENGTH; i++) { target.set(i, source[i]); } // check, then set if unequal for (int i=0; i < ARRAY_LENGTH; i++) { int x = source[i]; if (target.get(i) != x) { target.set(i, x); } }

Y aquí están los resultados:

arrayType benchmark us linear runtime SAME CopyCheckAI 2.85 ======= SAME CopyNoCheckAI 10.21 =========================== DIFFERENT CopyCheckAI 11.33 ============================== DIFFERENT CopyNoCheckAI 11.19 =============================

Las cosas han cambiado. La primera comprobación es ~ 3.5 veces más rápida que el método habitual. En general, todo es mucho más lento: en el caso del cheque, estamos pagando ~ 3 ns por ciclo, y en el peor de los casos ~ 10 ns (los tiempos anteriores están en nosotros, y cubren la copia del conjunto de 1000 elementos). Las escrituras volátiles realmente son más caras. Hay aproximadamente 1 ns de overheaded incluidos en el caso DIFFERENT para restablecer la matriz en cada iteración (razón por la cual incluso el simple es ligeramente más lento para DIFFERENT). Sospecho que una gran parte de la sobrecarga en el caso de "verificación" es en realidad comprobación de límites.

Esto es todo de rosca simple. Si tu real tuviese una contención de núcleo cruzado sobre un volátil, los resultados serían mucho, mucho peores para el método simple, y casi tan buenos como el anterior para el caso de verificación (la línea de caché se ubicaría simplemente en el estado compartido - no tráfico de coherencia necesario).

También solo he probado los extremos de "cada elemento igual" frente a "cada elemento diferente". Esto significa que la rama en el algoritmo de "comprobación" siempre está perfectamente predicha. Si tuviera una combinación de iguales y diferentes, no obtendría solo una combinación ponderada de los tiempos para los MISMOS y DIFERENTES casos; lo haría peor, debido a la predicción errónea (tanto a nivel de hardware, y quizás también en el nivel JIT , que ya no puede optimizar para la rama siempre tomada).

Por lo tanto, si es sensato, incluso si es volátil, depende del contexto específico: la combinación de valores iguales y desiguales, el código circundante, etc. Normalmente no lo haría solo por volátil en un escenario de subproceso único, a menos que sospechara que una gran cantidad de conjuntos son redundantes. Sin embargo, en las estructuras de muchos subprocesos múltiples, leer y luego escribir de forma volátil (u otra operación costosa, como CAS) es una mejor práctica y verá un código de calidad como java.util.concurrent structures.


Sería sensato si tuvieras una semántica de bloqueo de lectura-escritura involucrada, siempre que leer sea menos disruptivo que escribir.