Почему наличие предложения по умолчанию в выборе goroutine делает его медленнее? - PullRequest
0 голосов
/ 03 июня 2018

Обращаясь к следующим тестовым кодам бенчмаркинга:

func BenchmarkRuneCountNoDefault(b *testing.B) {
    b.StopTimer()
    var strings []string
    numStrings := 10
    for n := 0; n < numStrings; n++{
        s := RandStringBytesMaskImprSrc(10)
        strings = append(strings, s)
    }
    jobs := make(chan string)
    results := make (chan int)

    for i := 0; i < runtime.NumCPU(); i++{
        go RuneCountNoDefault(jobs, results)
    }
    b.StartTimer()

    for n := 0; n < b.N; n++ {
        go func(){
            for n := 0; n < numStrings; n++{
                <-results
            }
            return
        }()

        for n := 0; n < numStrings; n++{
            jobs <- strings[n]
        }
    }

    close(jobs)
}

func RuneCountNoDefault(jobs chan string, results chan int){
    for{
        select{
        case j, ok := <-jobs:
            if ok{
                results <- utf8.RuneCountInString(j)
            } else {
                return
            }
        }
    }
}

func BenchmarkRuneCountWithDefault(b *testing.B) {
    b.StopTimer()
    var strings []string
    numStrings := 10
    for n := 0; n < numStrings; n++{
        s := RandStringBytesMaskImprSrc(10)
        strings = append(strings, s)
    }
    jobs := make(chan string)
    results := make (chan int)

    for i := 0; i < runtime.NumCPU(); i++{
        go RuneCountWithDefault(jobs, results)
    }
    b.StartTimer()

    for n := 0; n < b.N; n++ {
        go func(){
            for n := 0; n < numStrings; n++{
                <-results
            }
            return
        }()

        for n := 0; n < numStrings; n++{
            jobs <- strings[n]
        }
    }

    close(jobs)
}


func RuneCountWithDefault(jobs chan string, results chan int){
    for{
        select{
        case j, ok := <-jobs:
            if ok{
                results <- utf8.RuneCountInString(j)
            } else {
                return
            }
        default: //DIFFERENCE
        }
    }
}

//https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
    letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

var src = rand.NewSource(time.Now().UnixNano())

func RandStringBytesMaskImprSrc(n int) string {
    b := make([]byte, n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)
}

Когда я сравнил обе функции, в которых одна функция, RuneCountNoDefault не имеет предложения default в select, а другая, RuneCountWithDefault имеет предложение default, я получаю следующий тест:

BenchmarkRuneCountNoDefault-4             200000              8910 ns/op
BenchmarkRuneCountWithDefault-4                5         277798660 ns/op

Проверяя cpuprofile, сгенерированный тестами, я заметил, что функция с предложением default тратит многовремя в следующих операциях канала:

enter image description here

Почему наличие условия по умолчанию в goroutine select делает его медленнее?

Iиспользую Go версию 1.10 для windows/amd64

1 Ответ

0 голосов
/ 03 июня 2018

Спецификация языка программирования Go

Выбор операторов

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


Изменение вашего эталонного теста для подсчета количества выполненных и принятых по умолчанию случаев:

$ go test default_test.go -bench=.
goos: linux
goarch: amd64
BenchmarkRuneCountNoDefault-4         300000          4108 ns/op
BenchmarkRuneCountWithDefault-4           10     209890782 ns/op
--- BENCH: BenchmarkRuneCountWithDefault-4
    default_test.go:90: proceeds 114
    default_test.go:91: defaults 128343308
$ 

В то время как другие случаи не могли продолжаться, случай по умолчанию был взят 128343308 раз за 209422470, (209890782 - 114 * 4108), наносекунды или 1,63 наносекунды за случай по умолчанию.Если вы делаете что-то небольшое большое количество раз, оно складывается.


default_test.go:

package main

import (
    "math/rand"
    "runtime"
    "sync/atomic"
    "testing"
    "time"
    "unicode/utf8"
)

func BenchmarkRuneCountNoDefault(b *testing.B) {
    b.StopTimer()
    var strings []string
    numStrings := 10
    for n := 0; n < numStrings; n++ {
        s := RandStringBytesMaskImprSrc(10)
        strings = append(strings, s)
    }
    jobs := make(chan string)
    results := make(chan int)

    for i := 0; i < runtime.NumCPU(); i++ {
        go RuneCountNoDefault(jobs, results)
    }
    b.StartTimer()

    for n := 0; n < b.N; n++ {
        go func() {
            for n := 0; n < numStrings; n++ {
                <-results
            }
            return
        }()

        for n := 0; n < numStrings; n++ {
            jobs <- strings[n]
        }
    }

    close(jobs)
}

func RuneCountNoDefault(jobs chan string, results chan int) {
    for {
        select {
        case j, ok := <-jobs:
            if ok {
                results <- utf8.RuneCountInString(j)
            } else {
                return
            }
        }
    }
}

var proceeds ,defaults uint64

func BenchmarkRuneCountWithDefault(b *testing.B) {
    b.StopTimer()
    var strings []string
    numStrings := 10
    for n := 0; n < numStrings; n++ {
        s := RandStringBytesMaskImprSrc(10)
        strings = append(strings, s)
    }
    jobs := make(chan string)
    results := make(chan int)

    for i := 0; i < runtime.NumCPU(); i++ {
        go RuneCountWithDefault(jobs, results)
    }
    b.StartTimer()

    for n := 0; n < b.N; n++ {
        go func() {
            for n := 0; n < numStrings; n++ {
                <-results
            }
            return
        }()

        for n := 0; n < numStrings; n++ {
            jobs <- strings[n]
        }
    }

    close(jobs)

    b.Log("proceeds", atomic.LoadUint64(&proceeds))
    b.Log("defaults", atomic.LoadUint64(&defaults))

}

func RuneCountWithDefault(jobs chan string, results chan int) {
    for {
        select {
        case j, ok := <-jobs:

            atomic.AddUint64(&proceeds, 1)

            if ok {
                results <- utf8.RuneCountInString(j)
            } else {
                return
            }
        default: //DIFFERENCE

            atomic.AddUint64(&defaults, 1)

        }
    }
}

//https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
    letterIdxBits = 6                    // 6 bits to represent a letter index
    letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
    letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

var src = rand.NewSource(time.Now().UnixNano())

func RandStringBytesMaskImprSrc(n int) string {
    b := make([]byte, n)
    // A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
    for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
        if remain == 0 {
            cache, remain = src.Int63(), letterIdxMax
        }
        if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
            b[i] = letterBytes[idx]
            i--
        }
        cache >>= letterIdxBits
        remain--
    }

    return string(b)
}

Детская площадка: https://play.golang.org/p/DLnAY0hovQG

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...