[LINUX] [Go language] Versuchen Sie, einen nutzlos zählbaren Zähler mit mehreren Threads zu erstellen

Einführung

Ich kann die Angewohnheit, nutzlose Dinge zu produzieren, nicht loswerden. Dieses Mal schrieb ich einen Artikel über den Code des Zeilenzählers, der nutzlos multithreaded war.

Einige Dateien sind schneller als wc -l, was für diejenigen nützlich sein kann, die die Zeilen am schnellsten zählen möchten.

Go lernt alleine. Wir freuen uns auf Ihre Vorschläge zu Fehlern und besserem Schreiben.

Nachschlagewerk

"Parallelverarbeitung in Go-Sprache" (Warum haben Sie es nicht früher gelesen?) Rubbing io.Reader (Erzählen Sie mir von der Verborgenheit von Go?)

Zu implementierende Funktionen

Zählt die Anzahl der Zeilen in der durch das Argument angegebenen Datei und zeigt sie auf dem Bildschirm an.

Die folgenden Werte können im Argument angegeben werden -Dateiname (-f) -Anzahl der Unterteilungen für geteiltes Lesen (-s) -Anzahl der Threads für die Parallelverarbeitung (-t) -Lesen Sie die Puffergröße (-b)

Charakteristisch

-Zählen Sie die Anzahl der Zeilen durch Multithread-Verarbeitung mit Goroutine und Kanälen -Rubben der Datei mit io.Reader (Reiben von io.Reader) -Verschieben Sie die Lesestartposition mit Suchen und teilen und lesen Sie die Datei nutzlos. ・ Es ist umweltfreundlich, da es den Lesepuffer direkt durchsucht.

Prozessablauf

Der allgemeine Verarbeitungsablauf ist wie folgt.

  1. Erhalten Sie die bereits erwähnten Optionsinformationen aus dem Befehlszeilenargument
  2. Ermitteln Sie die Dateigröße (Anzahl der Bytes) der Zieldatei
  3. Teilen Sie die Dateigröße durch die Anzahl der Unterteilungen, um die Lesestartposition und die Lesegröße zu bestimmen, für die jeder Prozess verantwortlich ist.
  4. Starten Sie die Goroutine für die angegebene Anzahl von Threads und zählen Sie die Anzahl der Zeilen von der Lesestartposition aus.
  5. Sammeln Sie das Zählergebnis jeder Goroutine über den Kanal und zeigen Sie die Gesamtzahl der Zeilen auf der Konsole an, wenn die gesamte Verarbeitung abgeschlossen ist.

Repository

Der gesamte Code ist hier

Codebeschreibung

decke den Tisch

Importieren Sie die erforderlichen Bibliotheken. Verwenden Sie das Flag, um Argumente und Glog für die Protokollierung zu erhalten.

package main
    
import (
    "bytes"                            
    "flag"          
    "fmt"
    "io"
    "math"
    "os"
    "sync"
    "time"
            
    "github.com/golang/glog"
)

Deklarieren Sie Variablen, indem Sie eine Struktur definieren, die Argumente empfängt.

// Arg is struct for commandline arg.
type Arg struct {          
    //Zu verarbeitende Datei
    targetFile string
        
    //Anzahl der Unterteilungen beim Zählen der Unterteilungen
    splitNum int
        
    //Threads laufen gleichzeitig(Maximal)Nummer
    maxThreads int
  
    //Puffergröße zum Lesen von Dateien
    buffersize int
}                                                                     
                                                                         
var (
    arg Arg
)

Schreiben Sie den Argumenterweiterungsprozess usw. in die Init-Funktion.

Da Glog ein eigenes Argument verwendet, verwenden wir flag, um das eigene Argument von Glog neu zu definieren und zu initialisieren. Wenn die Befehlshilfe angezeigt wird, wird auch die Erklärung des Glog-Arguments angezeigt und ist verwirrend. Daher habe ich in die Argumenterklärung dieses Prozesses "(go-lc)" eingefügt, um sie zu unterscheiden (dieser Bereich ist intelligenter). Es scheint einen Weg zu geben, es zu schreiben.

