https://tartanllama.xyz/posts/inline-hints/
This library seems to have the annotation on every function, though, so it's possible the author is just following a convention of always using it for functions defined in header files (it'd be required if the functions weren't declared `static`).
In the former case the compiler is allowed to always inline the function.
In the latter case, even when the compiler chooses to inline the function, it also emits code for an independent instance of the function, because the function is public and it may be called from another file.
So "static inline" in the worst case does nothing, but it suggests to the compiler that the function should be inlined everywhere, which it will probably do, unless it decides that the function is too long (or it uses some features forbidden in inlined functions, e.g. variadic arguments, setjmp, alloca, etc.), so the benefits of inlining it may be less than the disadvantages.
When the compiler refuses to follow the suggestion of inlining the function, it can be made to tell the reason, e.g. with "-Winline".
So the compiler does not ignore the suggestion, even if it may choose to not follow it.
Not in standard C. "inline" function provides implementation for usage iff compiler decides to inline the call. If it does decide not to inline, it will emit call to external symbol that needs to be defined in different TU (otherwise you will get errors at link time).
Quote from the gcc manual:
"GCC implements three different semantics of declaring a function inline. One is available with -std=gnu89 or -fgnu89-inline or when gnu_inline attribute is present on all inline declarations, another when -std=c99, -std=gnu99 or an option for a later C version is used (without -fgnu89-inline), and the third is used when compiling C++."
Nevertheless, "static inline" means the same thing in all 3 standards, unlike "inline" alone.
This can be a reason to always prefer "static inline", because then it does not matter whether the program is compiled as C or as C++.
In reality header only libraries allow for deep inlining, the compiler may optimize very specifically to your code and usage.
The situation is a bit more exaggerated with C++ because of templates, but there is some remaining gains to he had in C alone.
This is also why everyone implements their own (buggy) linked-list implementations, etc.
And header-only is more efficient to include and build with than header+source.
Now that I'm thinking about it, CMake also isn't particularly good at this the way most people use it.
But, more commonly I've seen that it's just easier to not need to add C files at all. Add a single include path and you can avoid the annoyances of vendoring dependencies, tracking upstream updates, handling separate linkage, object files, output paths, ABIs, and all the rest. Something like Cargo does all of this for you, which is why people prefer it to calling rustc directly.
I tried to use cargo in the past and found it very bad compared to apt / apt-get (even when ignoring that it is a supply-chain disaster), essentially the same mess as npm or pip. Some python packages certainly wasted far more time of my life than all dependencies for C projects I ever had deal with combined.
Get over it. Some people like header only.
To comment on this, I have a couple of header-only projects I have written. It makes sense in some scenarios. Sometimes I want no external dependencies and a single header file interface.
It's a tradeoff people make between ease of integration - just download the .h file into your project folder and #include it in your source file instead of worrying about source build system vs target build system, cross compiling headaches etc...
And compilation times: any time you change any of your source files, your compiler also has to recompile your dependencies. (Assuming you haven't used precompiled headers).
int add(int a, int b){
// Long logic and then this
return a+b;
}
Let's say this is your main.c. #include "add.h"
int main(void) {
return add(5,6);
}
The preprocessor just copies the contents of add.h into your main.c whenever you're trying to compile main.c. (let's ignore the concept of precompiled headers for now).What you can instead do is just put the add function declaration in add.h that just tells the compiler that add function takes two integers and returns an integer.
int add(int a, int b);
You can then put the add function definition in add.c , compile that to an add.o and link it to your main.o at link time to get your final binary - without having to recompile add.o every time you change your main.c.Precompiled headers: https://maskray.me/blog/2023-07-16-precompiled-headers
In strict terms when you place implementation in a .c file you probably want that code to be shared when different things call it, and the compiler will "link" to that same implementation.
When you have a header only library the compiler is free to optimize in more ways specific to your actual use case.
I can still push the file on git and it works everywhere else.
The low-hanging fruit for this library would be SIMD. At 128d float32, each distance computation touches 512 bytes of data. AVX2 processes 8 floats per cycle, NEON does 4. That's a 4-6x speedup on the hot path without changing the algorithm at all. For a header-only library where simplicity is the point, that seems like the right optimization to reach for before adding approximate indexing.
One gotcha: metadata isn't persisted on save/load. The README mentions the binary format stores vectors and IDs but not metadata. Anyone attaching text chunks to their embeddings for RAG will lose them on reload.
I also don't think it has any indexes either? So search performance is a function of the number of entries.