Параллельная версия намного медленнее, чем серийная в golang - PullRequest
0 голосов
/ 09 марта 2020

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

Чтобы убедиться, что эффект параллелизма заметен, я тестирую код, используя список из 12 миллионы точек.

Детали моего процессора:

  • Название модели: Intel® Core ™ (TM) i5-4210U CPU @ 1.70GHz
  • CPU (s) ): 4

Вот две версии:

Общая часть:

type Point struct {
    X float64
    Y float64
}

func dist(p, q Point) float64 {
    return math.Sqrt(math.Pow(p.X-q.X,2)+math.Pow(p.Y-q.Y,2))
}

Последовательная функция:

func s_argmin(p Point, points_list []Point, i,j int)(int){
    best := 0
    d := dist(p, points_list[0])
    var new_d float64
    for k:=i;k<j+1;k++{
        new_d = dist(p, points_list[k])
        if new_d < d{
            d = new_d
            best = k
        }
    }
    return best
}

Параллельная функция :

func p_argmin(p Point, points_list []Point, i,j int)(int){
    if i==j{
        return i
    }else{
        mid := int((i+j)/2)
        var argmin1, argmin2 int
        c1 := make(chan int)
        c2 := make(chan int)
        go func(){
            c1 <- p_argmin(p, points_list, i, mid)
        }()
        go func(){
            c2 <- p_argmin(p, points_list, mid+1, j)
        }()
        argmin1 = <- c1
        argmin2 = <- c2
        close(c1)
        close(c2)
        if dist(p,points_list[argmin1])<dist(p,points_list[argmin2]){
            return argmin1
        }else{
            return argmin2
        }
    }
}

Я также попытался ограничить параллелизм с помощью оптимизированной функции, которая выполняет параллельную версию функции только тогда, когда размер ввода (ji) больше значения, но последовательная версия всегда быстрее.

Как улучшить результат параллельной версии?

Ответы [ 2 ]

2 голосов
/ 09 марта 2020

Бессмысленные микробенчмарки дают бессмысленные результаты.


Я не вижу причин полагать, что рекурсивный p_argmin может быть быстрее, чем s_argmin.

$ go test micro_test.go -bench=. -benchmem
goos: linux
goarch: amd64
BenchmarkS-4      946197          1263 ns/op           0 B/op          0 allocs/op
--- BENCH: BenchmarkS-4
    micro_test.go:81: 1 946197 946197
BenchmarkP-4        3477        302076 ns/op       80958 B/op        843 allocs/op
--- BENCH: BenchmarkP-4
    micro_test.go:98: 839 2917203 3477
$ 

micro_test.go:

package main

import (
    "math"
    "sync"
    "testing"
)

type Point struct {
    X float64
    Y float64
}

func dist(p, q Point) float64 {
    //return math.Sqrt(math.Pow(p.X-q.X, 2) + math.Pow(p.Y-q.Y, 2))
    return math.Sqrt((p.X-q.X)*(p.X-q.X) + (p.Y-q.Y)*(p.Y-q.Y))
}

func s_argmin(p Point, points_list []Point, i, j int) int {
    mbm.Lock()
    nbm++
    mbm.Unlock()

    best := 0
    d := dist(p, points_list[0])
    var new_d float64
    for k := i; k < j+1; k++ {
        new_d = dist(p, points_list[k])
        if new_d < d {
            d = new_d
            best = k
        }
    }
    return best
}

func p_argmin(p Point, points_list []Point, i, j int) int {
    mbm.Lock()
    nbm++
    mbm.Unlock()

    if i == j {
        return i
    }
    mid := int((i + j) / 2)
    var argmin1, argmin2 int
    c1 := make(chan int)
    c2 := make(chan int)
    go func() {
        c1 <- p_argmin(p, points_list, i, mid)
    }()
    go func() {
        c2 <- p_argmin(p, points_list, mid+1, j)
    }()
    argmin1 = <-c1
    argmin2 = <-c2
    if dist(p, points_list[argmin1]) < dist(p, points_list[argmin2]) {
        return argmin1
    }
    return argmin2
}

var (
    nbm int
    mbm sync.Mutex
)

func BenchmarkS(b *testing.B) {
    mbm.Lock()
    nbm = 0
    mbm.Unlock()

    points := make([]Point, 420)
    b.ResetTimer()
    for N := 0; N < b.N; N++ {
        s_argmin(points[0], points, 0, len(points)-1)
    }
    b.StopTimer()

    mbm.Lock()
    b.Log(float64(nbm)/float64(b.N), nbm, b.N)
    mbm.Unlock()
}

