Golangでsqlite3テーブルを読み取る最も速い方法は何ですか?
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))
}
.Next
は slow であるため、Goでは8秒かかります。 python aでfetchall
は4秒しかかかりません!パフォーマンスを落とさずにパフォーマンスを上げるためにGOで書き換えています。
これはpythonコードで、goでfetchall
に相当するものを見つけることができませんでした:
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
編集:バウンティを追加します。私はgoでデータを読んでいます、pythonとC、ここに結果があります。Cを使用したくないが、GOが高速でない場合はpythonのままです。 。:
py: 2.45s
go: 2.13s (using github.com/mxk/go-sqlite/sqlite3 instead of github.com/mattn/go-sqlite3)
c: 0.32s
私は行くのが物事のc側に近づくべきだと思いますか?誰でもそれを速くする方法を知っていますか?読み取り専用モードでミューテックスを回避することは可能ですか?
編集:
すべてのsqlite3実装が遅いようです(反映が多すぎ、変換のためのcgo呼び出しが多すぎます)。だから私は自分のインターフェースを書く必要があるだけです。
スキーマは次のとおりです。
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
);
クエリは動的ですが、通常は次のようになります。
SELECT c77,c77,c125,c126,c127,c74 from mytable
編集:
私はsqlite3実装をフォークしてパフォーマンスに焦点を合わせるいくつかのメソッドを作るように見えます、
これは、はるかに高速なコードの例です。
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)
}
私の想定では、ここでのパフォーマンスの測定方法に問題があるため、レコードを生成してSQLiteデータベースに保存する小さなGoプログラムと、PythonとGoを実装して小さなタスクを実行するそれらのレコードで行います。
対応するリポジトリは https://github.com/mwmahlberg/sqlite3perf にあります
生成されるレコードは、
ID
:A SQLiteによって生成された行IDRand
:A 16進エンコード8バイト疑似ランダム値hash
:A 16進エンコードエンコードされていないRand
)のSHA256ハッシュテーブルのスキーマは比較的単純です。
sqlite> .schema
CREATE TABLE bench (ID int PRIMARY KEY ASC, Rand TEXT, hash TEXT);
最初に1.5Mのレコードを生成し、後でsqliteデータベースをバキュームしました
$ ./sqlite3perf generate -r 1500000 -v
次に、これらの1.5Mレコードに対してGo実装を呼び出しました。 GoとPythonの実装は、基本的に同じ単純なタスクを実行します。
私の明示的な仮定は、Pythonがなんらかのタイプの遅延ロードや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
「クエリ後の時間」(クエリコマンドが戻るのにかかる時間)の値と、結果セットの反復が開始されてから最初の結果セットにアクセスするのにかかる時間に注意してください。
$ 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
ここでも、「クエリ後の時間」の値と、最初の結果セットにアクセスするのにかかった時間に注意してください。
Pythonseemedが比較すると非常に高速である一方で、SELECTクエリが送信された後にGo実装が戻るまでにかなりの時間がかかりました。ただし、最初の結果セットに実際にアクセスするのにかかった時間から、Goの実装は最初の結果セットに実際にアクセスするよりも500倍以上高速であり(5.372329ms対2719.312ms)、タスクの速度は約2倍であることがわかります。 Python実装として手元に。
Pythonは結果セットの遅延読み込みを行うように見え、一致する結果セットが実際にアクセスされない限り、クエリを実行することすらありません。このシミュレートされたシナリオでは、GoのmattnのSQLiteドライバーは、何をしたいかに応じて、Pythonのパフォーマンスを約100%から桁違いに向上させます。
編集:処理を高速化するために、Goでタスクを実装します。実際のクエリの送信には時間がかかりますが、結果セットの個々の行へのアクセスははるかに高速です。データの小さなサブセット、たとえば5万件のレコードから始めることをお勧めします。次に、コードをさらに改善するには、 profiling を使用してボトルネックを特定します。処理中に何をしたいかにもよりますが、たとえば pipelines は役立つかもしれませんが、実際のコードや完全な説明なしに、目前のタスクの処理速度を向上させる方法を語るのは困難です。
取得した行からの値のスキャン 例の読み取り手順1 。Query()
&QueryRow()
はそれぞれデータベースクエリからRowsへのポインターとRowへのポインターを返すため、 Scan() を使用できます。 Rows構造体の値にアクセスするためのRowsおよびRow構造体の関数。
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)
}
Row構造体のScan()関数も使用しました。 Scan()は、行構造体で宣言されている唯一のメソッドです。
func (r *Row) Scan(dest ...interface{}) error