Quel est le moyen le plus rapide de lire une table sqlite3 dans Golang?
package main
import (
"fmt"
"database/sql"
_ "github.com/mattn/go-sqlite3"
"log"
"time"
)
func main() {
start := time.Now()
db, err := sql.Open("sqlite3", "/Users/robertking/go/src/bitbucket.org/thematicanalysis/optimization_test/robs.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query("select * from data")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
}
err = rows.Err()
if err != nil {
log.Fatal(err)
}
fmt.Println(time.Since(start))
}
Cela prend 8 secondes en Go parce que .Next
est lent . En python, une fetchall
ne prend que 4 secondes! Je suis en train de réécrire dans GO pour gagner en performance et non en perdre.
Voici le code python, je n'ai pas trouvé d'équivalent de fetchall
dans go:
import time
start = time.time()
import sqlite3
conn = sqlite3.connect('/Users/robertking/go/src/bitbucket.org/thematicanalysis/optimization_test/robs.db')
c = conn.cursor()
c.execute("SELECT * FROM data")
x = c.fetchall()
print time.time() - start
Edit: ajouter une prime. Je lis les données dans go, python et C, voici les résultats. Ne voulez pas utiliser C, mais restera avec python si GO n'est pas plus rapide.:
py: 2.45s
go: 2.13s (using github.com/mxk/go-sqlite/sqlite3 instead of github.com/mattn/go-sqlite3)
c: 0.32s
Je me sens comme aller devrait être plus proche du côté c de la chose? Quelqu'un sait comment faire plus vite? est-il possible d'éviter le mutex avec le mode lecture seule?
modifier:
Il semble que toutes les implémentations de sqlite3 soient lentes (trop de réflexions et trop d'appels à la conversion pour obtenir des conversions). Donc, je vais devoir écrire ma propre interface.
Voici le schéma:
CREATE TABLE mytable
(
c0 REAL,
c1 INTEGER,
c15 TEXT,
c16 TEXT,
c17 TEXT,
c18 TEXT,
c19 TEXT,
c47 TEXT,
c74 REAL DEFAULT 0,
c77 TEXT,
c101 TEXT,
c103 TEXT,
c108 TEXT,
c110 TEXT,
c125 TEXT,
c126 TEXT,
c127 REAL DEFAULT 0,
x INTEGER
PRIMARY KEY
);
et la requête est dynamique mais habituellement, quelque chose comme ceci:
SELECT c77,c77,c125,c126,c127,c74 from mytable
modifier:
on dirait que je vais bifurquer l'implémentation de sqlite3 et faire quelques méthodes qui se concentrent sur la performance,
voici un exemple de code beaucoup plus rapide:.
package main
/*
#cgo LDFLAGS: -l sqlite3
#include "sqlite3.h"
*/
import "C"
import (
//"database/sql"
"log"
"reflect"
"unsafe"
)
type Row struct {
v77 string
v125 string
v126 string
v127 float64
v74 float64
}
// cStr returns a pointer to the first byte in s.
func cStr(s string) *C.char {
h := (*reflect.StringHeader)(unsafe.Pointer(&s))
return (*C.char)(unsafe.Pointer(h.Data))
}
func main() {
getDataFromSqlite()
}
func getDataFromSqlite() {
var db *C.sqlite3
name := "../data_dbs/all_columns.db"
rc := C.sqlite3_open_v2(cStr(name+"\x00"), &db, C.SQLITE_OPEN_READONLY, nil)
var stmt *C.sqlite3_stmt;
rc = C.sqlite3_prepare_v2(db, cStr("SELECT c77,c125,c126,c127,c74 from data\x00"), C.int(-1), &stmt, nil);
rc = C.sqlite3_reset(stmt);
var result C.double
result = 0.0
rc = C.sqlite3_step(stmt)
for rc == C.SQLITE_ROW {
C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_text(stmt, 0))))
C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_text(stmt, 1))))
C.GoString((*C.char)(unsafe.Pointer(C.sqlite3_column_text(stmt, 2))))
C.sqlite3_column_double(stmt, 3)
result += C.sqlite3_column_double(stmt, 4)
rc = C.sqlite3_step(stmt)
}
log.Println(result)
}
Mon hypothèse était que nous avons un problème avec la façon dont la performance est mesurée ici, alors j'ai écrit un petit programme Go pour générer des enregistrements et les enregistrer dans une base de données SQLite ainsi qu'une implémentation Python et Go d'une petite tâche à effectuer sur ces enregistrements. .
Vous pouvez trouver le référentiel correspondant à https://github.com/mwmahlberg/sqlite3perf
Les enregistrements générés sont constitués de
ID
: A ID de ligne généré par SQLiteRand
: A hexa codé8 octetsvaleur pseudo-aléatoirehash
: un hex codéhash SHA256 de la non-codée Rand
Le schéma de la table est relativement simple:
sqlite> .schema
CREATE TABLE bench (ID int PRIMARY KEY ASC, Rand TEXT, hash TEXT);
Tout d’abord, j’ai généré 1,5 million d’enregistrements et passé l’aspirateur dans la base de
$ ./sqlite3perf generate -r 1500000 -v
Ensuite, j'ai appelé l'implémentation Go pour ces enregistrements de 1,5M. L’implémentation de Go ainsi que celle de Python remplissent essentiellement la même tâche simple:
Mon hypothèse était explicitement que Python effectuait un certain type de chargement paresseux et/ou éventuellement d’exécution de la requête SQL.
$ ./sqlite3perf bench
2017/12/31 15:21:48 bench called
2017/12/31 15:21:48 Time after query: 4.824009ms
2017/12/31 15:21:48 Beginning loop
2017/12/31 15:21:48 Acessing the first result set
ID 0,
Rand: 6a8a4ad02e5e872a,
hash: 571f1053a7c2aaa56e5c076e69389deb4db46cc08f5518c66a4bc593e62b9aa4
took 548.32µs
2017/12/31 15:21:50 641,664 rows processed
2017/12/31 15:21:52 1,325,186 rows processed
2017/12/31 15:21:53 1,500,000 rows processed
2017/12/31 15:21:53 Finished loop after 4.519083493s
2017/12/31 15:21:53 Average 3.015µs per record, 4.523936078s overall
Notez les valeurs de "time after query" (le temps nécessaire pour que la commande de requête revienne) et le temps nécessaire pour accéder au premier jeu de résultats après l'itération sur le jeu de résultats démarré.
$ python bench.py
12/31/2017 15:25:41 Starting up
12/31/2017 15:25:41 Time after query: 1874µs
12/31/2017 15:25:41 Beginning loop
12/31/2017 15:25:44 Accessing first result set
ID: 0
Rand: 6a8a4ad02e5e872a
hash: 571f1053a7c2aaa56e5c076e69389deb4db46cc08f5518c66a4bc593e62b9aa4
took 2.719312 s
12/31/2017 15:25:50 Finished loop after 9.147431s
12/31/2017 15:25:50 Average: 6.098µs per record, 0:00:09.149522 overall
Encore une fois, notez la valeur pour "temps après requête" et le temps nécessaire pour accéder au premier jeu de résultats.
L’implémentation de Go a mis un certain temps à revenir après l’envoi de la requête SELECT, tandis que Python semblait était extrêmement rapide en comparaison. Cependant, depuis le temps nécessaire pour accéder au premier jeu de résultats, nous pouvons constater que l'implémentation de Go est 500 fois plus rapide pour accéder au premier jeu de résultats (5.372329ms vs 2719.312ms) et environ deux fois plus rapide pour la tâche. à portée de main comme l'implémentation Python.
Python semble effectuer un chargement paresseux des jeux de résultats et même éventuellement n'exécuter une requête que si le jeu de résultats correspondant est réellement utilisé. Dans ce scénario simulé, le pilote SQLite de Mattn pour Go dépasse celui de Python d'environ 100% et de plusieurs ordres de grandeur, en fonction de ce que vous souhaitez faire.
Edit: Pour accélérer le traitement, implémentez votre tâche dans Go. Bien que l'envoi de la requête prenne plus de temps, accéder aux différentes lignes du jeu de résultats est bien plus rapide. Je suggérerais de commencer avec un petit sous-ensemble de vos données, par exemple 50 000 enregistrements. Ensuite, pour améliorer encore votre code, utilisez profiling pour identifier vos goulots d'étranglement. En fonction de ce que vous voulez faire pendant le traitement, les pipelines peuvent par exemple vous aider, mais il est difficile de dire comment améliorer la vitesse de traitement de la tâche à exécuter sans un code réel ou une description détaillée.
Analyse des valeurs des lignes extraites exemple de lecture étape 10 .
Puisque Query()
& QueryRow()
renvoie un pointeur sur les lignes et un pointeur sur Row de la requête de base de données, nous pouvons utiliser la structure Scan () sur les lignes et les lignes pour obtenir un accès aux valeurs de la structure des lignes.
for rows.Next() {
var empID sql.NullInt64
var empName sql.NullString
var empAge sql.NullInt64
var empPersonId sql.NullInt64
if err := rows.Scan(&empID, &empName, &empAge,
&empPersonId); err != nil {
log.Fatal(err)
}
fmt.Printf("ID %d with personID:%d & name %s is age %d\n",
empID.Int64, empPersonId.Int64, empName.String, empAge.Int64)
}
Nous avons également utilisé la fonction Scan () de la structure Row. Scan () est la seule méthode déclarée dans la structure de lignes.
func (r *Row) Scan(dest ...interface{}) error