#define STRINGZILLA_MULTIVERSIONED 1 0 folds each dispatch table to its best kernel; 1, the default, keeps every compiled one.
The headers declare the StringZilla library, so the simplest integration links it and includes the umbrella header. Its dispatch points pick the best capability at runtime, so one binary runs optimally on any CPU.
#include <stringzilla/stringzilla.h> // C#include <stringzilla/stringzilla.hpp> // C++
namespace sz = ashvardanian::stringzilla;Each family can also be included on its own when you only need a slice of the API and want to keep compile times down:
#include <stringzilla/find.h> // `sz_find_best`, `sz_rfind_best`, byte-set scans
#include <stringzilla/hash.h> // `sz_hash_best`, `sz_bytesum_best`, incremental state
#include <stringzilla/sort.h> // `sz_sort_order_best`
#include <stringzilla/compare.h> // `sz_equal_best`, `sz_order_best`
#include <stringzilla/intersect.h> // `sz_intersect_best`
#include <stringzilla/strings.h> // `sz_strings_to_tape_best`, `sz_strings_lookup_best`
#include <stringzilla/small_string.h> // `sz_string_t` SSO container
#include <stringzilla/levenshtein.h> // `sz_levenshtein_engine_init`, `sz_levenshtein_distances`
#include <stringzilla/overlap.h> // `sz_overlap_engine_init`, `sz_overlap_scores`
#include <stringzilla/substrings.h> // `sz_substrings_engine_init`, `sz_substrings_counts`The three engine families prepare a batch of queries once and score it against many batches of candidates, writing into a strided block the caller supplies rather than allocating one per round.
Each init takes the mask, the allocator and a stream last, and each round is a single verb with no _best twin, as the engine already holds the capability its init picked:
sz_status_t sz_levenshtein_engine_init(sz_levenshtein_engine_t *engine, sz_strings_view_t const *queries,
sz_levenshtein_symbol_t symbol, sz_capability_t capabilities,
sz_allocator_t *allocator, sz_stream_t stream);
sz_status_t sz_levenshtein_distances(sz_levenshtein_engine_t *engine, sz_strings_view_t const *candidates,
sz_size_t const *symbol_counts, sz_size_t const *order, sz_size_t bound,
sz_size_t *distances, sz_size_t distances_stride, sz_stream_t stream);
void sz_levenshtein_engine_free(sz_levenshtein_engine_t *engine, sz_stream_t stream);Their per-tier building blocks and their design notes live beside the kernels, in levenshtein/README.md, overlap/README.md, and substrings/README.md.
With CMake 3.14 or newer, pull the project in with FetchContent and link the static target stringzilla::static, which compiles every capability the toolchain can build.
include(FetchContent)
FetchContent_Declare(
stringzilla
GIT_REPOSITORY https://github.com/ashvardanian/StringZilla.git
GIT_TAG main)
FetchContent_MakeAvailable(stringzilla)
target_link_libraries(your_app PRIVATE stringzilla::static)add_subdirectory(stringzilla) works the same way.
Pulled in as a subproject, the static library is compiled only once something links it, and STRINGZILLA_BUILD_SHARED defaults to off.
Turn that on for the shared target stringzilla::shared:
set(STRINGZILLA_BUILD_SHARED ON)
FetchContent_MakeAvailable(stringzilla)
target_link_libraries(your_app PRIVATE stringzilla::shared)Either library’s dispatch points pick a capability by the mask each call passes, so one artifact runs optimally on any CPU.
C code that wants no compiled dependency links the interface target stringzilla::header, which adds include/ to your search path and defines STRINGZILLA_HEADER_ONLY=1:
target_link_libraries(your_app PRIVATE stringzilla::header)The kernels your compiler flags enable then compile into your translation units, and you call them by name, like sz_find_haswell.
Every dispatch point and finder reports sz_missing_library_k there, so the C++ layer runs the serial kernels over its default mask, which is empty in these builds.
Configure with -DSTRINGZILLA_INSTALL=ON to install the headers and libraries system-wide, then find them from an unrelated project:
find_package(stringzilla REQUIRED)
target_link_libraries(your_app PRIVATE stringzilla::shared)stringzilla::shared joins the package whenever STRINGZILLA_BUILD_SHARED was on for the install. stringzilla::header always does, as it needs no submodules and compiles nothing, so it works straight out of a release tarball.
A shared-library install also writes lib/pkgconfig/stringzilla.pc, for build systems that read pkg-config rather than CMake:
cc main.c $(pkg-config --cflags --libs stringzilla) -o mainThe behavior of the core is controlled entirely by preprocessor macros, so you can tune it without touching the sources.
The most important ones, mirrored from stringzilla.h:
| Macro | Default | Effect |
|---|---|---|
STRINGZILLA_HEADER_ONLY | 0 | Inline kernels instead of the library |
STRINGZILLA_DEBUG | 0 | Debug assertions and logging |
STRINGZILLA_WITH_LIBC | 1 | 0 builds freestanding, without libc |
STRINGZILLA_ALLOW_MISALIGNED_LOADS | platform | Unaligned word loads in SWAR fallbacks |
STRINGZILLA_SWAR_THRESHOLD | 24 | Length below which scalar loops are used |
STRINGZILLA_DEFAULT_ALIGNMENT | platform | Default alignment and padding policy |
STRINGZILLA_HEADER_ONLY chooses between the two builds: with 0, the default, the dispatch points live in the library, which compiles every capability and picks one by the mask each call passes; with 1 the kernels your compiler flags enable inline into your translation unit, and the dispatch points report sz_missing_library_k. STRINGZILLA_WITH_LIBC=0 builds freestanding without the C standard library, which disables the default malloc-based allocator and the offsetof static checks. STRINGZILLA_ALLOW_MISALIGNED_LOADS allows unaligned word loads in the SWAR fallbacks where the platform permits.
Per-ISA backends are toggled with their own macros; if left undefined they are auto-detected from the compiler’s target flags: STRINGZILLA_TARGET_WESTMERE for SSE4.2 + AES-NI, STRINGZILLA_TARGET_GOLDMONT for SHA-NI, STRINGZILLA_TARGET_HASWELL for AVX2, STRINGZILLA_TARGET_SKYLAKE for AVX-512 F/BW/VL, STRINGZILLA_TARGET_ICELAKE for AVX-512 VBMI + VAES, then STRINGZILLA_TARGET_NEON, STRINGZILLA_TARGET_NEONAES, STRINGZILLA_TARGET_NEONSHA, STRINGZILLA_TARGET_SVE, STRINGZILLA_TARGET_SVE2, and STRINGZILLA_TARGET_SVE2AES on ARM, STRINGZILLA_TARGET_V128 and STRINGZILLA_TARGET_V128RELAXED for WebAssembly SIMD128, STRINGZILLA_TARGET_RVV and STRINGZILLA_TARGET_RVVCRYPTO for the RISC-V Vector extension, STRINGZILLA_TARGET_LOONGSONASX for LoongArch, and STRINGZILLA_TARGET_POWERVSX for IBM Power.
The umbrella header also exposes the version triple as STRINGZILLA_VERSION_MAJOR/_MINOR/_PATCH, with matching sz_version_major(), sz_version_minor(), and sz_version_patch() accessors.
The headers require only C 99 and C++ 20, so any reasonably modern compiler builds the serial and SWAR baseline. The per-ISA SIMD kernels and the project’s own tests are CI-validated with these toolchains:
| Toolchain | Recommended Versions |
|---|---|
| GCC | 13 or newer |
| Clang | 18 or newer |
| MSVC | Visual Studio 2022 |
| NVCC | CUDA 13+ |
The RVV kernels need Clang 21 or newer, as older Clang hides the 64-bit vector types from a unit compiled without v, and an older compiler builds the libraries without them.
On macOS, prefer Homebrew Clang over Apple Clang; on Windows, MinGW with GCC works alongside MSVC.
NVCC with CUDA 13 builds the device backends of the engine families, reached by building an engine with the mask sz_capabilities_enabled_cuda reports for a device.
HIP-Clang builds the same sources for AMD GPUs, reached through sz_capabilities_enabled_rocm.
Built with STRINGZILLA_METAL_ARCH, the three engines also run on Apple GPUs of the Apple7 family and newer, through the same constructors and verbs.
A device round reads memory the device reaches, and strings only as a tape, never through an accessor, which is host code. strings.h declares allocation and gathering into tapes; capabilities.h declares stream creation, synchronization and release. Their _best dispatch points take a mask, with _serial, _cuda, _rocm and _metal twins, the GPU ones exported only where built:
sz_allocator_init_unified_best fills a stateless sz_allocator_t whose blocks the host and the device share: the heap on the CPU, managed memory on CUDA and ROCm, shared buffers on Metal, each made on the device of the stream it is called with.sz_strings_to_tape_best gathers strings of any layout into a sz_strings_span_t tape of payload and offsets, two blocks from the allocator it is given, and sz_strings_span_free returns both.sz_stream_synchronize_best waits for one stream, after which what a round wrote is readable from the host.sz_capabilities_enabled_cuda(0, &caps);
sz_capabilities_enabled_cpu(&cpu_caps);
sz_allocator_init_unified_best(&unified, caps);
sz_strings_span_t tape = {0};
sz_strings_to_tape_best(&host_candidates, NULL, host_candidates.count, &unified, &tape, cpu_caps, NULL);
sz_strings_view_t candidates;
sz_strings_from_span(&tape, &candidates);
sz_levenshtein_engine_init(&engine, &queries, sz_levenshtein_bytes_k, caps, NULL, stream);
sz_levenshtein_distances(&engine, &candidates, NULL, NULL, STRINGZILLA_SIZE_MAX, distances, candidates.count, stream);
sz_stream_synchronize_best(caps, stream);
sz_levenshtein_engine_free(&engine, stream);
sz_strings_span_free(&tape, &unified, stream);A null stream is the default stream of the default device, and a vendor’s devices are only numbered by its producers, like sz_capabilities_enabled_cuda(ordinal, &caps).
StringZilla also compiles to WebAssembly: the wasm32 toolchain targets wasm32-wasip1, and STRINGZILLA_MARCH names the module’s one SIMD capability, serial, v128 or the default v128relaxed, which enables the STRINGZILLA_TARGET_V128 and STRINGZILLA_TARGET_V128RELAXED kernels listed above.
STRINGZILLA_MULTIVERSIONEDCompiles every kernel into this translation unit and stubs every dispatch point and finder with sz_missing_library_k, instead of linking them from the StringZilla library.#define STRINGZILLA_MULTIVERSIONED 1 0 folds each dispatch table to its best kernel; 1, the default, keeps every compiled one.
STRINGZILLA_TARGET_SERIALDefining the serial kernels: STRINGZILLA_TARGET_SERIAL.#define STRINGZILLA_TARGET_SERIAL STRINGZILLA_HEADER_ONLY Header-only builds define them in every translation unit; in the library only its serial unit does, so units that include a serial header for its helpers leave its kernels to the library.
STRINGZILLA_DEFAULT_ALIGNMENTDefault alignment policy for padding and allocation-size heuristics.#define STRINGZILLA_DEFAULT_ALIGNMENT 128