Сложение элементов двух массивов

у вас есть два массива

int[] a = {......} // Total elements 1 million
int[] b = {......} // Total elements 1 million , Length is same for both arrays.

Q1. Я должен создать массив

int[] c 

элементы которого являются суммой соответствующих индексов A[] и b [] Бывший.

 c[0] = a[0] + b[0];
 c[1] = a[1] + b[1];
 c[2] = a[2] + b[2];

решение: - я могу воспользоваться преимуществами многопоточности. Разделите весь массив на 10 или более частей и назначьте каждый сегмент потоку для выполнения вычисления. Примечание - > интервьюер предложил использовать многопоточность

Q2. Теперь его немного изменили.Элементы массива с имейте такие значения: ->

c[0] = a[0] + b[0];
c[1] = a[1] + b[1] + c[0]; // At this line c[0] is Sum of a[0] + b[0]; The Above Line
c[2] = a[2] + b[2] + c[1]; // At this line c[0] is Sum of a[1] + b[1]+ a[0] + b[0]; The Above Line

MySolution - > решить часть 1 (Q1) и создать временный массив, и после этого мы должны выполнить сложение, как это.

C[1] = temp[1]+temp[0]
C[2] = temp[2]+temp[1]

Примечание: - > нам действительно не нужен temp[], мы можем сделать это только с помощью массива c также. Просто объяснить это на так легко.

5 ответов


вы можете сделать часть 1 многопоточный, как вы говорите, давая -

temp[0] = a[0] + b[0];
temp[1] = a[1] + b[1];
temp[2] = a[2] + b[2];
etc....

тогда расчет для части 2 становится -

c[0] = temp[0];
c[1] = temp[1] + temp[0];
c[2] = temp[2] + temp[1] + temp[0];
c[3] = temp[3] + temp[2] + temp[1] + temp[0];
etc...

хотя это выглядит последовательным и невозможно распараллелить, на самом деле это довольно распространенная операция, называемая "суммой префикса" или "сканированием". Дополнительные сведения, в том числе о распараллеливании, см. В разделе Википедия или Blelloch.

в случае 8 элементов это становится следующим, где каждая рекурсивная фаза может быть распараллелен, поскольку каждый расчет не зависит от других вычислений на том же этапе.

// 1st phase 
u[0] = temp[0] + temp[1];
u[1] = temp[2] + temp[3];
u[2] = temp[4] + temp[5];
u[3] = temp[6] + temp[7];

// 2nd phase
v[0] = u[0] + u[1];
v[1] = u[2] + u[3];

// 3rd phase
w[0] = v[0] + v[1];

// final phase
c[0] = temp[0];
c[1] = u[0];
c[2] = u[0] + temp[2];
c[3] = v[0];
c[4] = v[0] + temp[4];
c[5] = v[0] + u[2];
c[6] = v[0] + u[2] + temp[6];
c[7] = w[0];

на мой взгляд, для второго вопроса у вас есть две техники:

сначала нужно сделать два шага. Шаг 1 с помощью потоков вы можете добавить

 c[0] = a[0] + b[0];
 c[1] = a[1] + b[1];
 c[2] = a[2] + b[2];

как вы предложили.

но Шаг-2 должен выполняться последовательно. потому что c[ i + 1] значение зависит от обновленного значения c [i]

вторая техника немного сложна, но может быть быстрой.

то, что вас просят сделать во втором вопросе, - это сделать что-то вроде:

 c[0] = a[0] + b[0];
 c[1] = a[1] + b[1] + a[0] + b[0];
 c[2] = a[2] + b[2] + a[1] + b[1] + a[0] + b[0];

это может быть параллельно.

c[i] =  thread1( sum(a[0]...a[i] )) + thread2( sum(b[0]...b[i] ))
         for i >= 0

хорошо в этой технике, вы можете рассчитать c[i] параллельно для всех i (два уровня резьбовые модели).

вы можете дополнительно улучшить функции thread1 / thread2 как многопоточные с дочерними потоками для выполнения sum – но помните, что когда-то многопоточный код работает медленно, а затем однопоточный код из-за времени переключения контекста потока (поэтому вы должны дать достаточное количество задач каждому потоку).

точка, которая не похожа на вторую технику, - это " большая часть того, что потоки для c[i] сделать то же самое, что темы для c[i-1] сделать так же".
Благодаря @jogojapan чтобы сообщить мне об этом недостатке.

для улучшения техники чтения обновляется!--33-->ответ г-н Jogojapan.


