extralite 2.5 → 2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|