No se puede diferenciar fácilmente el desperdicio debido a la conmutación de subprocesos y debido a la contención de la memoria caché. Usted PUEDE medir la contención del hilo. A saber, en Linux, puede cat/proc/PID/XXX y obtener toneladas de estadísticas detalladas por cada hilo. SIN EMBARGO, dado que el programador preventivo no se disparará en el pie, no obtendrá más que 30 conmutadores de ctx por segundo, sin importar cuántos hilos use. Y ese tiempo será relativamente pequeño vs la cantidad de trabajo que está haciendo ... El costo real del cambio de contexto es la contaminación del caché. p.ej. hay una alta probabilidad de que tenga la mayoría de las fallas de caché una vez que vuelva a conectar el contexto. Por lo tanto, el tiempo de SO y los conteos de cambio de contexto tienen un valor mínimo.
Lo que REALMENTE es valioso es la proporción de suciedad de la línea de caché entre hilos. Dependiendo de la CPU, una línea de caché sucia seguida de una lectura de CPU par es más LENTA que una falta de caché, porque tienes que forzar a la CPU par a escribir su valor en la memoria principal antes de que puedas comenzar a leer. Algunos Las CPU le permiten extraer de las líneas de caché del mismo nivel sin tocar la memoria principal.
Así que la clave es la absolutamente minimizar cualquier estructuras modificadas de memoria compartida .. Hacer todo como de sólo lectura como sea posible .. Esto incluye la parte memorias intermedias FIFO (incluyendo piscinas ejecutor) .. A saber si se ha utilizado una cola sincronizada - luego cada sync-op es una región de memoria sucia compartida. Y más aún, si la tasa es lo suficientemente alta, probablemente activará una trampa del sistema operativo para detenerse, a la espera de mutex del hilo de igual.
Lo ideal es segmentar RAM, distribuir a un número fijo de trabajadores una sola unidad grande de trabajo, luego utilizar un bloqueo de cuenta regresiva u otra barrera de memoria (de modo que cada hilo solo lo toque una vez). Idealmente, cualquier búfer temporal se preasignan en lugar de entrar y salir de un grupo de memoria compartida (que a su vez causa la contención de la memoria caché). Los bloques "sincronizados" de Java aprovechan (detrás de las escenas) un espacio de memoria de tabla hash compartido y desencadenan así las lecturas sucias no deseadas, no he determinado si los objetos java 5 Lock lo evitan, pero todavía está aprovechando los puestos de OS que ganó No ayuda en su rendimiento. Obviamente, la mayoría de las operaciones de OutputStream desencadenan dichas llamadas sincronizadas (y, por supuesto, suelen llenar un búfer de transmisión común).
En general, mi experiencia es que el subproceso único es más rápido que el mulithreading para una matriz de bytes común/matriz de objetos, etc. Al menos con algoritmos de clasificación/filtrado simplistas con los que he experimentado. Esto es cierto tanto en Java como en C en mi experiencia. No he probado operaciones intuitivas de FPU (como divisiones, sqrt), donde las líneas de caché pueden ser menos importantes.
Básicamente, si usted es una sola CPU, no tiene problemas con la línea de caché (a menos que el sistema operativo siempre esté descargando la memoria caché incluso en subprocesos compartidos), pero la multitoma le compra menos que nada. En hyperthreading, es el mismo trato. En configuraciones de caché L2/L3 compartidas de una sola CPU (por ejemplo, AMD), es posible que encuentre algún beneficio. En el bus Intel de múltiples CPU, olvídalo: la memoria de escritura compartida es peor que la del subproceso único.
Si no está buscando diseñar una aplicación, sino simplemente medir la diferencia en el rendimiento (volviendo a leer su pregunta). Luego, con un poco de suerte, el algoritmo se puede dividir linealmente y luego pasar a una cantidad configurable de hilos, con 1 posiblemente una ruta de código alternativa especial. A continuación, ejecute cada uno (posiblemente teniendo pre-exit zip up/proc/self/*). También use registrar/informar el nano-tiempo del inicio/finalización de cada hilo (más bien el delta del mismo). –