> modernc, modernc.org/sqlite, a pure Go solution. This is a newer library, based on the SQLite C code re-written in Go.
Unless I'm mistaken, this is not a re-write in Go. This is a transpilation of the the SQLite C library into go, using https://gitlab.com/cznic/ccgo
Just use zig as your c compiler and it all just works.
The only proper way to use sqlite is to use FFI.
Obviously it "works" and they didn't "just" transpile it but spent quite a bit of effort on this. Whether it's free of bugs that are not present in SQLite is a different matter. And while it's not run against the SQLite proprietary tests, it does pass all of the TCL tests IIRC, which are quite comprehensive on their own, so there shouldn't be huge glaring bugs.
It is a less battle-tested C compiler, sure, but it's a C compiler. IMO, you can't argue that something shouldn't be called sqlite because it contains a transpiled copy of sqlite. To me this isn't really substantially different from transpiling sqlite to JS using Emscripten.
But yeah it does give me a little shiver to think you are transpiling C code to go code, and the go-code is not really "pure go", but has a platform-dependent unsafe operations. Just look at the repo
https://gitlab.com/cznic/sqlite/-/tree/master/lib?ref_type=h...
Which one do you mean? The WASM one? It includes WASM, it needs to be compiled too.
Ah github.com/cvilsmeier/sqinn-go. Which... is made by the same person that made this benchmark...?
edit: and that requires some random binary to be pre-installed...? Which is in C anyway?
https://github.com/cvilsmeier/sqinn-go
https://github.com/cvilsmeier/sqinn
so I don't see any "actually written in go".
Also, although I do (lightly) patch SQLite which might invalidate results in your view, the SQLite team is producing/testing/releasing WASM builds of SQLite built with much the same toolchain, which, hopefully, smokes out compiler bugs.
[1]: https://github.com/ncruces/go-sqlite3/blob/main/.github/work...
(OP here) Honestly, me too. Maybe it has to do with leaving out that database/sql driver layer. I guess it would need another round of pprof to find out the real reason.
[0] https://github.com/search?q=topic%3Ajdbc%20topic%3Asqlite&ty...
- There is a version transpiled from C to Go. A transpilation from C to Java (or even the JVM) would be considerably more difficult, a naive translation would likely undergo a much larger performance hit. Maybe with Valhalla this will change.
- Modern Java deployment is not generally complicated by JNI/JNA (it's already that complicated to start with; Maven already wrangles some of it, although it's still a pain in many cases). Go deployments are simpler if no C linkage is involved.
- Some of these are not database/sql, the equivalent of JDBC, drivers. They're purpose-built drivers that expose rich SQLite-specific features. With the huge popularity of Spring most developers don't even interact at the JDBC level today, only e.g. JPA. IMO Java developers are missing out on richer SQL features in their DBs, but well, they seem to mostly manage.
W.r.t. benchmark results.
wazero's current compiler is somewhat naive, which may explain a large performance delta in CPU bound tests. A new compiler is in the works [1].
OTOH it seems interesting that in the (IO bound?) large test I'm doing better than modernc. I wonder why.
I'll dig deeper into the results.
To fix a recent crash [1] that was happening due to a particular case of reentrancy, which only showed up when I implemented virtual tables and queried other tables to implement one (e.g.: Go calls sqlite3_step to execute a query, which calls Go because it's a query on virtual table, which calls sqlite3_step to scan another table) I introduced a performance regression.
The fix [2] was not to reuse some objects I was allocating once per connection. A mitigation for the regression was (very naive) caching [3].
TLDR: my caching is just not good enough. Simply caching more will go a long way (confirmed already by doubling cache size), but now that I have a good benchmark, I'll do better.
I expect to cut numbers for CPU bound tests in half due to this mishap.
So, thanks cvilsmeier!
[1]: https://github.com/ncruces/go-sqlite3/commit/a9e32fd3f0b9f39... [2]: https://github.com/ncruces/go-sqlite3/commit/d862f47d95d522f... [3]: https://github.com/ncruces/go-sqlite3/commit/9c562f5d8bf7436...
In [1] I implemented a simple PLRU bit cache, and I'm seeing an 8x performance improvement in some of the tests I was doing worse in:
Before:
bench-ncruces - simple insert query dbsize
bench-ncruces - simple 21224 16495 58687488
bench-ncruces - complex/200/100/20 insert query dbsize
bench-ncruces - complex/200/100/20 14993 15228 25354240
bench-ncruces - many/N=10 query dbsize
bench-ncruces - many/N=10 483 36864
bench-ncruces - many/N=100 query dbsize
bench-ncruces - many/N=100 3129 36864
bench-ncruces - many/N=1000 query dbsize
bench-ncruces - many/N=1000 28034 94208
bench-ncruces - large/N=50000 query dbsize
bench-ncruces - large/N=50000 428 501981184
bench-ncruces - large/N=100000 query dbsize
bench-ncruces - large/N=100000 779 1003761664
bench-ncruces - large/N=200000 query dbsize
bench-ncruces - large/N=200000 1475 2007330816
bench-ncruces - concurrent/N=2 query dbsize
bench-ncruces - concurrent/N=2 13091 56573952
bench-ncruces - concurrent/N=4 query dbsize
bench-ncruces - concurrent/N=4 14731 56573952
bench-ncruces - concurrent/N=8 query dbsize
bench-ncruces - concurrent/N=8 24730 56573952
After: bench-ncruces - simple insert query dbsize
bench-ncruces - simple 5128 3026 58687488
bench-ncruces - complex/200/100/20 insert query dbsize
bench-ncruces - complex/200/100/20 3127 3730 25354240
bench-ncruces - many/N=10 query dbsize
bench-ncruces - many/N=10 93 36864
bench-ncruces - many/N=100 query dbsize
bench-ncruces - many/N=100 403 36864
bench-ncruces - many/N=1000 query dbsize
bench-ncruces - many/N=1000 3470 94208
bench-ncruces - large/N=50000 query dbsize
bench-ncruces - large/N=50000 444 501981184
bench-ncruces - large/N=100000 query dbsize
bench-ncruces - large/N=100000 717 1003761664
bench-ncruces - large/N=200000 query dbsize
bench-ncruces - large/N=200000 1401 2007330816
bench-ncruces - concurrent/N=2 query dbsize
bench-ncruces - concurrent/N=2 3275 56573952
bench-ncruces - concurrent/N=4 query dbsize
bench-ncruces - concurrent/N=4 3404 56573952
bench-ncruces - concurrent/N=8 query dbsize
bench-ncruces - concurrent/N=8 4918 56573952
There's still work to do. I could use ints rather than strings for function identifiers.
I'll evaluate that later.[1]: https://github.com/ncruces/go-sqlite3/commit/964a42c76deb9c7...
I am reposting it below shortly with minor stylistic changes for politeness.
“Go's SQLite drivers exhibit surprisingly poor performance. In C# I was able to conduct 2 millions of point queries per second on my laptop. And it's not the fastest language in the world.
Go is overrated. It's crudely trying to imitate what Pascal had in '80s using awkward syntax and tooling, but giving you extra CVEs for free.”
Personally, I did not expect it to be this bad…and C# SQLite drivers aren’t even something new - most of them have been written eons ago and consist of fairly standard somewhat allocatey code.
I wonder if it’s because of significant interop overhead in Go, or just fundamental language limitations and quality issues.
An example of one thing missing is the pointer passing interface which limits the ability to create complex extensions. If support was added, the way function creation and virtual tables where wrapped would make it hard to use.
But even more trivial things like a decent interface to incremental blob IO have been left unaddressed.
What's wrong with it?
Our workloads are a bit different and obviously our machines are a bit different.
Mine only compares mattn/go-sqlite3 to my own fork of https://github.com/bvinc/go-sqlite-lite. go-sqlite-lite seemed like an easier-to-use version of crawshaw's package but it was abandoned so I forked it to bring it up to date.
I agree, for best performance you shouldn't use mattn/go-sqlite3. It does some extra work in hotpaths. It is also higher level and easier to use though. So pick what's important to you.
"Complex" kinda covers this, since there are foreign keys involved, and it's also where e.g. Zombie shows an uncharacteristic slowdown compared to the other benchmarks. Seems like it's probably not a coincidence?
I seem to remember that modernc worked great until i added a FTS5 [1] table and things became very slow. This was a while ago now, so it may have changed or I may mave misattributed the slowness to the non-CGO implementation.
I'd be curious to see how each performs with a fts index and some triggers.
https://github.com/gwenn/gosqlite
That's important when processing data from untrusted sources (user generated content, etc).
No idea how it compares to the others performance wise though. :)