1

go を使用している割り当てについて、これから行うことの 1 つは、uniprot データベース ファイルを 1 行ずつ解析して、uniprot レコードを収集することです。

私はあまり多くのコードを共有したくないのですが、そのようなファイル (2.5 GB) を 48 秒 (time go-package を使用して測定) で正しく解析するコード スニペットがあります。ファイルを繰り返し解析し、レコード終了シグナル (完全なレコード) に到達するまでレコードに行を追加し、レコードのメタデータを作成します。次に、レコード文字列がヌルになり、新しいレコードが行ごとに収集されます。それから go-routine を使ってみようと思いました。

以前にスタックオーバーフローからいくつかのヒントを得て、元のコードに、メタデータの作成に関するすべてを処理する関数を単純に追加しました。

だから、コードはやっている

  1. 空のレコードを作成し、
  2. ファイルを繰り返し、レコードに行を追加します。
  3. 記録停止信号が見つかった場合 (これで完全な記録が得られました) - go ルーチンに渡してメタデータを作成します
  4. レコード文字列を null にして、2) から続行します。

sync.WaitGroup()また、各ルーチンが終了するまで (最後に) 待機するように を追加しました。これにより、ゴルーチンが各レコードに作用している間も解析が続けられるため、データベースファイルの解析に費やされる時間が実際に短縮されると思いました。ただし、コードが 20 分以上実行されているように見えることは、何かが間違っているか、オーバーヘッドが異常になったことを示しています。助言がありますか?

package main

import (
    "bufio"
    "crypto/sha1"
    "fmt"
    "io"
    "log"
    "os"
    "strings"
    "sync"
    "time"
)

type producer struct {
    parser uniprot
}

type unit struct {
    tag string
}

type uniprot struct {
    filenames     []string
    recordUnits   chan unit
    recordStrings map[string]string
}

func main() {
    p := producer{parser: uniprot{}}
    p.parser.recordUnits = make(chan unit, 1000000)
    p.parser.recordStrings = make(map[string]string)
    p.parser.collectRecords(os.Args[1])
}

func (u *uniprot) collectRecords(name string) {
    fmt.Println("file to open ", name)
    t0 := time.Now()
    wg := new(sync.WaitGroup)
    record := []string{}
    file, err := os.Open(name)
    errorCheck(err)
    scanner := bufio.NewScanner(file)
    for scanner.Scan() { //Scan the file
        retText := scanner.Text()
        if strings.HasPrefix(retText, "//") {
            wg.Add(1)
            go u.handleRecord(record, wg)
            record = []string{}
        } else {
            record = append(record, retText)
        }
    }
    file.Close()
    wg.Wait()
    t1 := time.Now()
    fmt.Println(t1.Sub(t0))
}

func (u *uniprot) handleRecord(record []string, wg *sync.WaitGroup) {
    defer wg.Done()
    recString := strings.Join(record, "\n")
    t := hashfunc(recString)
    u.recordUnits <- unit{tag: t}
    u.recordStrings[t] = recString
}

func hashfunc(record string) (hashtag string) {
    hash := sha1.New()
    io.WriteString(hash, record)
    hashtag = string(hash.Sum(nil))
    return
}

func errorCheck(err error) {
    if err != nil {
        log.Fatal(err)
    }
}
4

1 に答える 1

3

まず第一に、あなたのコードはスレッドセーフではありません。主な理由は、ハッシュマップに同時にアクセスしているためです。これらは、go での同時実行に対して安全ではないため、ロックする必要があります。コード内の行に問題があります:

u.recordStrings[t] = recString

> 1 でgo を実行しているときにこれが爆発するのでGOMAXPROCS、私はあなたがそうしていないと仮定しています。GOMAXPROCS=2並列処理を実現するには、アプリケーションを またはそれ以上で実行していることを確認してください。デフォルト値は 1 であるため、コードは 1 つの OS スレッドで実行されます。もちろん、2 つの CPU または CPU コアで同時にスケジュールすることはできません。例:

$ GOMAXPROCS=2 go run udb.go uniprot_sprot_viruses.dat

最後に: チャネルから値を取得しないと、プログラムは終了しません。ゴルーチンの数が制限を超えると、デッドロックが発生します。76MiB のデータ ファイルでテストした ところ、ファイルは約 2.5GB であるとのことでした。16347 件のエントリがあります。直線的な成長を仮定すると、ファイルは 1e6 を超えるため、チャネルに十分なスロットがなく、プログラムはデッドロックし、実行されないゴルーチンを蓄積している間に結果が得られず、最後に失敗します (悲惨なことに)。

したがって、解決策は、チャネルから値を取得してそれらを処理する go ルーチンを追加することです。

補足: パフォーマンスが心配な場合は、文字列は常にコピーされるため使用しないでください。[]byte代わりに使用してください。

于 2013-10-09T23:49:15.143 に答える