web-dev-qa-db-ja.com

Goでのより高速なsqlite 3クエリ? 100万行以上をできるだけ速く処理する必要がある

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

.Nextslow であるため、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)
}
24
robert king

前書き

私の想定では、ここでのパフォーマンスの測定方法に問題があるため、レコードを生成してSQLiteデータベースに保存する小さなGoプログラムと、PythonとGoを実装して小さなタスクを実行するそれらのレコードで行います。

対応するリポジトリは https://github.com/mwmahlberg/sqlite3perf にあります

データモデル

生成されるレコードは、

テーブルのスキーマは比較的単純です。

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の実装は、基本的に同じ単純なタスクを実行します。

  1. データベースからすべてのエントリを読み取ります。
  2. 各行について、16進数からランダムな値をデコードし、結果からSHA256 16進数を作成します。
  3. 生成されたSHA256 16進数文字列をデータベースに保存されているものと比較します
  4. 一致する場合は続行し、それ以外の場合は中断します。

仮定

私の明示的な仮定は、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の実装

$ 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が結果セットで遅延読み込みを実際に行うという仮定を証明するために、Pythonが実際に値を読み取るように強制するために、すべての行と列にアクセスする必要がありました。データベース。
  • おそらくSHA256の実装が両方の言語で高度に最適化されているため、ハッシュタスクを選択しました。

結論

Pythonは結果セットの遅延読み込みを行うように見え、一致する結果セットが実際にアクセスされない限り、クエリを実行することすらありません。このシミュレートされたシナリオでは、GoのmattnのSQLiteドライバーは、何をしたいかに応じて、Pythonのパフォーマンスを約100%から桁違いに向上させます。

編集:処理を高速化するために、Goでタスクを実装します。実際のクエリの送信には時間がかかりますが、結果セットの個々の行へのアクセスははるかに高速です。データの小さなサブセット、たとえば5万件のレコードから始めることをお勧めします。次に、コードをさらに改善するには、 profiling を使用してボトルネックを特定します。処理中に何をしたいかにもよりますが、たとえば pipelines は役立つかもしれませんが、実際のコードや完全な説明なしに、目前のタスクの処理速度を向上させる方法を語るのは困難です。

23

取得した行からの値のスキャン 例の読み取り手順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
1
5377037