Я экспериментировал с параллелизмом языка Go и обнаружил нечто, что мне не совсем понятно.
Я написал параллельное умножение матриц, то есть каждая задача вычисляет одну строку матрицы произведения, перемножая соответствующие строки и столбцы исходной матрицы.
Вот программа на Java
public static double[][] parallelMultiply(int nthreads, final double[][] m1, final double[][] m2) {
final int n = m1.length, m = m1[0].length, l = m2[0].length;
assert m1[0].length == m2.length;
double[][] r = new double[n][];
ExecutorService e = Executors.newFixedThreadPool(nthreads);
List> results = new LinkedList>();
for (int ii = 0; ii < n; ++ii) {
final int i = ii;
Future result = e.submit(new Callable() {
public double[] call() throws Exception {
double[] row = new double[l];
for (int j = 0; j < l; ++j) {
for (int k = 0; k < m; ++k) {
row[j] += m1[i][k]*m2[k][j];
}
}
return row;
}
});
results.add(result);
}
try {
e.shutdown();
e.awaitTermination(1, TimeUnit.HOURS);
int i = 0;
for (Future result : results) {
r[i] = result.get();
++i;
}
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
return r;
}
, а это программа на Go
type Matrix struct {
n, m int
data [][]float64
}
func New(n, m int) *Matrix {
data := make([][]float64, n)
for i, _ := range data {
data[i] = make([]float64, m)
}
return &Matrix{n, m, data}
}
func (m *Matrix) Get(i, j int) float64 {
return m.data[i][j]
}
func (m *Matrix) Set(i, j int, v float64) {
m.data[i][j] = v
}
func MultiplyParallel(m1, m2 *Matrix) *Matrix {
r := New(m1.n, m2.m)
c := make(chan interface{}, m1.n)
for i := 0; i < m1.n; i++ {
go func(i int) {
innerLoop(r, m1, m2, i)
c <- nil
}(i)
}
for i := 0; i < m1.n; i++ {
<-c
}
return r
}
func innerLoop(r, m1, m2 *Matrix, i int) {
for j := 0; j < m2.m; j++ {
s := 0.0
for k := 0; k < m1.m; k++ {
s = s + m1.Get(i, k) * m2.Get(k, j)
}
r.Set(i, j, s)
}
}
Когда я использую программу на Java с nthreads=1 и nthreads=2, на моем двухъядерном нетбуке N450 Atom происходит почти двукратное ускорение. Когда я использую программу Go с GOMAXPROCS=1 и GOMAXPROCS=2, ускорения вообще нет!
Несмотря на то, что код Java использует дополнительное хранилище для Future
s, а затем собирает их значения в результирующую матрицу вместо прямого обновления массива в рабочем коде (это то, что делает версия Go), он выполняет намногобыстрее на нескольких ядрах, чем версия Go.
Особенно забавно, что версия Go с GOMAXPROCS=2 загружает оба ядра (htop показывает 100% загрузку обоих процессоров при работе программы), но время вычислений такое же, как и при GOMAXPROCS=1 (htop показывает только 100% загрузку на одно ядро в этом случае).
Другая проблема заключается в том, что программа на Java быстрее программы на Go даже при простом однопоточном умножении, но это не совсем неожиданно (принимая во внимание тесты из здесь) и не должно влиять на множитель многоядерной производительности.
Что я здесь делаю неправильно? Есть ли способ ускорить программу Go?
УПД:
кажется, я нашел, что я делаю неправильно. Я проверял время Java-программы, используя System.currentTimeMillis ()
, и программу Go, используя time
команду оболочки. Я ошибочно принял «пользовательское» время из вывода zsh за время работы программы, а не за «общее». Теперь я перепроверил скорость вычислений, и она тоже дает мне почти двукратное ускорение (хотя оно немного меньше, чем у Java):
% time env GOMAXPROCS=2 ./4-2-go -n 500 -q
env GOMAXPROCS=2 ./4-2-go -n 500 -q 22,34s user 0,04s system 99% cpu 22,483 total
% time env GOMAXPROCS=2 ./4-2-go -n 500 -q -p
env GOMAXPROCS=2 ./4-2-go -n 500 -q -p 24,09s user 0,10s system 184% cpu 13,080 total
Кажется, мне нужно быть более внимательным.
Еще java программа дает в пять раз меньше времени на один и тот же случай. Но это вопрос другого вопроса, я думаю.