Во-первых, если в программе не создано несколько потоков, то в этой программе есть только один поток выполнения.
То, что 25% ресурсов ЦП используется для программы, свидетельствует о том, что одно ядро из четырех используется на 100%, но все остальные ядра не используются. Если бы использовались все ядра, то теоретически процесс мог бы использовать 100% ресурсов ЦП.
В качестве примечания, графики, показанные в диспетчере задач в Windows, показывают загрузку ЦП всеми процессами, запущенными в данный момент, а не только для одного процесса.
Во-вторых, код, который вы представляете, может быть разбит на код, который может выполняться в двух отдельных потоках для выполнения в двух ядрах. Я предполагаю, что вы хотите показать, что a
и b
не зависят друг от друга и зависят только от i
. В такой ситуации разделение внутри цикла for
, как показано ниже, может позволить многопоточную работу, которая может привести к повышению производительности:
// Process this in one thread:
for (int i = 0; i < 1000; i++) {
a = i * 2;
}
// Process this in another thread:
for (int i = 0; i < 1000; i++) {
b = i + 1;
}
Однако, что становится хитрым, так это то, что должно быть время, когда необходимо оценить результаты двух отдельных потоков, что, как кажется, подразумевается в последующем утверждении if
:
for (i = 0; i < 1000; i++) {
// manipulate "a" and "b"
if (a == ... || b == ...) { ... }
}
Для этого потребуется поиск значений a
и b
, которые находятся в отдельных потоках (которые выполняются на отдельных процессорах), что является серьезной головной болью.
Нет реальной хорошей гарантии, что значения i
двух потоков одинаковы в одно и то же время (в конце концов, умножение и сложение, вероятно, будут выполняться разное количество раз), а это означает, что один поток может потребоваться дождаться, пока другие значения i
синхронизируются, прежде чем сравнивать a
и b
, которые соответствуют зависимому значению i
. Или мы создаем третий поток для сравнения значений и синхронизации двух потоков? В любом случае сложность начинает накапливаться очень быстро, поэтому я думаю, что мы можем согласиться с тем, что мы начинаем сталкиваться с серьезным беспорядком - разделение состояний между потоками может быть очень сложным.
Поэтому приведенный вами пример кода можно распараллелить только частично без особых усилий, однако, как только возникает необходимость сравнить две переменные, разделение этих двух операций становится очень трудным очень быстро.
Несколько правил, когда речь идет о параллельном программировании:
Если есть задачи, которые можно разбить на части, включающие обработку данных, полностью независимую от других данных и их результатов (состояний), то распараллеливание может быть очень простым.
Например, две функции, которые вычисляют значение из ввода (в псевдокоде):
f(x) = { return 2x }
g(x) = { return x+1 }
Эти две функции не зависят друг от друга, поэтому они могут выполняться параллельно без какой-либо боли. Кроме того, поскольку они не являются состояниями для совместного использования или обработки между вычислениями, даже если было несколько значений x
, которые необходимо вычислить, даже эти можно разделить дальше:
x = [1, 2, 3, 4]
foreach t in x:
runInThread(f(t))
foreach t in x:
runInThread(g(t))
Теперь в этом примере мы можем иметь 8 отдельных потоков, выполняющих вычисления. Отсутствие побочных эффектов может быть очень хорошей вещью для параллельного программирования.
Однако, как только возникает зависимость от данных и результатов других вычислений (что также означает наличие побочных эффектов), распараллеливание становится чрезвычайно трудным. Во многих случаях эти типы проблем должны выполняться последовательно, поскольку они ожидают результатов других вычислений, которые будут возвращены.
Возможно, вопрос сводится к тому, почему компиляторы не могут определить части, которые можно автоматически распараллелить, и выполнить эти оптимизации? Я не специалист по компиляторам, поэтому не могу сказать, но в Википедии есть статья о автоматической парализации , которая может содержать некоторую информацию.