SQLite performance of simplest selects or attempt to explain strange unexpected overhead comparing to native tcl
... using time measurement only, estimating the bottleneck by comparision with different tcl approaches and internals.
# command to measure within a proc (better compiled and fewer overhead for access vars, don't leak vars, etc):
proc timeit args {
set v 10;
if {[lindex $args 0] eq "-setup"} {set args [lassign $args _ c]; eval $c};
timerate {*}$args
}
timeit -calibrate {}
package require sqlite3
sqlite3 db :memory:
% timeit { set v }
0.000929 µs/# 23378303 # 1076547384 #/sec 21.716 net-ms
% timeit { db eval { select :v } }
0.691755 µs/# 1363139 # 1445598 #/sec 942.958 net-ms
Hmm... it has very large deviation.
But lets estimate the overhead between compiled and non-compiled eval (because set v
will be compiled in TEBC
very efficiently compared to non-compiled command):
Lets rewrite loadScalar
instruction to push
+ loadStk
combi:
% timeit { set [list v] }
0.010497 µs/# 19104745 # 95263655 #/sec 200.546 net-ms
% timeit { db eval { select :v } }
0.693341 µs/# 1360200 # 1442292 #/sec 943.082 net-ms
Nevertheless, blatant difference. We need possibly rewrite it to involve the overhead for the command invocation (TEBCResume, NRE trampoline, etc)
Lets try to rewrite it using invokeStk
, or simply invokeExpanded
(what is comparable in eval times):
% timeit -setup { set cmd set } { $cmd v }; # invokeStk
0.148422 µs/# 5255760 # 6737566 #/sec 780.068 net-ms
% timeit { {*}[list set v] }; # invokeExpanded
0.158082 µs/# 5001789 # 6325813 #/sec 790.695 net-ms
% timeit { db eval { select :v } }
0.692798 µs/# 1361203 # 1443421 #/sec 943.039 net-ms
Not so large anymore, but lets estimate the overhead for the prepare + statement compilation, caching etc. So simply test multiple variables instead of single variable.
% timeit -setup { set cmd list; set c [list v] } { $cmd [set $c] [set $c] [set $c] [set $c] [set $c] [set $c] [set $c] [set $c] [set $c] [set $c] }
0.376549 µs/# 2390087 # 2655699 #/sec 899.984 net-ms
% timeit { db eval {select :v, :v, :v, :v, :v, :v, :v, :v, :v, :v} }
2.969883 µs/# 332035 # 336713 #/sec 986.105 net-ms
Oops! It grows unexpectedly, may be it is the flexible typing or type affinity feature of sqlite. Lets check it with fixed string or null (variable is not there):
% timeit { db eval {select '', '', '', '', '', '', '', '', '', ''} }
0.944053 µs/# 1014303 # 1059262 #/sec 957.556 net-ms
% timeit -setup { unset v } { db eval {select :v, :v, :v, :v, :v, :v, :v, :v, :v, :v} }
1.409812 µs/# 688867 # 709314 #/sec 971.173 net-ms
% timeit { db eval {select :v, :v, :v, :v, :v, :v, :v, :v, :v, :v} }
3.081652 µs/# 320153 # 324501 #/sec 986.600 net-ms
OK, here maybe a potential for an optimization... But lets check, whether it is by constructing of the result, for instance using multiple reads but single value to return:
# static 10 fields, eval vs. onecolumn:
% timeit { db eval {select '' , '' , '' , '' , '' , '' , '' , '' , '' , ''} }
0.974208 µs/# 984258 # 1026474 #/sec 958.872 net-ms
% timeit { db onecolumn {select '' , '' , '' , '' , '' , '' , '' , '' , '' , ''} }
0.305482 µs/# 2879118 # 3273510 #/sec 879.520 net-ms
# static 1 fields (expr), eval vs. onecolumn:
% timeit { db eval {select '' + '' + '' + '' + '' + '' + '' + '' + '' + ''} }
0.843663 µs/# 1129294 # 1185308 #/sec 952.743 net-ms
% timeit { db onecolumn {select '' + '' + '' + '' + '' + '' + '' + '' + '' + ''} }
0.716921 µs/# 1317929 # 1394853 #/sec 944.851 net-ms
# dynamic 10 vars => 10 fields, eval vs. onecolumn:
% timeit { db eval {select :v , :v , :v , :v , :v , :v , :v , :v , :v , :v} }
2.942984 µs/# 335027 # 339791 #/sec 985.979 net-ms
% timeit { db onecolumn {select :v , :v , :v , :v , :v , :v , :v , :v , :v , :v} }
2.071647 µs/# 473150 # 482707 #/sec 980.200 net-ms
# dynamic 10 vars => 1 fields (expr), eval vs. onecolumn:
% timeit { db eval {select :v + :v + :v + :v + :v + :v + :v + :v + :v + :v} }
0.987323 µs/# 971658 # 1012840 #/sec 959.340 net-ms
% timeit { db onecolumn {select :v + :v + :v + :v + :v + :v + :v + :v + :v + :v} }
0.887403 µs/# 1076138 # 1126883 #/sec 954.968 net-ms
Yes, the build of result set seems to be involved too indeed. But it's thought too simple...
How one can see (especially considering difference by 1st test) - it has huge potential to improve by:
- better compilation or caching of statement and smarter prepare of set of parameters (vars)
- the variable binding (especially deterministic, when variable is a constant)
- integration sqlite-handle command within TEBC
- more efficient resultset creation or resultset prepare (preserve list by known columns and result lengh, better type affinity of tcl-object, etc)