Zeroshtein

March 15, 2023 ยท View on GitHub

Simple and fast implementation of the Levenshtein distance algorithm. And nothing more. It utilizes Span<T> over stackallock to avoid allocations and improve performance. And ArrayPool<T> in case of exceed MaxStackAllocSize static property value.

It has a lot of optimizations and tricks to improve performance:

  • Do not use unsafe code.
  • Reducing array indexings.
  • Allocating only array for current line calculation and reusing it for next line calculation.
  • Zero index is not part of the line and stored in a separate variable.
  • etc.

Benchmarks

And in result it extremely fast on Windows and MacOS. It even faster than Fastenshtein library (a little bit :smirk:).

Windows 11, x86

MethodMeanErrorStdDevRatioGen0Allocated
Fastenshtein7.613 us0.0295 us0.0276 us1.000.93085896 B
FastenshteinZeroAlloc6.845 us0.0136 us0.0120 us0.90--
Zeroshtein6.958 us0.0093 us0.0083 us0.91--
ZeroshteinInMemory12.122 us0.0263 us0.0246 us1.59--
StringSimilarity17.948 us0.0691 us0.0647 us2.361.739511064 B
NinjaNye19.700 us0.0586 us0.0490 us2.597.141144880 B
FuzzyStrings77.905 us0.2432 us0.2031 us10.2326.8555169040 B
Quickenshtein23.650 us0.0324 us0.0271 us3.11--

Benchmark was performed in next environment:

BenchmarkDotNet=v0.13.5, OS=Windows 11 (10.0.22000.1574/21H2/SunValley)
Intel Core i7-9700K CPU 3.60GHz (Coffee Lake), 1 CPU, 8 logical and 8 physical cores
.NET SDK=7.0.102
  [Host]     : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
  DefaultJob : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2

MacOS 13.2, arm64

MethodMeanErrorStdDevRatioGen0Allocated
Fastenshtein5.690 us0.0240 us0.0225 us1.000.93845896 B
FastenshteinZeroAlloc5.237 us0.0142 us0.0132 us0.92--
Zeroshtein5.469 us0.0105 us0.0093 us0.96--
ZeroshteinInMemory6.906 us0.0188 us0.0176 us1.21--
StringSimilarity8.884 us0.0173 us0.0162 us1.561.754811064 B
NinjaNye11.147 us0.0263 us0.0219 us1.967.141144880 B
FuzzyStrings46.450 us0.1166 us0.0974 us8.1726.9165169040 B
Quickenshtein5.692 us0.0115 us0.0108 us1.00--

Benchmark was performed in next environment:

BenchmarkDotNet=v0.13.5, OS=macOS Ventura 13.2.1 (22D68) [Darwin 22.3.0]
Apple M1 Pro, 1 CPU, 10 logical and 10 physical cores
.NET SDK=7.0.101
  [Host]     : .NET 7.0.1 (7.0.122.56804), Arm64 RyuJIT AdvSIMD
  DefaultJob : .NET 7.0.1 (7.0.122.56804), Arm64 RyuJIT AdvSIMD

Usage

var distance = Zeroshtein.Levenshtein.Distance("horce", "rose");

By default Zeroshtein uses stackallock to allocate calculation array in stack only. And if it exceeds MaxStackAllocSize static property value it uses ArrayPool<T> to allocate array in memory. Default value of MaxStackAllocSize is 1024 bytes. But can be changed to any value, it is a static property.

Zeroshtein.Levenshtein.MaxStackAllocSize = 4096;

It should be set with caution. Big value can cause StackOverflowException in case of long strings. This exception can't be caught and handled. It will cause application crash.