Oltre ai punti menzionati nelle altre risposte (difficile dimostrare che le operazioni sono indipendenti e i programmatori pensano in serie), c'è un terzo fattore che deve essere considerato: il costo della parallelizzazione.
La verità è che il parallelismo dei thread ha costi molto significativi ad esso associati:
-
La creazione del thread è molto costoso: per il kernel, l'avvio di un thread equivale all'incirca all'avvio di un processo. Non sono sicuro dei costi precisi, ma credo che sia nell'ordine di dieci microsecondi.
-
La comunicazione del thread tramite mutex è costosa: in genere, richiede una chiamata di sistema su ciascun lato, probabilmente mettendo un thread in stop e risveglio, producendo latenza e cache fredde e TLB scaricati. In media, prendere e rilasciare un mutex costa circa un microsecondo.
Finora, tutto bene. Perché questo è un problema per il parallelismo implicito? Perché il parallelismo implicito è più facile da dimostrare sulle piccole scale. Una cosa è dimostrare che due iterazioni di un ciclo semplice sono indipendenti l'una dall'altra, è una cosa completamente diversa dimostrare che la stampa di qualcosa su stdout
e l'invio di una query a un database sono indipendenti l'una dall'altra e possono essere eseguite in parallelo (il processo del database potrebbe essere sull'altro lato del tubo!).
Cioè, il parallelismo implicito che un programma per computer può dimostrare è probabilmente non sfruttabile perché i costi della parallelizzazione sono maggiori del vantaggio dell'elaborazione parallela. D'altra parte, il parallelismo su larga scala che può davvero accelerare un'applicazione non è dimostrabile per un compilatore. Basti pensare a quanto lavoro può fare una CPU in un microsecondo. Ora, se la parallelizzazione dovrebbe essere più veloce del programma seriale, il programma parallelo deve essere in grado di mantenere tutte le CPU occupate per diversi microsecondi tra due chiamate mutex. Ciò richiede un parallelismo a grana grossa, quasi impossibile da provare automaticamente.
Infine, nessuna regola senza eccezione: lo sfruttamento del parallelismo implicito funziona dove non sono coinvolti i thread, come nel caso della vettorizzazione del codice (utilizzando set di istruzioni SIMD come AVX, Altivec, ecc.). Questo in effetti funziona meglio per il parallelismo su piccola scala che è relativamente facile da dimostrare.