На самом деле вы можете использовать многопоточность в этой задаче. Ваш алгоритм будет состоять из двух частей:

  1. применить алгоритм Q1 - эта часть будет использовать преимущество многопоточности.

  2. просто в одном цикле примените formyla:c[n] = c[n] + c[n-1], n=1...999999.


вы можете использовать многопоточность здесь, на пути от 1-го вопроса. Я имею в виду, вы можете рассчитать

sumA0B0 = a[0] + b[0];

в отдельных потоках и даже ждать расчета (синхронизация ie на[i]). Затем в отдельном потоке вы можете вычислить c[i] = sumAiBi + c[i-1];


один из способов использования многопоточности для Q2-сделать это за два прохода (каждый с использованием T потоков внутри, где T можно выбрать свободно):

  1. вычислить c[i] = a[i] + b[i] + c[i-1] для всех ячеек обычным многопоточным способом, т. е. после разделения входных данных на диапазоны [0, r1), [r1, r2),... [rk, n) и применение одного потока к каждому диапазону. Да, это будет неверно для всех диапазонов, кроме первого, и Шаг 2 исправит его.

  2. вычислить исправления, опять же многопоточным способом. Для этого мы ищем самое правильное значение каждого диапазона, т. е. corr1:=c[r1-1], corr2:=corr1+c[r2-1], corr3:=corr2+c[r3-1] etc., что дает нам исправления стоимостью для каждого потока, а затем вычислить, снова используя многопоточность с теми же диапазонами, что и раньше, c[i] += corrk здесь corrk - это корректирующее значение для конкретного потока для k-го потока. (Для нулевого потока мы можем использовать corr0:=0, так что поток не нужно делать что угодно.)

это улучшает теоретическое время выполнения фактором T, где T-количество потоков (которые могут быть выбраны свободно), поэтому это оптимальное решение в отношении многопоточности.


чтобы проиллюстрировать, как это работает, вот пример, где мы предполагаем массивов длины n==30. Далее мы предполагаем, что используем 3 потока: один для вычисления диапазона c[0..9], для c[10..19] и один для c[20..29].

ясно, что цель состоит в том, что в ячейке c[i], для любого 0<i<n, мы получим

c[i] == a[0]+...+a[i]+b[0]+...+b[i]

(т. е. сумма всех a[0..i] и b[0..i]) после завершения алгоритма. Давайте посмотрим, как алгоритм туда попадает, например cell i==23. Эта ячейка обрабатывается третьим потоком, т. е. потоком, ответственным за диапазон c[20..29].

Шаг 1: наборы потоков

c[20] = a[20]+b[20]
c[21] = c[20]+a[21]+b[21] == a[20]+a[21]+b[20]+b[21]
c[22] = c[21]+a[22]+b[22] == a[20]+a[21]+a[22]+b[20]+b[21]+b[22]
c[23] = c[22]+a[23]+b[23] == a[20]+a[21]+a[22]+a[23]+b[20]+b[21]+b[22]+b[23]
...

так, после того как раздел 1 заканчивал, мы у некоторых из a[20..23] и b[20..23] в ячейке c[23]. Чего не хватает, так это суммы a[0..19] и b[0..19].

аналогично, первый и второй потоки установили значения в c[0..9] и c[10..19] такое, что

c[0] = a[0]+b[0]
c[1] = c[0]+a[1]+b[1] == a[0]+a[1]+b[0]+b[1]
...
c[9] = a[0]+...+a[9]+b[0]+...+b[9]

и

c[10] = a[10]+b[10]
...
c[19] = a[10]+...+a[19]+b[10]+...+b[19]

Шаг 2: корректирующее значение для третьего потока,corr2 - сумма corr1 и справа значение, вычисленное вторым потоком, в то время как corr1 является самым правильным значением, вычисленным первым нитка. Отсюда

corr2 == c[9]+c[19] == (a[0]+...+a[9]+b[0]+...+b[9]) + (a[10]+...+a[19]+b[10]+...+b[19])

и это действительно сумма, отсутствующая в значении c[23] после шага 1. На Шаге 2, мы добавляем это значение ко всем элементам c[20..29], следовательно, после завершения шага 2,c[23] правильно (как и все другие клетки).


причина, по которой это работает, заключается в том, что вычисление значений ячеек является строго инкрементной операцией слева направо, а порядок операций для вычисления одной ячейки не имеет значения (потому что + является ассоциативным и коммутативным). Следовательно, конечный результат ("самое правое значение") любого данного потока после шага 1 может быть использован для исправления результатов потоков, ответственных за диапазоны справа от него на Шаге 2.