func init() {
    //Hilfemeldung einstellen
    flag.Usage = func() {  
        fmt.Fprintf(os.Stderr, "%s\n", fmt.Sprintf("%s -f TARGETFILE [options] [glog options]", 
os.Args[0]))
        flag.PrintDefaults()
    }               
 
    //Grundeinstellung des Loggers
    _ = flag.Set("stderrthreshold", "INFO")
    _ = flag.Set("v", "0")
        
    //Festlegen von Befehlszeilenoptionen
    flag.StringVar(&arg.targetFile, "f", "", "(go-lc) Target File.")
    flag.IntVar(&arg.splitNum, "s", 2, "(go-lc) Num of File split.")
    flag.IntVar(&arg.maxThreads, "t", 2, "(go-lc) Max Num of Threads.")
    flag.IntVar(&arg.buffersize, "b", 1024*1024, "(go-lc) Size of ReadBuffer(default=1024*1024).")
}

Die Befehlshilfe in dieser Konfiguration wird wie folgt angezeigt: Die Hilfe von Glog ist ziemlich laut.

(Es ist laut, aber für ein kleines Tool ist es einfach, da Sie den Änderungsprozess der Protokollebene nicht selbst implementieren müssen.)

> ./bin/go-lc --help
./bin/go-lc -f TARGETFILE [options] [glog options]
  -alsologtostderr
        log to standard error as well as files
  -b int
        (go-lc) Size of ReadBuffer (default 1048576)
  -f string
        (go-lc) Target File
  -log_backtrace_at value
        when logging hits line file:N, emit a stack trace
  -log_dir string
        If non-empty, write log files in this directory
  -logtostderr
        log to standard error instead of files
  -s int
        (go-lc) Num of File split (default 2)
  -stderrthreshold value
        logs at or above this threshold go to stderr
  -t int
        (go-lc) Max Num of Threads (default 2)
  -v value
        log level for V logs
  -vmodule value
        comma-separated list of pattern=N settings for file-filtered logging

Hauptverarbeitung

Ich werde zuerst den Hauptprozess erklären. Es wird jedoch nur die Funktion getNumOfLines aufgerufen, die aggregiert, das Ergebnis empfängt und auf dem Bildschirm anzeigt.

func main() {
    flag.Parse()
            
    glog.V(1).Infof("Start")
            
    //Starten Sie den Timer für die Berechnung der Verarbeitungszeit
    startTime := time.Now() 

    //Führen Sie die Aggregationsverarbeitung aus
    numOfLines, _ := getNumOfLines(arg.targetFile, arg.splitNum, arg.maxThreads, arg.buffersize)
            
    //Bearbeitungszeit anzeigen
    glog.V(1).Infof("End(%s)", time.Since(startTime))
            
    fmt.Printf("%d\n", numOfLines)
}

getFileSize()

Eine Funktion zum Abrufen der Dateigröße. Siehe Kommentare zur Verarbeitung.

func getFileSize(filename string) (int, error) {
    //Öffnen Sie die Zieldatei
    fh, err := os.OpenFile(filename, 0, 0)
    if err != nil {  
        return 0, err
    }
    defer fh.Close()

    //Dateiinformationen abrufen
    fileinfo, err := fh.Stat()
    if err != nil {
        return 0, err
    }

    //Ruft die Anzahl der Bytes in der Datei ab und gibt sie zurück
    return int(fileinfo.Size()), nil
}

getNumOfLines()

Dies ist getNumOfLines (), das ich hauptsächlich gelesen habe. Es ist eine etwas lange Funktion, deshalb werde ich es separat erklären.

Der erste Teil berechnet die Anzahl der Pufferlesevorgänge für die gesamte Datei. Die folgende Formel.

*** Anzahl der Lesevorgänge = Dateigröße / Puffergröße (+1 wenn nicht teilbar) ***