func BenchmarkP(b *testing.B) {
    mbm.Lock()
    nbm = 0
    mbm.Unlock()

    points := make([]Point, 420)
    b.ResetTimer()
    for N := 0; N < b.N; N++ {
        p_argmin(points[0], points, 0, len(points)-1)
    }
    b.StopTimer()

    mbm.Lock()
    b.Log(float64(nbm)/float64(b.N), nbm, b.N)
    mbm.Unlock()
}
0 голосов
/ 09 марта 2020

Стоимость имеет значение (много) может Попробуйте онлайн

Чистый [SERIAL] поток кода- выполнение показывает незначительную стоимость оцениваемого расстояния на точку. Требуется , но примерно 36 [ns] за Point

//  ... The   [SERIAL] flow of code-execution took      77.095 µs for       [10]
//  --------^^^^^^^^^^------------------------------------|---------------------
//  ... The [PARALLEL] flow of code-execution took     142.563 µs for       [10] Points
//  ... The [PARALLEL] flow of code-execution took     386.27  µs for      [100] Points
//  ... The [PARALLEL] flow of code-execution took    4260.941 µs for     [1000] Points
//  ... The [PARALLEL] flow of code-execution took   31455.29  µs for    [10000] Points
//  ... The   [SERIAL] flow of code-execution took     591.604 µs for    [10000] Points
//  ... The [PARALLEL] flow of code-execution took  391694.389 µs for   [100000] Points
//  ... The   [SERIAL] flow of code-execution took    6425.999 µs for   [100000] Points
//  ... The [PARALLEL] flow of code-execution took 2807615.771 µs for  [1000000] Points
//  ... The   [SERIAL] flow of code-execution took   64596.044 µs for  [1000000] Points
//                                                 |  |  | ... ns   
//                                                 |  |  +____ µs
//                                                 |  +_______ ms
//                                                 +__________  s

Учитывая это, затраты на создание go -параллельного потока выполнения (разделить-и-завоевать ) накапливаются такие огромные накладные расходы, что вряд ли они оправдаются для любых разумно используемых []Point размеров.

Даже для больших []Point размеров самые накладные расходы здесь вызывают ~ 2807 [ns] ~ 78 x медленная обработка за Point (верно из-за неправильной конструкции cost_of_computing / cost_of_overheads .

Пересмотренная строгая накладная нагрузка Аргумент Амдала (не оригинал) действителен здесь (оригинальная формулировка не заставляла людей принимать также скрытое дополнение накладные расходы , и любители часто склонны искажать ожидания ускорения)

func SERIAL( aPointToSEEK Point, aListOfPOINTs []Point ){

   defer TimeTRACK(  time.Now(), "The [SERIAL] flow of code-execution", len( aListOfPOINTs ) )
//   
//            2020/03/09 07:17:54 The [SERIAL] flow of code-execution took    120.529 µs for        [1]
//            2020/03/09 07:17:28 The [SERIAL] flow of code-execution took    194.565 µs for       [10]
//            2020/03/09 07:11:28 The [SERIAL] flow of code-execution took     77.095 µs for      [100]
//            2020/03/09 07:12:16 The [SERIAL] flow of code-execution took    260.771 µs for     [1000]
//            2020/03/09 07:13:19 The [SERIAL] flow of code-execution took    591.604 µs for    [10000]
//            2020/03/09 07:13:57 The [SERIAL] flow of code-execution took   4585.917 µs for   [100000]
//            2020/03/09 07:14:33 The [SERIAL] flow of code-execution took  44317.063 µs for  [1000000]
//            2020/03/09 07:10:30 The [SERIAL] flow of code-execution took  36141.75  µs for  [1000000]
//            2020/03/09 07:15:10 The [SERIAL] flow of code-execution took 554986.415 µs for [10000000]
//            2020/03/09 07:24:10 The [SERIAL] flow of code-execution took 676098.025 µs for [10000000]
//                                                                        |  |  | ... ns   
//                                                                        |  |  +____ µs
//                                                                        |  +_______ ms
//                                                                        +__________  s

   log.Printf(       "%s got nearest aPointID# %d", "The [SERIAL] flow of code-execution", s_argmin( aPointToSEEK, aListOfPOINTs, 0, len( aListOfPOINTs ) - 1 ) )
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...