Arhn - архитектура программирования

Хуже производительность при использовании подпрограмм go в параллельной реализации быстрой сортировки.

Примечание. Вопрос «Параллельный сегмент Go-lang работает медленнее, чем последовательный сегмент» касается условий гонки, у этого есть еще одна проблема, поэтому имхо это не дубликат.

Я пытаюсь найти объяснение следующей ситуации: выполнение параллельной быстрой сортировки приводит к значительному увеличению времени выполнения при использовании подпрограмм go.

Бенчмарки после кода:

package c9sort

import (
    "time"
)

var runInParllel bool

func Quicksort(nums []int, parallel bool) ([]int, int) {
    started := time.Now()
    ch := make(chan int)
    runInParllel = parallel

    go quicksort(nums, ch)

    sorted := make([]int, len(nums))
    i := 0
    for next := range ch {
        sorted[i] = next
        i++
    }
    return sorted, int(time.Since(started).Nanoseconds() / 1000000)
}

func quicksort(nums []int, ch chan int) {

    // Choose first number as pivot
    pivot := nums[0]

    // Prepare secondary slices
    smallerThanPivot := make([]int, 0)
    largerThanPivot := make([]int, 0)

    // Slice except pivot
    nums = nums[1:]

    // Go over slice and sort
    for _, i := range nums {
        switch {
        case i <= pivot:
            smallerThanPivot = append(smallerThanPivot, i)
        case i > pivot:
            largerThanPivot = append(largerThanPivot, i)
        }
    }

    var ch1 chan int
    var ch2 chan int

    // Now do the same for the two slices
    if len(smallerThanPivot) > 1 {
        ch1 = make(chan int, len(smallerThanPivot))
        if runInParllel {
            go quicksort(smallerThanPivot, ch1)
        } else {
            quicksort(smallerThanPivot, ch1)
        }
    }
    if len(largerThanPivot) > 1 {
        ch2 = make(chan int, len(largerThanPivot))
        if runInParllel {
            go quicksort(largerThanPivot, ch2)
        } else {
            quicksort(largerThanPivot, ch2)
        }
    }

    // Wait until the sorting finishes for the smaller slice
    if len(smallerThanPivot) > 1 {
        for i := range ch1 {
            ch <- i
        }
    } else if len(smallerThanPivot) == 1 {
        ch <- smallerThanPivot[0]
    }
    ch <- pivot

    if len(largerThanPivot) > 1 {
        for i := range ch2 {
            ch <- i
        }
    } else if len(largerThanPivot) == 1 {
        ch <- largerThanPivot[0]
    }

    close(ch)
}

Ориентиры для случайной перм 500000 целых чисел:

Пробежал 100 раз

Непараллельное среднее — 1866 мс

Параллельное среднее - 2437 мс

Любое объяснение будет оценено. Я знаю, что горутины не подходят для такого параллелизма, но я пытаюсь понять причину.

Заранее спасибо.



Ответы:


1

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

Для алгоритма «разделяй и властвуй», такого как быстрая сортировка, способы распараллеливания могут быть интересными. Как правило: когда вы рекурсивно, вы можете запустить задачу «сортировать половину массива» в горутине, если она достаточно велика. Часть «если он достаточно велик» — это то, как вы уменьшаете накладные расходы на координацию.

Более подробно это выглядит так:

  1. написать непараллельное qsort(data []int)

  2. измените qsort на необязательный элемент *sync.WaitGroup, который мы вызовем wg, чтобы сообщить, что это сделано. Добавьте if wg != nil { wg.Done() } перед каждым из его return.

  3. где qsort рекурсирует, проверьте, является ли половина данных, которые он собирается отсортировать, большой (скажем, более 500 элементов?), и

    • if it's large, start another task: wg.Add(1); go qsort(half, wg)
    • если нет, сортируй здесь и сейчас: qsort(half, nil)
  4. оберните qsort, чтобы скрыть параллельный механизм от пользователя: например, quicksort(data) сделайте wg := new(sync.WaitGroup), выполните начальное wg.Add(1), вызовите qsort(data, wg) и выполните wg.Wait(), чтобы дождаться завершения всех фоновых сортировок.

Это не оптимальная стратегия, потому что она продолжает создавать новые горутины даже после того, как задач достаточно, чтобы ваши ядра были заняты. И можно многое настроить вокруг того, как вы передаете некоторые задачи в фоновый режим. Но важно то, что использование другого потока только для больших подзадач — это способ распараллелить быструю сортировку, не отвлекаясь на координацию.

Вот демонстрация с до неприличия небрежной быстрой сортировкой (вы запускали ее локально, чтобы узнать время) -- хорошая вероятность ошибок, определенно квадратична для уже отсортированных данных; суть в том, чтобы донести идею параллельного принципа «разделяй и властвуй».

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

(И если вы не установили GOMAXPROCS, установите что-то вроде runtime.GOMAXPROCS(runtime.NumCPU()) в main.)

Наконец, посмотрите на пакет сортировки Go. Кода много, но он достаточно понятен, и как только вы его получите, вы сможете проводить свои эксперименты с «настоящей», детализированной реализацией сортировки.

20.03.2015
  • Проверив все, причина была достаточно проста и не в самом алгоритме. Кроме того, причина, по которой я не использую пакет sort, заключается в том, что я даю курс Go, и это одна из проблем. Я хотел понять проблему для класса. 21.03.2015

  • 2

    Оказалось, это очень просто. Поскольку я на новой машине, переменная GOMAXPROCS не была установлена.

    Новый тест, как и предполагалось, поддерживает параллельную реализацию: установите удвоенное количество ядер:

    Using 16 goroutines
    Ran 100 times
    
    Non parallel average - 1980
    Parallel average - 1133
    

    Установите количество ядер:

    Using 8 goroutines
    Ran 100 times
    
    Non parallel average - 2004
    Parallel average - 1197
    

    Кстати, это довольно последовательно. Среднее значение для двойного количества ядер всегда немного лучше.

    Ориентир для большей коллекции (1000000):

    Using 8 goroutines
    Ran 100 times
    
    Non parallel average - 3748
    Parallel average - 2265
    

    С двойным:

    Using 16 goroutines
    Ran 100 times
    
    Non parallel average - 3817
    Parallel average - 2012
    
    21.03.2015
  • Вы не хотите просто всегда слепо устанавливать GOMAXPROCS. Установите его соответствующим образом для каждой программы, или для некоторых программ может быть уместно использовать runtime.GOMAXPROCS() ( возможно, через флаг -cpu или -ncpu вашей программы). 21.03.2015
  • Я не уверен, что согласен, так как этот вызов исчезнет, ​​когда планировщик улучшит runtime.GOMAXPROCS() . В любом случае, дело было в том, что значение по умолчанию вообще не имеет значения. 21.03.2015
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..