func getNumOfLines(filename string, splitNum int, maxThreads int, buffersize int) (int, error) {
    //Dateigröße abrufen
    fsize, err := getFileSize(filename)
    if err != nil {  
        return 0, err
    }

    // loglevel =Informationsanzeige mit 1
    glog.V(1).Infof("FileSize   : %10d byte", fsize)
    glog.V(1).Infof("Read buffer: %10d byte", buffersize)
    glog.V(1).Infof("Max Threads: %d", maxThreads)
    glog.V(1).Infof("Split Num  : %d", splitNum)

    //Berechnen Sie, wie oft es in Einheiten der Puffergröße gelesen werden kann.
    var readCountTotal int = int(math.Trunc(float64(fsize) / float64(buffersize)))

    //Wenn es einen Rest gibt, addieren Sie 1 zur Anzahl der Lesevorgänge
    if fsize-(readCountTotal*buffersize) > 0 {
        readCountTotal++
    }

Als nächstes werden wir das Multithreading einrichten. Eine Wartegruppe ist möglicherweise nicht erforderlich, wenn Sie festlegen, wie der Kanal verwendet werden soll. Sie wird jedoch aus Gründen der Übersichtlichkeit verwendet.

    //Initialisieren Sie die Endwartegruppe
    wg := &sync.WaitGroup{}

    //Kanal zur Begrenzung der Anzahl gleichzeitiger Goroutine-Läufe
    jc := make(chan interface{}, maxThreads)
    defer close(jc)

    //Kanal zum Empfangen des Zeilenzählungsergebnisses jeder Goroutine
    counterCh := make(chan int, maxThreads)

    //Vom Ende der Standby-Goroutine jeder Goroutine
    //Kanal für die Rückgabe aggregierter Ergebnisse an die Hauptverarbeitung
    resultCh := make(chan int)
    defer close(resultCh)

    //Starten Sie Goroutine, um Ergebnisse zu erhalten
    //Die Endbedingung ist nahe(counterCh)
    go func(counterCh <-chan int) {
        cAll := 0
        for c := range counterCh {
            cAll += c

            glog.V(2).Infof("[receiver] receive: %d\n", c)
        }
     
        resultCh <- cAll
    }(counterCh)

Dies ist eine Schleife, die Goroutine (countWorker) startet, die die Anzahl der Zeilen zählt. byteOffset ist die Lesestartposition. EachReadCount gibt an, wie oft der Puffer gelesen wurde, die Berechnungsmethode stammt jedoch von der folgenden Site. Teilen Sie n ganze Zahlen so gleichmäßig wie möglich auf Einige Leute sind schlau.

Ein Kanal namens jc wird verwendet, um die Anzahl der gleichzeitigen Starts zu steuern. Es ist eine Standardverwendung, aber ich denke, die folgende URL wird hilfreich sein. So steuern Sie die maximale Anzahl von Goroutinen Begrenzen Sie die Anzahl der parallelen Prozesse entsprechend der Anzahl der CPUs in der Go-Sprache Übrigens verwenden die beiden oben genannten Sites den Bool-Typ und den Int-Typ, aber laut "Parallelverarbeitung durch Go-Sprache (O'Reilly)" ist "Eine Schnittstelle mit einer Kapazität von 0 gut für den Kanal zur Steuerung der Anzahl der Starts!" Das ist.

    //Anzahl der Zeilen Anzahl Startposition für die Übergabe an die Goroutine(0 ist#Für 1 Goroutine)        
    var byteOffset int64 = 0

    //Zeilenzählschleife zum Starten der Goroutine
    for i := 0; i < splitNum; i++ {
        //Wie oft muss der Puffer in countLinesInThread gelesen werden?
        eachReadCount := int(math.Trunc(float64(readCountTotal+i) / float64(splitNum)))

        //Füllen Sie ein Goroutine-Startnummernarray
        jc <- true

        //Erhöhen Sie die Wartegruppe um eins
        wg.Add(1)

        //Startlinienzählung Goroutine
        go countWorker(filename, eachReadCount, byteOffset, buffersize, wg, jc, counterCh)

        //Vorab-Leseversatz
        byteOffset += int64(eachReadCount * buffersize)
    }

    wg.Wait()
    close(counterCh)

    return <-resultCh, nil
}

countWorker()

