четверг, 31 января 2019 г.

Эффективный Go: распараллеливание вычислений

Одно из применений идей, описанных в предыдущих постах о go-процедурах и каналах - это распараллеливание вычисления по нескольким ядрам процессора. Если вычисление можно разбить на отдельные части, которые могут выполняться независимо, тогда вычисление может быть распараллелено, с использованием каналов, чтобы сигнализировать, когда каждая часть вычисления завершается.

Допустим, у нас есть дорогая операция для вектора элементов, и что значение операции для каждого элемента является независимым, как в следующем идеализированном примере.

type Vector []float64

// Применяеи операцию к v[i], v[i+1] ... до v[n-1].
func (v Vector) DoSome(i, n int, u Vector, c chan int) {
    for ; i < n; i++ {
        v[i] += u.Op(v[i])
    }
    c <- 1    // сигнализируем что эта часть выполнена
}

Мы запускаем части вычисления независимо друг от друга, по одному на каждом ядре процессора. Они могут быть выполнены в любом порядке, но это не имеет значения; мы только считаем сигналы завершения, опустошив канал после запуска всех go-процедур.

const numCPU = 4 // количество ядер процессора

func (v Vector) DoAll(u Vector) {

    // Буферизация необязательна, но разумна
    c := make(chan int, numCPU)  
    for i := 0; i < numCPU; i++ {
        go v.DoSome(i*len(v)/numCPU, 
                   (i+1)*len(v)/numCPU, u, c)
    }
    // Опустошаем канал
    for i := 0; i < numCPU; i++ {
        <-c    // ждем завершения одного задания
    }
    // Все выполнено
}

Вместо того, чтобы создавать постоянное значение для numCPU, мы можем спросить среду выполнения, какое значение уместно. Функция runtime.NumCPU возвращает количество аппаратных ядер процессора на машине, таким образом мы можем написать:

var numCPU = runtime.NumCPU()

Также есть функция runtime.GOMAXPROCS, которая сообщает (или устанавливает) указанное пользователем количество ядер, которое может запустить Go программа одновременно. По умолчанию используется значение runtime.NumCPU, но оно может быть переопределено установкой одноименной переменной среды оболочки или вызывом этой функции с положительным номером. Вызов этой функции с нолем просто запрашивает значение. Поэтому, если мы хотим удовлетворить запрос ресурса пользователя, мы должны написать:

var numCPU = runtime.GOMAXPROCS(0)

Убедитесь, что не путаете идеи параллелизма - структурирования программы как независимо выполняемых компонентов - и параллелизма - выполнения параллельных вычислений для повышения эффективности на нескольких ядрах процессора. Хотя функции параллелизма Go могут облегчить некоторые проблемы структурирования как параллельных вычислений, Go - это многопоточный язык, не параллельный, и не все проблемы распараллеливания соответствуют модели Go.


Читайте также:


Комментариев нет:

Отправить комментарий