
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!