Definieren Sie als Nächstes countWorker (), den Hauptteil von Goroutine. Eigentlich öffne ich einfach die durch Dateiname angegebene Datei, verschiebe die Leseposition mit f.Seek und lese dann getNumOfCharsOnIo. Der Teil, der dieselbe Datei mehrmals öffnet, scheint etwas überflüssig zu sein, aber da es einen Suchprozess für das geteilte Lesen gibt, mache ich dies (Ist es sinnvoll, hier zu teilen? Ich habe ein Gefühl, aber es ist mir egal In einigen Fällen ist es wichtig, die ersten Absichten am Entwicklungsstandort für Erwachsene zu haben.

func countWorker(filename string, eachReadCount int, byteOffset int64, buffersize int,
    wg *sync.WaitGroup, jc <-chan interface{}, counter chan<- int) {
    var c int = 0

    defer func() {
        //Anonyme Funktionen können auf Variablen des äußeren Bereichs zugreifen, sodass dies kein Problem darstellt
        counter <- c
        wg.Done()
        <-jc
    }()

    // loglevel=Informationsanzeige mit 2
    glog.V(2).Infof("[countWorker] start (offset: %d, read size: %d)\n", byteOffset, eachReadCount*buffersize)
    //Öffnen Sie die Zieldatei erneut
    //Der Lesecursor von Seek wird durch die Verwendung des ursprünglichen Dateihandlers durcheinander gebracht
    f, err := os.OpenFile(filename, 0, 0)
    if err != nil {
        return
    }
    defer f.Close()

    //Gehen Sie zur angegebenen Lesestartposition
    _, err = f.Seek(byteOffset, 0)
    if err != nil {
        return
    }

    //Sie können auch ein wie folgt erstelltes Bufio an getNumOfCharsOnIo übergeben
    //Diesmal io.Da die Größe der vom Reader gelesenen Daten geändert werden kann, wird sie nicht verwendet, da sie als wenig wertvoll angesehen werden.
    // br := bufio.NewReaderSize(f, 1024*1024)

    c, err = getNumOfCharsOnIo(f, buffersize, eachReadCount)
    if err != nil {
        panic(err)
    }
}

getNumOfCharsOnIo()

Definieren Sie abschließend getNumOfCharsOnIo (), das tatsächlich die Anzahl der Zeilen zählt. Der Vorgang ähnelt dem berühmten Artikel "io.Reader Susukore", aber bytes.IndexByte () wird dem Teil hinzugefügt, der Zeilenumbrüche zählt. Ich benutze. Ich denke, es ist in Ordnung, wenn man den UTF-8-Mechanismus berücksichtigt, aber es ist möglicherweise sicherer, Index Rune zu verwenden, das auskommentiert ist. Hmm. Im Vergleich zum Ergebnis von wc in einigen realen Dateien war es dasselbe, aber es kann Fälle geben, in denen das Ergebnis unterschiedlich ist.

// io.Lesen Sie die Puffergröße aus dem Reader und wiederholen Sie den Vorgang des Zählens der Anzahl der Vorkommen von targetStr repeatCount-Zeiten.
func getNumOfCharsOnIo(r io.Reader, buffersize int, repeatCount int) (int, error) {
    //Lesepuffer initialisieren
    buf := make([]byte, buffersize)

    //Variable zum Speichern der Anzahl der Zeilen
    var c int = 0

    //Lesen Sie von der Startposition aus die Byte-Zeichenfolge in Puffergröße und weisen Sie sie buf zu
    for j := 0; j < repeatCount; j++ {
        n, err := r.Read(buf)
        //Wenn die Lesegröße 0 ist
        if n == 0 {
            return c, err
        }

        //Verarbeitung beim Lesefehler
        if err != nil {
            return c, err
        }

        //Offset zum Scannen des Pufferinhalts
        of := 0

        //Verarbeitung zum Zählen von Zeilenumbrüchen im Puffer
        for {
            //Die Größe wird durch n angegeben, da buf verwendet wird.
            // index := bytes.IndexRune(buf[of:n], rune('\n'))
            index := bytes.IndexByte(buf[of:n], '\n')

            //Verlassen Sie danach die Schleife ohne Zeilenumbrüche
            if index == -1 {
                break
            }

            // (Von Zeilenumbrüchen)Erhöhen Sie den Zähler
            c++

            //Entdeckungsposition+Vorabversatz auf 1
            of += index + 1
        }
    }

    return c, nil
}

Der größte Teil der Erklärung wurde Inline-Kommentaren überlassen, aber das ist alles für den Code.

Das Ganze ist hier

Anwendungsbeispiel

Da es sich um ein Programm handelt, das Optionen enthält, werde ich kurz zeigen, wie es verwendet wird.

Zeig Hilfe

> ./bin/go-lc --help
./bin/go-lc -f TARGETFILE [options] [glog options]
  -alsologtostderr
        log to standard error as well as files
  -b int
        (go-lc) Size of ReadBuffer (default 1048576)
  -f string
        (go-lc) Target File
  -log_backtrace_at value
        when logging hits line file:N, emit a stack trace
  -log_dir string
        If non-empty, write log files in this directory
  -logtostderr
        log to standard error instead of files
  -s int
        (go-lc) Num of File split (default 2)
  -stderrthreshold value
        logs at or above this threshold go to stderr
  -t int
        (go-lc) Max Num of Threads (default 2)
  -v value
        log level for V logs
  -vmodule value
        comma-separated list of pattern=N settings for file-filtered logging

Zählen Sie vorerst die Anzahl der Zeilen

> ./bin/go-lc -f /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql
1428

Ändern Sie loglevel und zählen Sie die Anzahl der Zeilen, während Sie verschiedene anzeigen

Standardmäßig ist es zweigeteilt und wird in zwei Threads ausgeführt.

> ./bin/go-lc -f /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql -v 5
I1216 20:47:23.781456   12091 main.go:233] Start
I1216 20:47:23.781785   12091 main.go:79] FileSize   : 1426087753 byte
I1216 20:47:23.781801   12091 main.go:80] Read buffer:    1048576 byte
I1216 20:47:23.781816   12091 main.go:81] Max Threads: 2
I1216 20:47:23.781826   12091 main.go:82] Split Num  : 2
I1216 20:47:23.781871   12091 main.go:160] [countWorker] start (offset: 713031680, read size: 714080256)
I1216 20:47:23.781953   12091 main.go:160] [countWorker] start (offset: 0, read size: 713031680)
I1216 20:47:23.957093   12091 main.go:115] [receiver] receive: 699
I1216 20:47:23.969989   12091 main.go:115] [receiver] receive: 729
I1216 20:47:23.970048   12091 main.go:242] End(188.280638ms)
1428

