mini_sql 0.2.4 → 1.1.0
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/.github/workflows/ci.yml +66 -0
- data/.rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml +355 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG +22 -0
- data/Gemfile +3 -1
- data/Guardfile +2 -0
- data/README.md +125 -1
- data/Rakefile +3 -1
- data/bench/builder_perf.rb +138 -0
- data/bench/decorator_perf.rb +143 -0
- data/bench/mini_sql_methods_perf.rb +80 -0
- data/bench/prepared_perf.rb +59 -0
- data/bench/shared/generate_data.rb +133 -0
- data/bench/timestamp_perf.rb +22 -21
- data/bench/topic_mysql_perf.rb +1 -7
- data/bench/topic_perf.rb +27 -169
- data/bench/topic_wide_perf.rb +92 -0
- data/bin/console +1 -0
- data/lib/mini_sql.rb +20 -8
- data/lib/mini_sql/abstract/prepared_binds.rb +74 -0
- data/lib/mini_sql/abstract/prepared_cache.rb +45 -0
- data/lib/mini_sql/builder.rb +64 -24
- data/lib/mini_sql/connection.rb +15 -3
- data/lib/mini_sql/decoratable.rb +22 -0
- data/lib/mini_sql/deserializer_cache.rb +2 -0
- data/lib/mini_sql/inline_param_encoder.rb +12 -13
- data/lib/mini_sql/mysql/connection.rb +18 -3
- data/lib/mini_sql/mysql/deserializer_cache.rb +14 -16
- data/lib/mini_sql/mysql/prepared_binds.rb +15 -0
- data/lib/mini_sql/mysql/prepared_cache.rb +21 -0
- data/lib/mini_sql/mysql/prepared_connection.rb +44 -0
- data/lib/mini_sql/postgres/coders.rb +2 -0
- data/lib/mini_sql/postgres/connection.rb +89 -0
- data/lib/mini_sql/postgres/deserializer_cache.rb +36 -16
- data/lib/mini_sql/postgres/prepared_binds.rb +15 -0
- data/lib/mini_sql/postgres/prepared_cache.rb +25 -0
- data/lib/mini_sql/postgres/prepared_connection.rb +36 -0
- data/lib/mini_sql/postgres_jdbc/connection.rb +8 -1
- data/lib/mini_sql/postgres_jdbc/deserializer_cache.rb +43 -43
- data/lib/mini_sql/result.rb +30 -0
- data/lib/mini_sql/serializer.rb +84 -0
- data/lib/mini_sql/sqlite/connection.rb +20 -2
- data/lib/mini_sql/sqlite/deserializer_cache.rb +14 -16
- data/lib/mini_sql/sqlite/prepared_binds.rb +15 -0
- data/lib/mini_sql/sqlite/prepared_cache.rb +21 -0
- data/lib/mini_sql/sqlite/prepared_connection.rb +40 -0
- data/lib/mini_sql/version.rb +1 -1
- data/mini_sql.gemspec +7 -2
- metadata +75 -11
- data/.travis.yml +0 -26
data/Gemfile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
source "https://rubygems.org"
|
2
4
|
|
3
|
-
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
4
6
|
|
5
7
|
# Specify your gem's dependencies in mini_sql.gemspec
|
6
8
|
gemspec
|
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -32,11 +32,28 @@ conn.query("select 1 id, 'bob' name").each do |user|
|
|
32
32
|
puts user.id # 1
|
33
33
|
end
|
34
34
|
|
35
|
+
# extend result objects with additional method
|
36
|
+
module ProductDecorator
|
37
|
+
def amount_price
|
38
|
+
price * quantity
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
conn.query_decorator(ProductDecorator, "select 20 price, 3 quantity").each do |user|
|
43
|
+
puts user.amount_price # 60
|
44
|
+
end
|
45
|
+
|
35
46
|
p conn.query_single('select 1 union select 2')
|
36
47
|
# [1,2]
|
37
48
|
|
38
49
|
p conn.query_hash('select 1 as a, 2 as b union select 3, 4')
|
39
50
|
# [{"a" => 1, "b"=> 1},{"a" => 3, "b" => 4}
|
51
|
+
|
52
|
+
p conn.query_array("select 1 as a, '2' as b union select 3, 'e'")
|
53
|
+
# [[1, '2'], [3, 'e']]
|
54
|
+
|
55
|
+
p conn.query_array("select 1 as a, '2' as b union select 3, 'e'").to_h
|
56
|
+
# {1 => '2', 3 => 'e'}
|
40
57
|
```
|
41
58
|
|
42
59
|
## The query builder
|
@@ -63,9 +80,18 @@ end
|
|
63
80
|
The builder allows for `order_by`, `where`, `select`, `set`, `limit`, `join`, `left_join` and `offset`.
|
64
81
|
|
65
82
|
## Is it fast?
|
66
|
-
|
67
83
|
Yes, it is very fast. See benchmarks in [the bench directory](https://github.com/discourse/mini_sql/tree/master/bench).
|
68
84
|
|
85
|
+
**Comparison mini_sql methods**
|
86
|
+
```
|
87
|
+
query_array 1351.6 i/s
|
88
|
+
query 963.8 i/s - 1.40x slower
|
89
|
+
query_hash 787.4 i/s - 1.72x slower
|
90
|
+
|
91
|
+
query_single('select id from topics limit 1000') 2368.9 i/s
|
92
|
+
query_array('select id from topics limit 1000').flatten 1350.1 i/s - 1.75x slower
|
93
|
+
```
|
94
|
+
|
69
95
|
As a rule it will outperform similar naive PG code while remaining safe.
|
70
96
|
|
71
97
|
```ruby
|
@@ -98,6 +124,83 @@ MiniSql is careful to always clear results as soon as possible.
|
|
98
124
|
|
99
125
|
MiniSql's default type mapper prefers treating `timestamp without time zone` columns as utc. This is done to ensure widest amount of compatability and is a departure from the default in the PG 1.0 gem. If you wish to amend behavior feel free to pass in a custom type_map.
|
100
126
|
|
127
|
+
## Custom type maps
|
128
|
+
|
129
|
+
When using Postgres, native type mapping implementation is used. This is roughly
|
130
|
+
implemented as:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
type_map ||= PG::BasicTypeMapForResults.new(conn)
|
134
|
+
# additional specific decoders
|
135
|
+
```
|
136
|
+
|
137
|
+
The type mapper instansitated once on-demand at boot and reused by all mini_sql connections.
|
138
|
+
|
139
|
+
Initializing the basic type map for Postgres can be a costly operation. You may
|
140
|
+
wish to amend the type mapper so for example you only return strings:
|
141
|
+
|
142
|
+
```
|
143
|
+
# maybe you do not want Integer
|
144
|
+
p cnn.query("select a 1").first.a
|
145
|
+
"1"
|
146
|
+
```
|
147
|
+
|
148
|
+
To specify a different type mapper for your results use:
|
149
|
+
|
150
|
+
```
|
151
|
+
MiniSql::Connections.get(pg_connection, type_map: custom_type_map)
|
152
|
+
```
|
153
|
+
|
154
|
+
In the case of Rails you can opt to use the type mapper Rails uses with:
|
155
|
+
|
156
|
+
```
|
157
|
+
pg_cnn = ActiveRecord::Base.connection.raw_connection
|
158
|
+
mini_sql_cnn = MiniSql::Connection.get(pg_cnn, type_map: pg_cnn.type_map_for_results)
|
159
|
+
```
|
160
|
+
|
161
|
+
Note the type mapper for Rails may miss some of the mapping MiniSql ships with such as `IPAddr`, MiniSql is also careful to use the very efficient TimestampUtc decoders where available.
|
162
|
+
|
163
|
+
## Streaming support
|
164
|
+
|
165
|
+
In some exceptional cases you may want to stream results directly from the database. This enables selection of 100s of thousands of rows with limited memory impact.
|
166
|
+
|
167
|
+
Two interfaces exists for this:
|
168
|
+
|
169
|
+
`query_each` : which can be used to get materialized objects
|
170
|
+
`query_each_hash` : which can be used to iterate through Hash objects
|
171
|
+
|
172
|
+
Usage:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
mini_sql_cnn.query_each("SELECT * FROM tons_of_cows limit :limit", limit: 1_000_000) do |row|
|
176
|
+
puts row.cow_name
|
177
|
+
puts row.cow_age
|
178
|
+
end
|
179
|
+
|
180
|
+
mini_sql_cnn.query_each_hash("SELECT * FROM one_million_cows") do |row|
|
181
|
+
puts row["cow_name"]
|
182
|
+
puts row["cow_age"]
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
Note, in Postgres streaming is going to be slower than non-streaming options due to internal implementation in the pq gem, each row gets a full result object and additional bookkeeping is needed. Only use it if you need to optimize memory usage.
|
187
|
+
|
188
|
+
Streaming support is only implemented in the postgres backend at the moment, PRs welcome to add to other backends.
|
189
|
+
|
190
|
+
## Prepared Statements
|
191
|
+
See [benchmark mini_sql](https://github.com/discourse/mini_sql/tree/master/bench/prepared_perf.rb)
|
192
|
+
[benchmark mini_sql vs rails](https://github.com/discourse/mini_sql/tree/master/bench/bilder_perf.rb).
|
193
|
+
|
194
|
+
By default prepared cache size is 500 queries. Use prepared queries only for frequent queries.
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
conn.prepared.query("select * from table where id = ?", id: 10)
|
198
|
+
|
199
|
+
ids = rand(100) < 90 ? [1] : [1, 2]
|
200
|
+
builder = conn.build("select * from table /*where*/")
|
201
|
+
builder.where("id IN (?)", ids)
|
202
|
+
builder.prepared(ids.size == 1).query # most frequent query
|
203
|
+
```
|
101
204
|
|
102
205
|
## I want more features!
|
103
206
|
|
@@ -106,6 +209,27 @@ MiniSql is designed to be very minimal. Even though the query builder and type m
|
|
106
209
|
## Development
|
107
210
|
|
108
211
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
212
|
+
### Local testing
|
213
|
+
```bash
|
214
|
+
docker run --name mini-sql-mysql --rm -it -p 33306:3306 -e MYSQL_DATABASE=test_mini_sql -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -d mysql:5.7
|
215
|
+
export MINI_SQL_MYSQL_HOST=127.0.0.1
|
216
|
+
export MINI_SQL_MYSQL_PORT=33306
|
217
|
+
|
218
|
+
docker run --name mini-sql-postgres --rm -it -p 55432:5432 -e POSTGRES_DB=test_mini_sql -e POSTGRES_HOST_AUTH_METHOD=trust -d postgres
|
219
|
+
export MINI_SQL_PG_USER=postgres
|
220
|
+
export MINI_SQL_PG_HOST=127.0.0.1
|
221
|
+
export MINI_SQL_PG_PORT=55432
|
222
|
+
|
223
|
+
sleep 10 # waiting for up databases
|
224
|
+
|
225
|
+
bundle exec rake
|
226
|
+
|
227
|
+
# end working on mini-sql
|
228
|
+
docker stop mini-sql-postgres mini-sql-mysql
|
229
|
+
```
|
230
|
+
|
231
|
+
Sqlite tests rely on the SQLITE_STMT view existing. This is enabled by default on most systems, however some may
|
232
|
+
opt for a leaner install. See: https://bugs.archlinux.org/task/70072. You may have to recompile sqlite on such systems.
|
109
233
|
|
110
234
|
## Contributing
|
111
235
|
|
data/Rakefile
CHANGED
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
gem 'mini_sql', path: '../'
|
8
|
+
gem 'pg'
|
9
|
+
gem 'activerecord'
|
10
|
+
gem 'activemodel'
|
11
|
+
gem 'benchmark-ips'
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'active_record'
|
15
|
+
require 'benchmark/ips'
|
16
|
+
require 'mini_sql'
|
17
|
+
|
18
|
+
require '../mini_sql/bench/shared/generate_data'
|
19
|
+
|
20
|
+
ar_connection, _ = GenerateData.new(count_records: 10_000).call
|
21
|
+
MINI_SQL = MiniSql::Connection.get(ar_connection.raw_connection)
|
22
|
+
|
23
|
+
def mini_sql(is_prepared, user_id)
|
24
|
+
MINI_SQL
|
25
|
+
.build(<<~SQL)
|
26
|
+
/*select*/ from topics /*join*/ /*where*/ /*group_by*/
|
27
|
+
SQL
|
28
|
+
.select('users.first_name, count(distinct topics.id) topics_count')
|
29
|
+
.join('users on user_id = users.id')
|
30
|
+
.join('categories on category_id = categories.id')
|
31
|
+
.where('users.id = ?', user_id)
|
32
|
+
.group_by('users.id')
|
33
|
+
.prepared(is_prepared)
|
34
|
+
.query
|
35
|
+
end
|
36
|
+
|
37
|
+
def mini_sql_optim(is_prepared, user_id)
|
38
|
+
@builder ||=
|
39
|
+
MINI_SQL
|
40
|
+
.build(<<~SQL)
|
41
|
+
/*select*/ from topics /*join*/ /*where*/ /*group_by*/
|
42
|
+
SQL
|
43
|
+
.select('users.first_name, count(distinct topics.id) topics_count')
|
44
|
+
.join('users on user_id = users.id')
|
45
|
+
.join('categories on category_id = categories.id')
|
46
|
+
.where('users.id = :user_id')
|
47
|
+
.group_by('users.id')
|
48
|
+
|
49
|
+
@builder
|
50
|
+
.prepared(is_prepared)
|
51
|
+
.query(user_id: user_id)
|
52
|
+
end
|
53
|
+
|
54
|
+
def ar_prepared(user_id)
|
55
|
+
Topic
|
56
|
+
.select(User.arel_table[:first_name] , Topic.arel_table[:id].count)
|
57
|
+
.joins(:user, :category)
|
58
|
+
.where(user_id: user_id)
|
59
|
+
.group(User.arel_table[:id])
|
60
|
+
.load
|
61
|
+
end
|
62
|
+
|
63
|
+
def ar_prepared_optim(user_id)
|
64
|
+
@rel ||= Topic
|
65
|
+
.select(User.arel_table[:first_name] , Topic.arel_table[:id].count)
|
66
|
+
.joins(:user, :category)
|
67
|
+
.group(User.arel_table[:id])
|
68
|
+
|
69
|
+
@rel
|
70
|
+
.where(user_id: user_id)
|
71
|
+
.load
|
72
|
+
end
|
73
|
+
|
74
|
+
def ar_unprepared(user_id)
|
75
|
+
Topic
|
76
|
+
.select('users.first_name, count(distinct topics.id) topics_count')
|
77
|
+
.joins(:user, :category)
|
78
|
+
.where(user_id: user_id)
|
79
|
+
.group('users.id')
|
80
|
+
.load
|
81
|
+
end
|
82
|
+
|
83
|
+
Benchmark.ips do |x|
|
84
|
+
x.report("mini_sql_prepared") do |n|
|
85
|
+
while n > 0
|
86
|
+
mini_sql(true, rand(100))
|
87
|
+
n -= 1
|
88
|
+
end
|
89
|
+
end
|
90
|
+
x.report("mini_sql") do |n|
|
91
|
+
while n > 0
|
92
|
+
mini_sql(false, rand(100))
|
93
|
+
n -= 1
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
x.report("mini_sql_prepared_optim") do |n|
|
98
|
+
while n > 0
|
99
|
+
mini_sql_optim(true, rand(100))
|
100
|
+
n -= 1
|
101
|
+
end
|
102
|
+
end
|
103
|
+
x.report("mini_sql_optim") do |n|
|
104
|
+
while n > 0
|
105
|
+
mini_sql_optim(false, rand(100))
|
106
|
+
n -= 1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
x.report("ar_prepared") do |n|
|
111
|
+
while n > 0
|
112
|
+
ar_prepared(rand(100))
|
113
|
+
n -= 1
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
x.report("ar_prepared_optim") do |n|
|
118
|
+
while n > 0
|
119
|
+
ar_prepared_optim(rand(100))
|
120
|
+
n -= 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
x.report("ar_unprepared") do |n|
|
125
|
+
while n > 0
|
126
|
+
ar_unprepared(rand(100))
|
127
|
+
n -= 1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
x.compare!
|
132
|
+
end
|
133
|
+
|
134
|
+
# Comparison:
|
135
|
+
# mini_sql_prepared: 8386.2 i/s
|
136
|
+
# mini_sql: 2742.3 i/s - 3.06x (± 0.00) slower
|
137
|
+
# ar_prepared: 1599.3 i/s - 5.24x (± 0.00) slower
|
138
|
+
# ar_unprepared: 868.9 i/s - 9.65x (± 0.00) slower
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
gem 'pg', github: 'ged/ruby-pg'
|
8
|
+
gem 'mini_sql', path: '../'
|
9
|
+
gem 'activerecord'
|
10
|
+
gem 'activemodel'
|
11
|
+
gem 'benchmark-ips'
|
12
|
+
gem 'sequel', github: 'jeremyevans/sequel'
|
13
|
+
gem 'sequel_pg', github: 'jeremyevans/sequel_pg', require: 'sequel'
|
14
|
+
gem 'draper'
|
15
|
+
gem 'pry'
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'active_record'
|
19
|
+
require 'benchmark/ips'
|
20
|
+
require 'mini_sql'
|
21
|
+
|
22
|
+
require '../mini_sql/bench/shared/generate_data'
|
23
|
+
|
24
|
+
ar_connection, conn_config = GenerateData.new(count_records: 1_000).call
|
25
|
+
MINI_SQL = MiniSql::Connection.get(ar_connection.raw_connection)
|
26
|
+
DB = Sequel.connect(ar_connection.instance_variable_get(:@config).slice(:database, :user, :password, :host, :adapter))
|
27
|
+
|
28
|
+
|
29
|
+
# https://github.com/drapergem/draper
|
30
|
+
class TopicDraper < Draper::Decorator
|
31
|
+
delegate :id
|
32
|
+
|
33
|
+
def title_bang
|
34
|
+
object.title + '!!!'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# https://ruby-doc.org/stdlib-2.5.1/libdoc/delegate/rdoc/SimpleDelegator.html
|
39
|
+
class TopicSimpleDelegator < SimpleDelegator
|
40
|
+
def title_bang
|
41
|
+
title + '!!!'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class TopicSequel < Sequel::Model(DB[:topics]); end
|
46
|
+
class TopicDecoratorSequel < TopicSequel
|
47
|
+
def title_bang
|
48
|
+
title + '!!!'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Topic < ActiveRecord::Base;end
|
53
|
+
class TopicArModel < Topic
|
54
|
+
def title_bang
|
55
|
+
title + '!!!'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module TopicDecorator
|
60
|
+
def title_bang
|
61
|
+
title + '!!!'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Benchmark.ips do |r|
|
66
|
+
r.report('query_decorator') do |n|
|
67
|
+
while n > 0
|
68
|
+
MINI_SQL.query_decorator(TopicDecorator, 'select id, title from topics order by id limit 1000').each do |obj|
|
69
|
+
obj.title_bang
|
70
|
+
obj.id
|
71
|
+
end
|
72
|
+
n -= 1
|
73
|
+
end
|
74
|
+
end
|
75
|
+
r.report('extend') do |n|
|
76
|
+
while n > 0
|
77
|
+
MINI_SQL.query('select id, title from topics order by id limit 1000').each do |obj|
|
78
|
+
d_obj = obj.extend(TopicDecorator)
|
79
|
+
d_obj.title_bang
|
80
|
+
d_obj.id
|
81
|
+
end
|
82
|
+
n -= 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
r.report('draper') do |n|
|
86
|
+
while n > 0
|
87
|
+
MINI_SQL.query('select id, title from topics order by id limit 1000').each do |obj|
|
88
|
+
d_obj = TopicDraper.new(obj)
|
89
|
+
d_obj.title_bang
|
90
|
+
d_obj.id
|
91
|
+
end
|
92
|
+
n -= 1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
r.report('simple_delegator') do |n|
|
96
|
+
while n > 0
|
97
|
+
MINI_SQL.query('select id, title from topics order by id limit 1000').each do |obj|
|
98
|
+
d_obj = TopicSimpleDelegator.new(obj)
|
99
|
+
d_obj.title_bang
|
100
|
+
d_obj.id
|
101
|
+
end
|
102
|
+
n -= 1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
r.report('query') do |n|
|
106
|
+
while n > 0
|
107
|
+
MINI_SQL.query('select id, title from topics order by id limit 1000').each do |obj|
|
108
|
+
obj.title + '!!!'
|
109
|
+
obj.id
|
110
|
+
end
|
111
|
+
n -= 1
|
112
|
+
end
|
113
|
+
end
|
114
|
+
r.report('ar model') do |n|
|
115
|
+
while n > 0
|
116
|
+
TopicArModel.limit(1000).order(:id).select(:id, :title).each do |obj|
|
117
|
+
obj.title_bang
|
118
|
+
obj.id
|
119
|
+
end
|
120
|
+
n -= 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
r.report('sequel model') do |n|
|
124
|
+
while n > 0
|
125
|
+
TopicDecoratorSequel.limit(1000).order(:id).select(:id, :title).each do |obj|
|
126
|
+
obj.title_bang
|
127
|
+
obj.id
|
128
|
+
end
|
129
|
+
n -= 1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
r.compare!
|
134
|
+
end
|
135
|
+
|
136
|
+
# Comparison:
|
137
|
+
# query: 1102.9 i/s
|
138
|
+
# query_decorator: 1089.0 i/s - same-ish: difference falls within error
|
139
|
+
# sequel model: 860.2 i/s - 1.28x (± 0.00) slower
|
140
|
+
# simple_delegator: 679.8 i/s - 1.62x (± 0.00) slower
|
141
|
+
# extend: 678.1 i/s - 1.63x (± 0.00) slower
|
142
|
+
# draper: 587.2 i/s - 1.88x (± 0.00) slower
|
143
|
+
# ar model: 172.5 i/s - 6.39x (± 0.00) slower
|