multiple - sqlite3 create database




Più veloce query sqlite 3 in go? Devo elaborare 1 milione di righe più velocemente possibile (2)

Qual è il modo più veloce per leggere un tavolo sqlite3 in 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))
}

Ciò richiede 8 secondi in Go perché. .Next è slow . In Python un fetchall richiede solo 4 secondi! Sto riscrivendo in GO per ottenere prestazioni senza perdere prestazioni.

Ecco il codice Python, non sono riuscito a trovare un equivalente di fetchall in 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

Modifica: aggiunta di taglie. Sto leggendo i dati in go, python e C, ecco i risultati. Non voglio usare C, ma rimarrà con Python se GO non è più veloce .:

py: 2.45s
go: 2.13s (using github.com/mxk/go-sqlite/sqlite3 instead of github.com/mattn/go-sqlite3)
c:  0.32s

Ho voglia di andare dovrebbe essere più vicino al lato c della cosa? qualcuno sa come renderlo più veloce? è possibile evitare il mutex con la modalità readonly?

modificare:

Sembra che tutte le implementazioni di sqlite3 siano lente (troppa riflessione e troppe chiamate Cgo per le conversioni). Quindi dovrò solo scrivere la mia interfaccia.

Ecco lo schema:

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
);

e la query è dinamica ma di solito qualcosa del genere:

SELECT c77,c77,c125,c126,c127,c74 from mytable

modificare:

sembra che forzerò l'implementazione sqlite3 e realizzerò alcuni metodi incentrati sulle prestazioni,

questo è un esempio di codice che è molto più veloce:

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)
}

introduzione

La mia ipotesi era che abbiamo un problema con il modo in cui viene misurata la performance qui, quindi ho scritto un piccolo programma Go per generare record e salvarli in un database SQLite e un'implementazione Python and Go di un piccolo compito da fare su quei record .

È possibile trovare il repository https://github.com/mwmahlberg/sqlite3perf su https://github.com/mwmahlberg/sqlite3perf

Il modello dei dati

I record generati consistono in

Lo schema della tabella è relativamente semplice:

sqlite> .schema
CREATE TABLE bench (ID int PRIMARY KEY ASC, rand TEXT, hash TEXT);

Per prima cosa ho generato record di 1,5 milioni e successivamente ho passato l'aspirapolvere al database sqlite

$ ./sqlite3perf generate -r 1500000 -v

Successivamente ho chiamato l'implementazione Go contro quei record di 1,5 milioni. Sia l'implementazione Go che l'implementazione Python fanno sostanzialmente lo stesso semplice compito:

  1. Leggi tutte le voci dal database.
  2. Per ogni riga, decodifica il valore casuale da esadecimale, quindi crea un esagono SHA256 dal risultato.
  3. Confronta la stringa esadecimale SHA256 generata con quella memorizzata nel database
  4. Se corrispondono, continua, altrimenti si rompono.

ipotesi

La mia ipotesi era esplicitamente che Python eseguisse un tipo di caricamento lazy e / o forse addirittura l'esecuzione della query SQL.

I risultati

Vai all'attuazione

$ ./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

Notare i valori per "tempo dopo query" (il tempo impiegato dal comando query per tornare) e il tempo impiegato per accedere al primo set di risultati dopo l'iterazione sul set di risultati.

Implementazione Python

$ 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

Di nuovo, si noti il ​​valore per "tempo dopo query" e il tempo impiegato per accedere al primo set di risultati.

Sommario

Ci è voluto un po 'di tempo prima che l'implementazione di Go tornasse dopo che la query SELECT era stata inviata, mentre Python sembrava essere molto veloce al confronto. Tuttavia, dal tempo impiegato per accedere effettivamente al primo set di risultati, possiamo vedere che l'implementazione Go è più di 500 volte più veloce per accedere effettivamente al primo set di risultati (5.372329 ms vs 2719.312 ms) e circa il doppio più veloce per l'attività. a portata di mano come l'implementazione Python.

Gli appunti

  • Per dimostrare l'ipotesi che Python esegua effettivamente il caricamento lazy sul set di risultati, è necessario accedere a ciascuna riga e colonna per assicurarsi che Python sia forzato a leggere effettivamente il valore dal database.
  • Ho scelto un'attività di hashing perché presumibilmente l'implementazione di SHA256 è altamente ottimizzata in entrambe le lingue.

Conclusione

Python sembra eseguire il caricamento lazy dei set di risultati e probabilmente non esegue nemmeno una query a meno che non si acceda effettivamente al set di risultati corrispondente. In questo scenario simulato, il driver SQLite di Mattn per Go supera quello di Python di circa il 100% e gli ordini di grandezza, a seconda di cosa si vuole fare.

Modifica : quindi, per avere un'elaborazione veloce, implementa il tuo compito in Go. Mentre richiede più tempo per inviare la query effettiva, l'accesso alle singole righe del set di risultati è di gran lunga più veloce. Suggerirei di iniziare con un piccolo sottoinsieme di dati, ad esempio 50k record. Quindi, per migliorare ulteriormente il codice, utilizzare la profiling di profiling per identificare i colli di bottiglia. A seconda di ciò che si vuole fare durante l'elaborazione, le pipelines , ad esempio, potrebbero aiutare, ma è difficile dire come migliorare la velocità di elaborazione del compito a portata di mano senza un codice reale o una descrizione approfondita.


Scansione dei valori dall'esempio delle righe recuperate leggere il passaggio 10 .
Poiché Query() e QueryRow() restituiscono rispettivamente un puntatore a Rows e un puntatore a Row dalla query del database, possiamo utilizzare le funzioni Scan() su struttura di righe e righe per ottenere l'accesso ai valori nella struttura delle righe.

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)
}

Abbiamo anche utilizzato la funzione Scan () dalla struttura Row. Scan () è l'unico metodo dichiarato nel Row Struct.

func (r *Row) Scan(dest ...interface{}) error




go