Es verarbeitet eine Datei von ca. 1,4 GB in 0,188 Sekunden.

Zählen Sie, indem Sie die Anzahl der Unterteilungen (-s) und die Anzahl der Threads (-t) angeben.

> ./bin/go-lc -f /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql -v 5 -s 4 -t 4
I1216 20:51:51.827208   13285 main.go:233] Start
I1216 20:51:51.827519   13285 main.go:79] FileSize   : 1426087753 byte
I1216 20:51:51.827534   13285 main.go:80] Read buffer:    1048576 byte
I1216 20:51:51.827553   13285 main.go:81] Max Threads: 4
I1216 20:51:51.827565   13285 main.go:82] Split Num  : 4
I1216 20:51:51.827607   13285 main.go:160] [countWorker] start (offset: 1069547520, read size: 357564416)
I1216 20:51:51.827706   13285 main.go:160] [countWorker] start (offset: 713031680, read size: 356515840)
I1216 20:51:51.827646   13285 main.go:160] [countWorker] start (offset: 356515840, read size: 356515840)
I1216 20:51:51.827642   13285 main.go:160] [countWorker] start (offset: 0, read size: 356515840)
I1216 20:51:51.938578   13285 main.go:115] [receiver] receive: 343
I1216 20:51:51.939430   13285 main.go:115] [receiver] receive: 356
I1216 20:51:51.952839   13285 main.go:115] [receiver] receive: 386
I1216 20:51:51.956868   13285 main.go:115] [receiver] receive: 343
I1216 20:51:51.956899   13285 main.go:242] End(129.400448ms)
1428

Mit der vorherigen Datei ist es schneller auf 0,129 Sekunden.

Versuchen Sie, die Größe des Lesepuffers zu ändern

> ./bin/go-lc -f /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql -v 5 -s 4 -t 4 -
b 1024
I1216 20:53:02.522702   13459 main.go:233] Start
I1216 20:53:02.523194   13459 main.go:79] FileSize   : 1426087753 byte
I1216 20:53:02.523217   13459 main.go:80] Read buffer:       1024 byte
I1216 20:53:02.523222   13459 main.go:81] Max Threads: 4
I1216 20:53:02.523229   13459 main.go:82] Split Num  : 4
I1216 20:53:02.523275   13459 main.go:160] [countWorker] start (offset: 1069565952, read size: 356521984)
I1216 20:53:02.523351   13459 main.go:160] [countWorker] start (offset: 0, read size: 356521984)
I1216 20:53:02.523442   13459 main.go:160] [countWorker] start (offset: 356521984, read size: 356521984)
I1216 20:53:02.526218   13459 main.go:160] [countWorker] start (offset: 713043968, read size: 356521984)
I1216 20:53:03.146721   13459 main.go:115] [receiver] receive: 343
I1216 20:53:03.149466   13459 main.go:115] [receiver] receive: 386
I1216 20:53:03.186216   13459 main.go:115] [receiver] receive: 356
I1216 20:53:03.190404   13459 main.go:115] [receiver] receive: 343
I1216 20:53:03.190443   13459 main.go:242] End(667.278999ms)
1428

