Además de los puntos mencionados en las otras respuestas (difícil de demostrar que las operaciones son independientes y los programadores piensan en serie), hay un tercer factor que debe considerarse: el costo de la paralelización.
La verdad es que ese paralelismo de hilos tiene costos muy importantes asociados con él:
La creación de subprocesos es muy costosa: para el núcleo, iniciar un subproceso es casi lo mismo que iniciar un proceso. No estoy seguro de los costos precisos, pero creo que es del orden de diez microsegundos.
La comunicación de subprocesos a través de mutexes es costosa: por lo general, esto requiere una llamada al sistema en cada lado, posiblemente poniendo un subproceso en suspensión y volviéndolo a activar, lo que produce latencia, así como cachés fríos y TLB vacíos. En promedio, tomar y liberar un mutex cuesta alrededor de un microsegundo.
Hasta aquí todo bien. ¿Por qué es esto un problema para el paralelismo implícito? Porque el paralelismo implícito es más fácil de probar a pequeña escala. Una cosa es demostrar que dos iteraciones de un bucle simple son independientes entre sí, es completamente diferente probar que imprimir algo stdout
y enviar una consulta a una base de datos son independientes entre sí y pueden ejecutarse en paralelo ( ¡el proceso de la base de datos podría estar al otro lado de la tubería!).
Es decir, el paralelismo implícito que un programa de computadora puede probar es probablemente inexplorable porque los costos de la paralelización son mayores que la ventaja del procesamiento paralelo. Por otro lado, el paralelismo a gran escala que realmente puede acelerar una aplicación no es demostrable para un compilador. Solo piense en cuánto trabajo puede hacer una CPU en un microsegundo. Ahora, si se supone que la paralelización es más rápida que el programa en serie, el programa paralelo debe ser capaz de mantener ocupadas todas las CPU durante varios microsegundos entre dos llamadas mutex. Eso requiere un paralelismo de grano muy grueso, que es casi imposible de probar automáticamente.
Finalmente, ninguna regla sin una excepción: la explotación del paralelismo implícito funciona donde no hay hilos involucrados, como es el caso de la vectorización del código (usando conjuntos de instrucciones SIMD como AVX, Altivec, etc.). De hecho, eso funciona mejor para el paralelismo a pequeña escala que es relativamente fácil de probar.