It's not a target for writing new code (you'd be better off with C# or golang), but something like sandboxing with WASM, except that Fil-C crashes more precisely.
It's not a target for writing new code (you'd be better off with C# or golang), but something like sandboxing with WASM, except that Fil-C crashes more precisely.
Thus, Fil-C compiled code is 1 to 4 times as slow as plain C. This is not in the "significantly slower" ballpark, like where most interpreters are. The ROOT C/C++ interpreter is 20+ times slower than binary code, for example.
> best case scenario since there is very little memory management involved and runtime is dominated by computation in tight loops.
This describes most C programs and many, if not most, C++ programs. Basically, this is how C/C++ code is being written, by avoiding memory management, especially in tight loops.But you know what might work?
Take current DuckDB, compile it with Fil-C, and use a new escape hatch to call out to the tiny unsafe kernels that do vectorized high-speed columnar data operations on fixed memory areas that the buffers safe code set up on behalf of the unsafe kernels. That's how it'd probably work if DuckDB were implemented in Rust today, and it's how it could be made to work with Fil-C without a major rewrite.
Granted, this model would require Fil-C's author to become somewhat less dogmatic about having no escape hatches at all whatsoever, but I suspect he'll un-harden his heart as his work gains adoption and legitimate use-cases for an FFI/escape hatch appear.
While I'm not a big fan of rewriting things, all of DuckDB has been written in the last 10 years. Surely a rewrite with the benefit of hindsight could reach equivalent functionality in less than 10 years?
(sqlite is quite a bit smaller than DuckDB tho)
sqlite is a "self contained system" depending on no external software except c standard library for target os:
> A minimal build of SQLite requires just these routines from the standard C library:
> memcmp(), memcpy(), memmove(), memset(), strcmp(), strlen(), strncmp()
> Most builds also use the system memory allocation routines:
> malloc(), realloc(), free()
> Default builds of SQLite contain appropriate VFS objects for talking to the underlying operating system, and those VFS objects will contain operating system calls such as open(), read(), write(), fsync(), and so forth
Quoting from the appropriately named https://sqlite.org/selfcontained.html
as a very rough and unfair estimate between the two project's source, sqlite is about 8% the size of duckdb:
$ pwd
/Users/jitl/src/duckdb/src
$ sloc .
---------- Result ------------
Physical : 418092
Source : 317274
Comment : 50113
Single-line comment : 46187
Block comment : 3926
Mixed : 4415
Empty block comment : 588
Empty : 55708
To Do : 136
Number of files read : 2611
----------------------------
$ cd ~/Downloads/sqlite-amalgamation-3500400/
$ sloc .
---------- Result ------------
Physical : 34742
Source : 25801
Comment : 8110
Single-line comment : 1
Block comment : 8109
Mixed : 1257
Empty block comment : 1
Empty : 2089
To Do : 5
Number of files read : 2
----------------------------Fil-C has its drawbacks, but they should be described carefully, just with any technology.
Quick example:
typedef struct Foo {
int buf[2];
float some_float;
} Foo;int main(void) {
Foo foo = {0};
for (size_t i = 0; i < 3; ++i) {
foo.buf[i] = 0x3f000000;
printf("foo.buf[%zu]: %d\n", i, foo.buf[i]);
}
printf("foo.some_float: %f\n", foo.some_float);
}This overflows into the float, not causing any panics, printing 0.5 for the float.
That said, as a rough guideline, nim c -d=release can certainly be almost the same speed as -d=danger and is often within a few (single digits) percent. E.g.:
.../bu(main)$ nim c -d=useMalloc --panics=on --cc=clang -d=release -o=/t/rel unfold.nim
Hint: mm: orc; opt: speed; options: -d:release
61608 lines; 0.976s; 140.723MiB peakmem; proj: .../bu/unfold.nim; out: /t/rel [SuccessX]
.../bu(main)$ nim c -d=useMalloc --panics=on --cc=clang -d=danger -o=/t/dan unfold.nim
Hint: mm: orc; opt: speed; options: -d:danger
61608 lines; 2.705s; 141.629MiB peakmem; proj: .../bu/unfold.nim; out: /t/dan [SuccessX]
.../bu(main)$ seq 1 100000 > /t/dat
.../bu(main)$ /t
/t$ re=(chrt 99 taskset -c 2 env -i HOME=$HOME PATH=$PATH)
/t$ $re tim "./dan -n50 <dat>/n" "./rel -n50 <dat>/n"
225.5 +- 1.2 μs (AlreadySubtracted)Overhead
4177 +- 15 μs ./dan -n50 <dat>/n
4302 +- 17 μs ./rel -n50 <dat>/n
/t$ a (4302 +- 17)/(4177 +- 15)
1.0299 +- 0.0055
/t$ a 299./55
5.43636... # kurtosis=>5.4 sigmas is not so significant
Of course, as per my first sentence, the best benchmarks are your own applications run against your own data and its idiosyncratic distributions.EDIT: btw, /t -> /tmp which is a /dev/shm bind mount while /n -> /dev/null.