Es wurde aufgrund des kleineren Puffers und der Ineffizienz auf 0,667 Sekunden verlangsamt.

Ergebnis

Übrigens denke ich, dass einige Leute besorgt sind über das Ergebnis des Schreibens solch nutzloser Verarbeitung und wie schnell es sein wird. Im Verwendungsbeispiel ist das Ergebnis der Puffergröße 1048576 (1024 * 1024) von 4 Unterteilungen und 4 Threads gut.

Das Ergebnis des Befehls wc -l für dieselbe Datei lautet

time wc -l /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql

(Erstes Mal)
1428 /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql
0.04user 0.26system 0:00.30elapsed 100%CPU (0avgtext+0avgdata 2104maxresident)k
0inputs+0outputs (0major+76minor)pagefaults 0swaps

(Zweites Mal)
time wc -l /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql
1428 /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql
0.03user 0.22system 0:00.26elapsed 99%CPU (0avgtext+0avgdata 2068maxresident)k
0inputs+0outputs (0major+75minor)pagefaults 0swaps

(Drittes Mal)
1428 /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql
0.03user 0.22system 0:00.26elapsed 99%CPU (0avgtext+0avgdata 2124maxresident)k
0inputs+0outputs (0major+75minor)pagefaults 0swaps

(4 ..)
1428 /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql
0.04user 0.20system 0:00.25elapsed 99%CPU (0avgtext+0avgdata 2104maxresident)k
0inputs+0outputs (0major+78minor)pagefaults 0swaps

(5. Mal)
1428 /mnt/v01/resource/wikipedia/jawiki/20191001/extract/jawiki-20191001-categorylinks.sql
0.04user 0.23system 0:00.27elapsed 100%CPU (0avgtext+0avgdata 2068maxresident)k
0inputs+0outputs (0major+75minor)pagefaults 0swaps

durchschnittlich: 0.268s

Der Durchschnitt von 5 Mal betrug 0,268 Sekunden.

Die Computerumgebung besteht ausschließlich aus Google Cloud Platform Compute Engine (n1-Standard-4 (vCPU x 4, Speicher 15 GB)), und die Zieldatei ist eine Wikidump-SQL-Datei mit 1,4 GB.

Es dauerte 0,129 Sekunden in 4 Divisionen, was besser ist als der Befehl wc -l. Es war die Verschwendung wert.

bitte beachten Sie

Tatsächlich ist das obige Ergebnis wahrscheinlich auf den Datei-Cache von GCE zurückzuführen. Als ich es mit einer anderen 20-GB-Datei versuchte, dauerte es ungefähr 3 Minuten wie folgt. Diese Größe ist auch schneller als wc, aber es scheint, dass es schneller sein kann, weil es 1,4 GB in 0,3 Sekunden verarbeitet. Daher können Dateien mit mehreren GB zwischengespeichert werden. Mit anderen Worten, es besteht das Risiko, dass es vor und nach dem Zwischenspeichern zu einem Unterschied in der Effizienz kommt. Und da GCE Festplatten über das Netzwerk bereitgestellt hat, hängt die Geschwindigkeit auch von den Netzwerkbedingungen ab. Bitte beachten Sie die obigen Ergebnisse nur als Referenz.

Listen Sie die Ergebnisse der Ausführung von wc -l und dieses Programms für eine Datei von ca. 20 GB auf.

> time wc -l /mnt/v01/resource/wikipedia/enwiki/20191001/extract/enwiki-20191001-categorylinks.sql

(wc 1. mal)
19525 /mnt/v01/resource/wikipedia/enwiki/20191001/extract/enwiki-20191001-categorylinks.sql
0.91user 8.21system 3:58.40elapsed 3%CPU (0avgtext+0avgdata 2116maxresident)k
31893064inputs+0outputs (0major+75minor)pagefaults 0swaps

