Правильный способ оптимизации многопоточности с WaitGroups и goroutines? - PullRequest
0 голосов
/ 29 июня 2019

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

То, как я его сейчас настроил, начнется с высокой скоростью (около 11 000 итераций в секунду), но затем быстро сократится до 2000. Моя цель - гораздо большее число, чем даже 11 000. Кроме того, похоже, что функция infofunc(i) не справляется с быстрыми скоростями, и использование процедуры для этой функции вызывает наложение печати на консоль. Кроме того, он будет иногда использовать WaitGroup до возвращения Ожидания.

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

import (
    "fmt"
    "math/big"
    "os"
    "os/exec"
    "sync"
    "time"
)

var found = 0
var pages_queried = 0
var start_time = time.Now()
var bignum = new(big.Int)
var foundAddresses = 0
var wg sync.WaitGroup
var set = make(map[string]bool)
var addresses = []string{"6ab42gyr", "lo08n4g6"}

func main() {
    bignum.SetString("1000000000000000000000000000", 10)
    pick := os.Args[1]
    kpp := 128
    switch pick {
    case "btc":
        i := new(big.Int)
        i, ok := i.SetString(os.Args[2], 10)
        if ok {
            cmd := exec.Command("clear")
            cmd.Stdout = os.Stdout
            cmd.Run()
            for i.Cmp(bignum) < 0 {
                wg.Add(1)
                go func(i *big.Int) {
                    defer wg.Done()
                    go printKeys(i.String(), kpp)
                    i.Add(i, big.NewInt(1))
                    pages_queried += 1
                    infofunc(i)
                }(i)
                wg.Wait()
            }
        }
    }
}

func infofunc(i *big.Int) {
    elapsed := time.Now().Sub(start_time)
    duration, _ := time.ParseDuration(elapsed.String())
    duration2 := int(duration.Seconds())
    if duration2 != 0 {
        fmt.Printf("\033[5;0H")
        fmt.Printf("Started at %s. Found: %d. Elapsed: %s. Queried: %d pages. Current page: %s. Rate: %d/s", start_time.String(), found, elapsed.String(), pages_queried, i.String(), (pages_queried / duration2))
    }
}

func printKeys(pageNumber string, keysPerPage int) {
    keys := generateKeys(pageNumber, keysPerPage)
    length := len(keys)
    var addressesLen = len(addresses)

    for i := 0; i < length; i++ {
        wg.Add(1)
        go func(i int) {
            defer wg.Done()
            for ii := 0; ii < addressesLen; ii++ {
                wg.Add(1)
                go func(i int, ii int, keys []key) {
                    defer wg.Done()
                    for _, v := range addresses {
                        if set[keys[i].compressed] || set[keys[i].uncompressed] {
                            fmt.Print("Found an address: " + v + "!\n")
                            fmt.Printf("%v", keys[i])
                            fmt.Print("\n")
                            foundAddresses += 1
                            found += 1
                        }
                    }
                }(i, ii, keys)
            }
        }(i)
        foundAddresses = 0
    }
}

1 Ответ

2 голосов
/ 29 июня 2019

Я бы не использовал глобальный sync.WaitGroup, трудно понять, что происходит. Вместо этого просто определите его там, где вам нужно.

Вы звоните wg.Wait() внутри блока цикла. Это в основном блокирует цикл каждую итерацию, ожидающую завершения goroutine. Что вы действительно хотите, так это порождать все горутины и только потом ждать их завершения.

if ok {
    cmd := exec.Command("clear")
    cmd.Stdout = os.Stdout
    cmd.Run()
    var wg sync.WaitGroup //I am about to spawn goroutines, I need to wait for them
    for i.Cmp(bignum) < 0 {
        wg.Add(1)
        go func(i *big.Int) {
            defer wg.Done()
            go printKeys(i.String(), kpp)
            i.Add(i, big.NewInt(1))
            pages_queried += 1
            infofunc(i)
        }(i)
    }
    wg.Wait() //Now that all goroutines are working, let's wait
}

Нельзя избежать перекрытия печати, если у вас есть несколько процедур. Если это проблема, вы можете подумать об использовании Go * log stdlib, который добавит вам временные метки. Затем вы сможете сортировать их в хронологическом порядке.

В любом случае, разделение кода на большее количество подпрограмм не обеспечивает ускорения. Если проблема, которую вы пытаетесь решить, является по сути последовательной, то большее количество процедур просто добавит больше конкуренции и давления на планировщик Go, что приведет к противоположному результату. Подробнее здесь. Таким образом, процедура для infofunc не поможет. Но это можно улучшить, используя библиотеку логгеров вместо простого пакета fmt.

func infofunc(i *big.Int) {
    duration := time.Since(start_time).Seconds()
    if duration != 0 {
        log.Printf("\033[5;0H")
        log.Printf("Started at %s. Found: %d. Elapsed: %s. Queried: %d pages. Current page: %s. Rate: %d/s", start_time.String(), found, elapsed.String(), pages_queried, i.String(), (pages_queried / duration2))
    }
}

Для printKeys я бы не создавал так много goroutines, они не помогут, если работа, которую они должны выполнить, связана с CPU, что, похоже, имеет место здесь.

func printKeys(pageNumber string, keysPerPage int) {
    keys := generateKeys(pageNumber, keysPerPage)
    length := len(keys)
    var addressesLen = len(addresses)
    var wg sync.WaitGroup //Local WaitGroup
    for i := 0; i < length; i++ {
        wg.Add(1)
        go func(i int) {  //This goroutine could be removed, in my opinion.
            defer wg.Done()
            for ii := 0; ii < addressesLen; ii++ {
                for _, v := range addresses {
                    if set[keys[i].compressed] || set[keys[i].uncompressed] {
                        log.Printf("Found an address: %v\n", v)
                        log.Printf("%v", keys[i])
                        log.Printf("\n")
                        foundAddresses += 1
                        found += 1
                    }
                }
            }
        }(i)
        foundAddresses = 0
    }
    wg.Wait()
}

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

...