Teilen und erobern


Bei der Arbeit mit einer Datenbank (insbesondere mit PostgreSQL) hatte ich die Idee, Daten aus einer Tabelle parallel auszuwĂ€hlen (mit Go GoP). Und ich fragte mich: „Ist es möglich, Probenlinien in einzelnen Goroutinen zu scannen?

Wie sich herausstellte, kann func (* Rows) Scan in Gourutins nicht gleichzeitig aufgerufen werden. Aufgrund dieser EinschrĂ€nkung habe ich mich dazu entschlossen, andere Prozesse parallel zum Scannen von Zeilen durchzufĂŒhren, insbesondere die Aufbereitung der resultierenden Daten.

Weil Scan stapelt die Daten gemĂ€ĂŸ den Zeigern. Ich habe beschlossen, zwei Slices zu erstellen (ich werde spĂ€ter erklĂ€ren, warum zwei), zwischen denen ich Scan wechseln werde, wĂ€hrend der Rest der Gourutins sich mit den bereits ausgewĂ€hlten Daten befasst.

ZunÀchst muss ich die Anzahl der Beispielspalten kennen:

columns, err = rows.Columns() count := len(columns) 

Als NĂ€chstes erstelle ich zwei Slices mit Werten und Zeigern auf diese Werte (wobei ich wĂ€hrend des Zeilenscannens Daten hinzufĂŒgen werde):

 values := make([]interface{}, count) valuesPtrs := make([]interface{}, count) values_ := make([]interface{}, count) valuesPtrs_ := make([]interface{}, count) for i := range columns { valuesPtrs[i] = &values;[i] valuesPtrs_[i] = &values;_[i] } 

In diesem Beispiel werde ich das Ergebnis der Auswahl hinzufĂŒgen, um [string] string zuzuordnen, wobei die Spaltennamen die SchlĂŒssel sind. Sie können eine bestimmte Struktur verwenden, die die Typen angibt, aber seit Der Zweck dieser Veröffentlichung ist es, aus der Hektik herauszufinden, wie realisierbar der vorgeschlagene Ansatz ist. Betrachten wir die Auswahl in der Karte.

Als nÀchstes trenne ich zwei Gorutins, von denen eines die resultierende Karte bilden wird:

 func getData(deleteNullValues bool, check, finish chan bool, dbData chan interface{}, columns []string, data *[]map[string]string) { lnc := len(columns) for <-check { row := make(map[string]string) for i := 0; i < lnc; i++ { el := <-dbData b, ok := el.([]byte) if ok { row[columns[i]] = string(b) } else { if el == nil { if deleteNullValues == false { row[columns[i]] = "" } } else { row[columns[i]] = fmt.Sprint(el) } } } *data = append(*data, row) } finish <- true } 

Und der zweite schaltet zwischen zwei Slices mit den von Scan generierten Werten um und sendet sie an den Kanal fĂŒr das vorherige Gourutin (das das Ergebnis bildet):

 func transferData(values, values_ []interface{}, dbData chan interface{}, swtch, working, check chan bool) { for <-working { check <- true switch <-swtch { case false: for _, v := range values { dbData <- v } default: for _, v := range values_ { dbData <- v } } } } 

Der Hauptprozess wechselt zwischen Zeigerscheiben und wÀhlt Daten aus:

 for rows.Next() { switch chnl { case false: if err = rows.Scan(valuesPtrs...); err != nil { fmt.Printf("rows.Scan: %s\n%s\n%#v\n", err, query, args) return nil, nil, err } default: if err = rows.Scan(valuesPtrs_...); err != nil { fmt.Printf("rows.Scan: %s\n%s\n%#v\n", err, query, args) return nil, nil, err } } working <- true swtch <- chnl chnl = !chnl } 

In der Datenbank habe ich eine Tabelle mit 32 Spalten erstellt und 100.000 Zeilen hinzugefĂŒgt.
Als Ergebnis des Tests (beim 50-maligen Abtasten von Daten) erhielt ich die folgenden Daten:
Zeitaufwand: 1m8.022277124s - Abtasten des Ergebnisses mit einem einzigen Schnitt
Zeitaufwand: 1m7.806109441s - Abtastung des Ergebnisses mit zwei Schnitten

Mit einer Erhöhung der Anzahl der Iterationen auf 100:
Zeitaufwand: 2M15.973344023s - Auswahl des Ergebnisses mit einem einzigen Slice
Zeitaufwand: 2M15.057413845s - Abtastung des Ergebnisses mit zwei Schnitten

Der Unterschied nimmt mit zunehmendem Datenvolumen und zunehmenden Spalten in der Tabelle zu.
Das gegenteilige Ergebnis wurde jedoch mit einer Verringerung der Datenmenge oder mit einer Verringerung der Anzahl der Spalten der Tabelle beobachtet, was im Prinzip verstĂ€ndlich ist, weil Der Aufwand fĂŒr die vorbereitenden Schritte und die Gourutin-Abteilung „verschlingen“ die kostbare Zeit und das Ergebnis wird eingeebnet.

Was zwei Slices und zwei Gorutins betrifft: Ich habe Tests mit einer großen Anzahl von Slices durchgefĂŒhrt, aber die Abtastzeit hat sich erhöht, da die Funktionen getData und transferData offensichtlich Daten schneller verarbeiten als Werte aus der Datenbank abtasten. Daher ist es auch bei einer grĂ¶ĂŸeren Anzahl von Kernen nicht sinnvoll, neue Slices fĂŒr Scan und zusĂ€tzliche Goroutinen hinzuzufĂŒgen (mit Ausnahme von sehr wilden Datenmengen).

Im Github-Code gebe ich ein funktionierendes Beispiel fĂŒr diesen Ansatz. Meine Aufgaben verwenden auch andere Pakete, die ich aus den oben genannten herausgereinigt habe, aber die Hauptidee sollte darunter nicht leiden.

Generell erwarte ich von der interessierten Community konstruktive Kritik. Vielen Dank!

Source: https://habr.com/ru/post/de485056/


All Articles