(wc zweites mal)
19525 /mnt/v01/resource/wikipedia/enwiki/20191001/extract/enwiki-20191001-categorylinks.sql
0.86user 7.86system 3:09.32elapsed 4%CPU (0avgtext+0avgdata 2044maxresident)k
26220800inputs+0outputs (0major+77minor)pagefaults 0swaps

(go-lc 1. mal)
bin/go-lc -f /mnt/v01/resource/wikipedia/enwiki/20191001/extract/enwiki-20191001-categorylinks.sql -v 5 -t 4 -s 4
I1216 21:19:05.381636   14202 main.go:233] Start
I1216 21:19:05.384584   14202 main.go:79] FileSize   : 20234931295 byte
I1216 21:19:05.384601   14202 main.go:80] Read buffer:    1048576 byte
I1216 21:19:05.384605   14202 main.go:81] Max Threads: 4
I1216 21:19:05.384609   14202 main.go:82] Split Num  : 4
I1216 21:19:05.384704   14202 main.go:160] [countWorker] start (offset: 15176040448, read size: 5059379200)
I1216 21:19:05.384733   14202 main.go:160] [countWorker] start (offset: 0, read size: 5058330624)
I1216 21:19:05.384786   14202 main.go:160] [countWorker] start (offset: 5058330624, read size: 5058330624)
I1216 21:19:05.384859   14202 main.go:160] [countWorker] start (offset: 10116661248, read size: 5059379200)
I1216 21:19:06.836037   14202 main.go:115] [receiver] receive: 4881
I1216 21:20:49.994339   14202 main.go:115] [receiver] receive: 4866
I1216 21:20:56.968630   14202 main.go:115] [receiver] receive: 4910
I1216 21:21:00.825423   14202 main.go:115] [receiver] receive: 4868
I1216 21:21:00.825466   14202 main.go:242] End(1m55.440902834s)
19525

(go-lc 2. mal)
bin/go-lc -f /mnt/v01/resource/wikipedia/enwiki/20191001/extract/enwiki-20191001-categorylinks.sql -v 5 -t 4 -s 4
I1216 21:21:19.065087   14343 main.go:233] Start
I1216 21:21:19.066146   14343 main.go:79] FileSize   : 20234931295 byte
I1216 21:21:19.066164   14343 main.go:80] Read buffer:    1048576 byte
I1216 21:21:19.066169   14343 main.go:81] Max Threads: 4
I1216 21:21:19.066182   14343 main.go:82] Split Num  : 4
I1216 21:21:19.066232   14343 main.go:160] [countWorker] start (offset: 15176040448, read size: 5059379200)
I1216 21:21:19.066234   14343 main.go:160] [countWorker] start (offset: 0, read size: 5058330624)
I1216 21:21:19.066314   14343 main.go:160] [countWorker] start (offset: 5058330624, read size: 5058330624)
I1216 21:21:19.066377   14343 main.go:160] [countWorker] start (offset: 10116661248, read size: 5059379200)
I1216 21:21:20.477393   14343 main.go:115] [receiver] receive: 4881
I1216 21:23:04.790516   14343 main.go:115] [receiver] receive: 4910
I1216 21:23:35.783612   14343 main.go:115] [receiver] receive: 4868
I1216 21:23:53.859878   14343 main.go:115] [receiver] receive: 4866
I1216 21:23:53.859920   14343 main.go:242] End(2m34.793812658s)
19525

schließlich

Dieser Prozess wurde auch als Test erstellt, um eine große Textdatei wie Wikidump mit hoher Geschwindigkeit zu verarbeiten, ohne das Ganze zu laden.

Da ich den Read-Handler separat erhalten habe, habe ich versucht, 4 Festplatten auf GCE zu mounten, dieselbe Datei zu kopieren und mit Festplattenverteilung zu lesen, aber ich habe erwartet, dass die Kommunikationsgeschwindigkeit mit NAS begrenzt ist. Der Effekt wurde nicht erzielt (es war fast die gleiche Geschwindigkeit wie beim separaten Lesen mit einer Einheit). GCS, ein Speicherdienst von GCP, unterstützt das Einlesen von Byteeinheiten, oder es kann schneller sein, ein geteiltes Lesen für GCS durchzuführen. Dieser Bereich scheint den Vorteil von io.Reader oder den Vorteil von Go zu haben, mit dem Sie den Code durch Lesen aus einer Datei und über die GCS-API wiederverwenden können.

