extralite 2.5 → 2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +34 -13
- data/Gemfile +4 -0
- data/Gemfile-bundle +1 -1
- data/LICENSE +1 -1
- data/README.md +1059 -247
- data/Rakefile +18 -0
- data/TODO.md +0 -7
- data/examples/kv_store.rb +49 -0
- data/examples/multi_fiber.rb +16 -0
- data/examples/on_progress.rb +9 -0
- data/examples/pubsub_store_polyphony.rb +194 -0
- data/examples/pubsub_store_threads.rb +204 -0
- data/ext/extralite/changeset.c +463 -0
- data/ext/extralite/common.c +177 -91
- data/ext/extralite/database.c +745 -276
- data/ext/extralite/extconf-bundle.rb +10 -4
- data/ext/extralite/extconf.rb +34 -34
- data/ext/extralite/extralite.h +104 -47
- data/ext/extralite/extralite_ext.c +6 -0
- data/ext/extralite/iterator.c +14 -86
- data/ext/extralite/query.c +171 -264
- data/extralite-bundle.gemspec +1 -1
- data/extralite.gemspec +1 -1
- data/gemspec.rb +10 -11
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +69 -10
- data/lib/sequel/adapters/extralite.rb +1 -1
- data/test/helper.rb +9 -1
- data/test/perf_argv_transform.rb +74 -0
- data/test/perf_ary.rb +14 -12
- data/test/perf_hash.rb +17 -15
- data/test/perf_hash_prepared.rb +58 -0
- data/test/perf_hash_transform.rb +66 -0
- data/test/perf_polyphony.rb +74 -0
- data/test/test_changeset.rb +161 -0
- data/test/test_database.rb +720 -104
- data/test/test_extralite.rb +2 -2
- data/test/test_iterator.rb +28 -13
- data/test/test_query.rb +352 -110
- data/test/test_sequel.rb +4 -4
- metadata +24 -16
- data/Gemfile.lock +0 -37
- data/test/perf_prepared.rb +0 -64
data/gemspec.rb
CHANGED
@@ -8,19 +8,18 @@ def common_spec(s)
|
|
8
8
|
s.files = `git ls-files`.split
|
9
9
|
s.homepage = 'https://github.com/digital-fabric/extralite'
|
10
10
|
s.metadata = {
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
"changelog_uri" => "https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md"
|
11
|
+
'homepage_uri' => 'https://github.com/digital-fabric/extralite',
|
12
|
+
'documentation_uri' => 'https://www.rubydoc.info/gems/extralite',
|
13
|
+
'changelog_uri' => 'https://github.com/digital-fabric/extralite/blob/master/CHANGELOG.md'
|
15
14
|
}
|
16
|
-
s.rdoc_options = [
|
17
|
-
s.extra_rdoc_files = [
|
18
|
-
s.require_paths = [
|
15
|
+
s.rdoc_options = ['--title', 'Extralite', '--main', 'README.md']
|
16
|
+
s.extra_rdoc_files = ['README.md']
|
17
|
+
s.require_paths = ['lib']
|
19
18
|
s.required_ruby_version = '>= 3.0'
|
20
19
|
|
21
|
-
s.add_development_dependency 'rake-compiler', '1.
|
22
|
-
s.add_development_dependency 'minitest', '5.
|
20
|
+
s.add_development_dependency 'rake-compiler', '1.2.7'
|
21
|
+
s.add_development_dependency 'minitest', '5.21.2'
|
23
22
|
s.add_development_dependency 'simplecov', '0.17.1'
|
24
|
-
s.add_development_dependency 'yard', '0.9.
|
25
|
-
s.add_development_dependency 'sequel', '5.
|
23
|
+
s.add_development_dependency 'yard', '0.9.34'
|
24
|
+
s.add_development_dependency 'sequel', '5.77.0'
|
26
25
|
end
|
data/lib/extralite/version.rb
CHANGED
data/lib/extralite.rb
CHANGED
@@ -29,10 +29,10 @@ module Extralite
|
|
29
29
|
class ParameterError < Error
|
30
30
|
end
|
31
31
|
|
32
|
-
#
|
32
|
+
# This class encapsulates an SQLite database connection.
|
33
33
|
class Database
|
34
34
|
# @!visibility private
|
35
|
-
TABLES_SQL = <<~SQL
|
35
|
+
TABLES_SQL = (<<~SQL).freeze
|
36
36
|
SELECT name FROM %<db>s.sqlite_master
|
37
37
|
WHERE type ='table'
|
38
38
|
AND name NOT LIKE 'sqlite_%%';
|
@@ -46,10 +46,11 @@ module Extralite
|
|
46
46
|
# @param db [String] name of attached database
|
47
47
|
# @return [Array] list of tables
|
48
48
|
def tables(db = 'main')
|
49
|
-
|
49
|
+
query_argv(format(TABLES_SQL, db: db))
|
50
50
|
end
|
51
51
|
|
52
|
-
# Gets or sets one or more pragmas
|
52
|
+
# Gets or sets one or more database pragmas. For a list of available pragmas
|
53
|
+
# see: https://sqlite.org/pragma.html#toc
|
53
54
|
#
|
54
55
|
# db.pragma(:cache_size) # get
|
55
56
|
# db.pragma(cache_size: -2000) # set
|
@@ -60,6 +61,11 @@ module Extralite
|
|
60
61
|
value.is_a?(Hash) ? pragma_set(value) : pragma_get(value)
|
61
62
|
end
|
62
63
|
|
64
|
+
# Error class used to roll back a transaction without propagating an
|
65
|
+
# exception.
|
66
|
+
class Rollback < Error
|
67
|
+
end
|
68
|
+
|
63
69
|
# Starts a transaction and runs the given block. If an exception is raised
|
64
70
|
# in the block, the transaction is rolled back. Otherwise, the transaction
|
65
71
|
# is commited after running the block.
|
@@ -69,20 +75,73 @@ module Extralite
|
|
69
75
|
# raise if db.query_single_value('select x from bar') > 42
|
70
76
|
# end
|
71
77
|
#
|
72
|
-
#
|
78
|
+
# For more information on transactions see:
|
79
|
+
# https://sqlite.org/lang_transaction.html
|
80
|
+
#
|
81
|
+
# @param mode [Symbol, String] transaction mode (deferred, immediate or exclusive).
|
73
82
|
# @return [Any] the given block's return value
|
74
83
|
def transaction(mode = :immediate)
|
75
|
-
execute "begin #{mode} transaction"
|
76
|
-
|
77
84
|
abort = false
|
85
|
+
execute "begin #{mode} transaction"
|
78
86
|
yield self
|
79
|
-
rescue
|
87
|
+
rescue => e
|
80
88
|
abort = true
|
81
|
-
raise
|
89
|
+
e.is_a?(Rollback) ? nil : raise
|
82
90
|
ensure
|
83
91
|
execute(abort ? 'rollback' : 'commit')
|
84
92
|
end
|
85
93
|
|
94
|
+
# Creates a savepoint with the given name. For more information on
|
95
|
+
# savepoints see: https://sqlite.org/lang_savepoint.html
|
96
|
+
#
|
97
|
+
# db.savepoint(:savepoint1)
|
98
|
+
# db.execute('insert into foo values (42)')
|
99
|
+
# db.rollback_to(:savepoint1)
|
100
|
+
# db.release(:savepoint1)
|
101
|
+
#
|
102
|
+
# @param name [String, Symbol] savepoint name
|
103
|
+
# @return [Extralite::Database] database
|
104
|
+
def savepoint(name)
|
105
|
+
execute "savepoint #{name}"
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
# Release a savepoint with the given name. For more information on
|
110
|
+
# savepoints see: https://sqlite.org/lang_savepoint.html
|
111
|
+
#
|
112
|
+
# @param name [String, Symbol] savepoint name
|
113
|
+
# @return [Extralite::Database] database
|
114
|
+
def release(name)
|
115
|
+
execute "release #{name}"
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
# Rolls back changes to a savepoint with the given name. For more
|
120
|
+
# information on savepoints see: https://sqlite.org/lang_savepoint.html
|
121
|
+
#
|
122
|
+
# @param name [String, Symbol] savepoint name
|
123
|
+
# @return [Extralite::Database] database
|
124
|
+
def rollback_to(name)
|
125
|
+
execute "rollback to #{name}"
|
126
|
+
self
|
127
|
+
end
|
128
|
+
|
129
|
+
# Rolls back the currently active transaction. This method should only be
|
130
|
+
# called from within a block passed to `Database#transaction`. This method
|
131
|
+
# raises a Extralite::Rollback exception, which will stop execution of the
|
132
|
+
# transaction block without propagating the exception.
|
133
|
+
#
|
134
|
+
# db.transaction do
|
135
|
+
# db.execute('insert into foo (42)')
|
136
|
+
# db.rollback!
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# @param name [String, Symbol] savepoint name
|
140
|
+
# @return [Extralite::Database] database
|
141
|
+
def rollback!
|
142
|
+
raise Rollback
|
143
|
+
end
|
144
|
+
|
86
145
|
private
|
87
146
|
|
88
147
|
def pragma_set(values)
|
@@ -91,7 +150,7 @@ module Extralite
|
|
91
150
|
end
|
92
151
|
|
93
152
|
def pragma_get(key)
|
94
|
-
|
153
|
+
query_single_argv("pragma #{key}")
|
95
154
|
end
|
96
155
|
end
|
97
156
|
|
data/test/helper.rb
CHANGED
@@ -7,4 +7,12 @@ require 'minitest/autorun'
|
|
7
7
|
puts "sqlite3 version: #{Extralite.sqlite3_version}"
|
8
8
|
|
9
9
|
IS_LINUX = RUBY_PLATFORM =~ /linux/
|
10
|
-
|
10
|
+
# Ractors are kinda flaky, there's no point in testing this
|
11
|
+
SKIP_RACTOR_TESTS = true #!IS_LINUX || (RUBY_VERSION =~ /^3\.[01]/)
|
12
|
+
|
13
|
+
module Minitest::Assertions
|
14
|
+
def assert_in_range exp_range, act
|
15
|
+
msg = message(msg) { "Expected #{mu_pp(act)} to be in range #{mu_pp(exp_range)}" }
|
16
|
+
assert exp_range.include?(act), msg
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Run on Ruby 3.3 with YJIT enabled
|
4
|
+
|
5
|
+
require 'bundler/inline'
|
6
|
+
|
7
|
+
gemfile do
|
8
|
+
source 'https://rubygems.org'
|
9
|
+
gem 'extralite', path: '..'
|
10
|
+
gem 'benchmark-ips'
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'benchmark/ips'
|
14
|
+
require 'fileutils'
|
15
|
+
|
16
|
+
DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
|
17
|
+
puts "DB_PATH = #{DB_PATH.inspect}"
|
18
|
+
|
19
|
+
$extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
|
20
|
+
|
21
|
+
def prepare_database(count)
|
22
|
+
$extralite_db.query('create table if not exists foo (b text)')
|
23
|
+
$extralite_db.query('delete from foo')
|
24
|
+
$extralite_db.query('begin')
|
25
|
+
count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
26
|
+
$extralite_db.query('commit')
|
27
|
+
end
|
28
|
+
|
29
|
+
class Model
|
30
|
+
def initialize(h)
|
31
|
+
@h = h
|
32
|
+
end
|
33
|
+
|
34
|
+
def values
|
35
|
+
@h
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
TRANSFORM = ->(b) { { b: b } }
|
40
|
+
|
41
|
+
def extralite_run_ary_map(count)
|
42
|
+
results = []
|
43
|
+
$extralite_db.query_ary('select * from foo') { |(b)| results << { b: b } }
|
44
|
+
raise unless results.size == count
|
45
|
+
end
|
46
|
+
|
47
|
+
def extralite_run_argv_map(count)
|
48
|
+
results = []
|
49
|
+
$extralite_db.query_argv('select * from foo') { |b| results << { b: b } }
|
50
|
+
raise unless results.size == count
|
51
|
+
end
|
52
|
+
|
53
|
+
def extralite_run_transform(count)
|
54
|
+
results = $extralite_db.query_argv(TRANSFORM, 'select * from foo')
|
55
|
+
raise unless results.size == count
|
56
|
+
end
|
57
|
+
|
58
|
+
[10, 1000, 100000].each do |c|
|
59
|
+
puts "Record count: #{c}"
|
60
|
+
prepare_database(c)
|
61
|
+
|
62
|
+
bm = Benchmark.ips do |x|
|
63
|
+
x.config(:time => 5, :warmup => 2)
|
64
|
+
|
65
|
+
x.report("ary_map") { extralite_run_ary_map(c) }
|
66
|
+
x.report("argv_map") { extralite_run_argv_map(c) }
|
67
|
+
x.report("transform") { extralite_run_transform(c) }
|
68
|
+
|
69
|
+
x.compare!
|
70
|
+
end
|
71
|
+
puts;
|
72
|
+
bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
|
73
|
+
puts;
|
74
|
+
end
|
data/test/perf_ary.rb
CHANGED
@@ -15,26 +15,25 @@ require 'fileutils'
|
|
15
15
|
DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
|
16
16
|
puts "DB_PATH = #{DB_PATH.inspect}"
|
17
17
|
|
18
|
+
$sqlite3_db = SQLite3::Database.new(DB_PATH)
|
19
|
+
$extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
|
18
20
|
|
19
21
|
def prepare_database(count)
|
20
22
|
db = Extralite::Database.new(DB_PATH)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
count.times {
|
25
|
-
|
26
|
-
db.close
|
23
|
+
$extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
|
24
|
+
$extralite_db.query('delete from foo')
|
25
|
+
$extralite_db.query('begin')
|
26
|
+
count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
27
|
+
$extralite_db.query('commit')
|
27
28
|
end
|
28
29
|
|
29
30
|
def sqlite3_run(count)
|
30
|
-
|
31
|
-
results = db.execute('select * from foo')
|
31
|
+
results = $sqlite3_db.execute('select * from foo')
|
32
32
|
raise unless results.size == count
|
33
33
|
end
|
34
34
|
|
35
35
|
def extralite_run(count)
|
36
|
-
|
37
|
-
results = db.query_ary('select * from foo')
|
36
|
+
results = $extralite_db.query('select * from foo')
|
38
37
|
raise unless results.size == count
|
39
38
|
end
|
40
39
|
|
@@ -43,12 +42,15 @@ end
|
|
43
42
|
|
44
43
|
prepare_database(c)
|
45
44
|
|
46
|
-
Benchmark.ips do |x|
|
47
|
-
x.config(:time =>
|
45
|
+
bm = Benchmark.ips do |x|
|
46
|
+
x.config(:time => 5, :warmup => 2)
|
48
47
|
|
49
48
|
x.report("sqlite3") { sqlite3_run(c) }
|
50
49
|
x.report("extralite") { extralite_run(c) }
|
51
50
|
|
52
51
|
x.compare!
|
53
52
|
end
|
53
|
+
puts;
|
54
|
+
bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
|
55
|
+
puts;
|
54
56
|
end
|
data/test/perf_hash.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Run on Ruby 3.3 with YJIT enabled
|
4
|
+
|
3
5
|
require 'bundler/inline'
|
4
6
|
|
5
7
|
gemfile do
|
@@ -15,40 +17,40 @@ require 'fileutils'
|
|
15
17
|
DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
|
16
18
|
puts "DB_PATH = #{DB_PATH.inspect}"
|
17
19
|
|
20
|
+
$sqlite3_db = SQLite3::Database.new(DB_PATH, results_as_hash: true)
|
21
|
+
$extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
|
22
|
+
|
18
23
|
def prepare_database(count)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
db.query('commit')
|
25
|
-
db.close
|
24
|
+
$extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
|
25
|
+
$extralite_db.query('delete from foo')
|
26
|
+
$extralite_db.query('begin')
|
27
|
+
count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
28
|
+
$extralite_db.query('commit')
|
26
29
|
end
|
27
30
|
|
28
31
|
def sqlite3_run(count)
|
29
|
-
|
30
|
-
results = db.execute('select * from foo')
|
32
|
+
results = $sqlite3_db.execute('select * from foo')
|
31
33
|
raise unless results.size == count
|
32
34
|
end
|
33
35
|
|
34
36
|
def extralite_run(count)
|
35
|
-
|
36
|
-
results = db.query('select * from foo')
|
37
|
+
results = $extralite_db.query('select * from foo')
|
37
38
|
raise unless results.size == count
|
38
39
|
end
|
39
40
|
|
40
41
|
[10, 1000, 100000].each do |c|
|
41
42
|
puts "Record count: #{c}"
|
42
|
-
|
43
43
|
prepare_database(c)
|
44
44
|
|
45
|
-
Benchmark.ips do |x|
|
46
|
-
x.config(:time =>
|
45
|
+
bm = Benchmark.ips do |x|
|
46
|
+
x.config(:time => 5, :warmup => 2)
|
47
47
|
|
48
48
|
x.report("sqlite3") { sqlite3_run(c) }
|
49
49
|
x.report("extralite") { extralite_run(c) }
|
50
50
|
|
51
51
|
x.compare!
|
52
52
|
end
|
53
|
-
puts;
|
53
|
+
puts;
|
54
|
+
bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
|
55
|
+
puts;
|
54
56
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
gem 'extralite', path: '..'
|
8
|
+
gem 'sqlite3'
|
9
|
+
gem 'benchmark-ips'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'benchmark/ips'
|
13
|
+
require 'fileutils'
|
14
|
+
|
15
|
+
DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
|
16
|
+
puts "DB_PATH = #{DB_PATH.inspect}"
|
17
|
+
|
18
|
+
def prepare_database(count)
|
19
|
+
$sqlite3_db = SQLite3::Database.new(DB_PATH, results_as_hash: true)
|
20
|
+
$extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
|
21
|
+
|
22
|
+
$extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
|
23
|
+
$extralite_db.query('delete from foo')
|
24
|
+
$extralite_db.query('begin')
|
25
|
+
count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
26
|
+
$extralite_db.query('commit')
|
27
|
+
|
28
|
+
$sqlite3_stmt = $sqlite3_db.prepare('select * from foo')
|
29
|
+
$extralite_q = $extralite_db.prepare('select * from foo')
|
30
|
+
end
|
31
|
+
|
32
|
+
def sqlite3_run(count)
|
33
|
+
results = $sqlite3_stmt.execute
|
34
|
+
raise unless results.to_a.size == count
|
35
|
+
end
|
36
|
+
|
37
|
+
def extralite_run(count)
|
38
|
+
results = $extralite_q.to_a
|
39
|
+
raise unless results.size == count
|
40
|
+
end
|
41
|
+
|
42
|
+
[10, 1000, 100000].each do |c|
|
43
|
+
puts "Record count: #{c}"
|
44
|
+
|
45
|
+
prepare_database(c)
|
46
|
+
|
47
|
+
bm = Benchmark.ips do |x|
|
48
|
+
x.config(:time => 5, :warmup => 2)
|
49
|
+
|
50
|
+
x.report("sqlite3") { sqlite3_run(c) }
|
51
|
+
x.report("extralite") { extralite_run(c) }
|
52
|
+
|
53
|
+
x.compare!
|
54
|
+
end
|
55
|
+
puts;
|
56
|
+
bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
|
57
|
+
puts;
|
58
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Run on Ruby 3.3 with YJIT enabled
|
4
|
+
|
5
|
+
require 'bundler/inline'
|
6
|
+
|
7
|
+
gemfile do
|
8
|
+
source 'https://rubygems.org'
|
9
|
+
gem 'extralite', path: '..'
|
10
|
+
gem 'benchmark-ips'
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'benchmark/ips'
|
14
|
+
require 'fileutils'
|
15
|
+
|
16
|
+
DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
|
17
|
+
puts "DB_PATH = #{DB_PATH.inspect}"
|
18
|
+
|
19
|
+
$extralite_db = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
|
20
|
+
|
21
|
+
def prepare_database(count)
|
22
|
+
$extralite_db.query('create table if not exists foo ( a integer primary key, b text )')
|
23
|
+
$extralite_db.query('delete from foo')
|
24
|
+
$extralite_db.query('begin')
|
25
|
+
count.times { $extralite_db.query('insert into foo (b) values (?)', "hello#{rand(1000)}" )}
|
26
|
+
$extralite_db.query('commit')
|
27
|
+
end
|
28
|
+
|
29
|
+
class Model
|
30
|
+
def initialize(h)
|
31
|
+
@h = h
|
32
|
+
end
|
33
|
+
|
34
|
+
def values
|
35
|
+
@h
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
TRANSFORM = ->(h) { Model.new(h) }
|
40
|
+
|
41
|
+
def extralite_run_map(count)
|
42
|
+
results = $extralite_db.query('select * from foo').map(&TRANSFORM)
|
43
|
+
raise unless results.size == count
|
44
|
+
end
|
45
|
+
|
46
|
+
def extralite_run_transform(count)
|
47
|
+
results = $extralite_db.query(TRANSFORM, 'select * from foo')
|
48
|
+
raise unless results.size == count
|
49
|
+
end
|
50
|
+
|
51
|
+
[10, 1000, 100000].each do |c|
|
52
|
+
puts "Record count: #{c}"
|
53
|
+
prepare_database(c)
|
54
|
+
|
55
|
+
bm = Benchmark.ips do |x|
|
56
|
+
x.config(:time => 5, :warmup => 2)
|
57
|
+
|
58
|
+
x.report("map") { extralite_run_map(c) }
|
59
|
+
x.report("transform") { extralite_run_transform(c) }
|
60
|
+
|
61
|
+
x.compare!
|
62
|
+
end
|
63
|
+
puts;
|
64
|
+
bm.entries.each { |e| puts "#{e.label}: #{(e.ips * c).round.to_i} rows/s" }
|
65
|
+
puts;
|
66
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Run on Ruby 3.3 with YJIT enabled
|
4
|
+
|
5
|
+
require 'bundler/inline'
|
6
|
+
gemfile do
|
7
|
+
gem 'polyphony'
|
8
|
+
gem 'extralite', path: '.'
|
9
|
+
gem 'benchmark-ips'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'benchmark/ips'
|
13
|
+
require 'polyphony'
|
14
|
+
|
15
|
+
DB_PATH = "/tmp/extralite_sqlite3_perf-#{Time.now.to_i}-#{rand(10000)}.db"
|
16
|
+
puts "DB_PATH = #{DB_PATH.inspect}"
|
17
|
+
|
18
|
+
$db1 = Extralite::Database.new(DB_PATH, gvl_release_threshold: -1)
|
19
|
+
$db2 = Extralite::Database.new(DB_PATH, gvl_release_threshold: 0)
|
20
|
+
$db3 = Extralite::Database.new(DB_PATH)
|
21
|
+
|
22
|
+
$snooze_count = 0
|
23
|
+
$db3.on_progress(25) { $snooze_count += 1; snooze }
|
24
|
+
|
25
|
+
def prepare_database(count)
|
26
|
+
$db1.execute('create table if not exists foo ( a integer primary key, b text )')
|
27
|
+
$db1.transaction do
|
28
|
+
$db1.execute('delete from foo')
|
29
|
+
rows = count.times.map { "hello#{rand(1000)}" }
|
30
|
+
$db1.batch_execute('insert into foo (b) values (?)', rows)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def extralite_run1(count)
|
35
|
+
results = $db1.query('select * from foo')
|
36
|
+
raise unless results.size == count
|
37
|
+
end
|
38
|
+
|
39
|
+
def extralite_run2(count)
|
40
|
+
results = $db2.query('select * from foo')
|
41
|
+
raise unless results.size == count
|
42
|
+
end
|
43
|
+
|
44
|
+
def extralite_run3(count)
|
45
|
+
results = $db3.query('select * from foo')
|
46
|
+
raise unless results.size == count
|
47
|
+
end
|
48
|
+
|
49
|
+
[10, 1000, 100000].each do |c|
|
50
|
+
puts "Record count: #{c}"
|
51
|
+
prepare_database(c)
|
52
|
+
|
53
|
+
bm = Benchmark.ips do |x|
|
54
|
+
x.config(:time => 3, :warmup => 1)
|
55
|
+
|
56
|
+
x.report('GVL threshold -1') { extralite_run1(c) }
|
57
|
+
x.report('GVL threshold 0') { extralite_run2(c) }
|
58
|
+
$snooze_count = 0
|
59
|
+
x.report('on_progress 1000') { extralite_run3(c) }
|
60
|
+
|
61
|
+
x.compare!
|
62
|
+
end
|
63
|
+
puts;
|
64
|
+
bm.entries.each do |e|
|
65
|
+
score = (e.ips * c).round.to_i
|
66
|
+
if e.label == 'on_progress 1000'
|
67
|
+
snooze_rate = ($snooze_count / e.seconds).to_i
|
68
|
+
puts "#{e.label}: #{score} rows/s snoozes: #{snooze_rate} i/s"
|
69
|
+
else
|
70
|
+
puts "#{e.label}: #{score} rows/s"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
puts;
|
74
|
+
end
|