Vielen Dank, dass Sie bis zum Ende des Artikels bei uns bleiben, der die Räder auf komplizierte Weise neu zu entwickeln scheint.

Recommended Posts

[Go language] Versuchen Sie, einen nutzlos zählbaren Zähler mit mehreren Threads zu erstellen
Versuchen Sie, eine Sprache auszuwählen
[C-Sprache] [Linux] Versuchen Sie, einen einfachen Linux-Befehl zu erstellen. * Fügen Sie einfach hinzu! !!
Versuchen Sie, einen neuen Befehl unter Linux zu erstellen
So erstellen Sie ein 1-zeiliges Kivy-Eingabefeld
Versuchen Sie, ein Python-Modul in C-Sprache zu erstellen
Versuchen Sie, mit Tkinter in Python dynamisch einen Checkbutton zu erstellen
[Go] So erstellen Sie einen benutzerdefinierten Fehler für Sentry
Erstellen Sie einen Webserver in der Sprache Go (net / http) (1)
Versuchen Sie, eine Python-Umgebung mit Visual Studio Code & WSL zu erstellen
Rails-Benutzer versuchen, mit Django eine einfache Blog-Engine zu erstellen
[Los] Erstellen Sie einen CLI-Befehl, um die Erweiterung des Bildes zu ändern
Versuchen Sie, einen Web-Service-ähnlichen Typ mit 3D-Markup-Sprache zu erstellen
[C-Sprache] So erstellen, vermeiden und erstellen Sie einen Zombie-Prozess
Erstellen Sie eine Funktion, um den Inhalt der Datenbank in Go abzurufen
Versuchen Sie, einen Artikel von Qiita mit der REST-API [Umweltvorbereitung] zu erstellen.
Erstellen Sie ein Befehlszeilenprogramm, um mit Python Dollar in Yen umzurechnen
Versuchen Sie, Yuma in der Sprache Go zu implementieren
Versuchen Sie, eine Bezier-Kurve zu zeichnen
Schritte zum Erstellen eines Django-Projekts
So erstellen Sie ein Conda-Paket
So erstellen Sie eine virtuelle Brücke
Wie erstelle ich eine Docker-Datei?
5 Möglichkeiten zum Erstellen eines Python-Chatbots
So erstellen Sie eine Konfigurationsdatei
Erstellen Sie mit Django einen LINE-Bot
[Python] [Word] [python-docx] Versuchen Sie, mit python-docx eine Vorlage für einen Wortsatz in Python zu erstellen
Wenn ich mit Chainer zurückkehre, passt es ein wenig
3. Verarbeitung natürlicher Sprache mit Python 1-2. So erstellen Sie einen Korpus: Aozora Bunko
Ich habe versucht, einen Linebot zu erstellen (Implementierung)
So erstellen Sie einen Klon aus Github
Erstellen Sie einen Bot, um Corona-Virus-Informationen zu retweeten
So erstellen Sie einen Git-Klonordner
Ich habe versucht, einen Linebot zu erstellen (Vorbereitung)
Machen wir einen Jupyter-Kernel
[GO-Sprache] Lesen wir die YAML-Datei
[Python] [LINE Bot] Erstellen Sie einen LINE Bot mit Papageienrückgabe
Erstellen Sie mit Python 3.4 einen Worthäufigkeitszähler
Verschiedene Möglichkeiten, ein Wörterbuch zu erstellen (Erinnerungen)
Versuchen Sie es mit einer probabilistischen Programmiersprache (Pyro).
So erstellen Sie ein Repository aus Medien
Skript zum Erstellen einer Mac-Wörterbuchdatei
Erstellen Sie einen Assembler für synthetische Sprachen auf hoher Ebene. Teil 6
Anfänger haben versucht, eine Cloud-native Webanwendung mit Datastore / GAE zu erstellen
Versuchen Sie, mit WebSocket mit Django (Swamp Dragon) eine Todo-Verwaltungssite zu erstellen.
[LINE WORKS-Version Trello Bot] So erstellen Sie einen privaten Gesprächsraum mit einem Gesprächsbot