AndanteSoft.SpanLinq 1.0.1

dotnet add package AndanteSoft.SpanLinq --version 1.0.1                
NuGet\Install-Package AndanteSoft.SpanLinq -Version 1.0.1                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AndanteSoft.SpanLinq" Version="1.0.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add AndanteSoft.SpanLinq --version 1.0.1                
#r "nuget: AndanteSoft.SpanLinq, 1.0.1"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install AndanteSoft.SpanLinq as a Cake Addin
#addin nuget:?package=AndanteSoft.SpanLinq&version=1.0.1

// Install AndanteSoft.SpanLinq as a Cake Tool
#tool nuget:?package=AndanteSoft.SpanLinq&version=1.0.1                

SpanLinq

Lightweight, Zero Allocation LINQ Implementation on Span<T>

<a href="https://www.nuget.org/packages/AndanteSoft.SpanLinq">NuGet Version</a> <a href="LICENSE">GitHub License</a> <a href="https://www.nuget.org/packages/AndanteSoft.SpanLinq">NuGet Downloads</a>

Logo

Usage

It's almost the same as LINQ, but it's implemented as an extension method on Span<T> / ReadOnlySpan<T> and can also be accessed starting from a SpanEnumerable.

using SpanLinq;

string[] array =
    SpanEnumerable.Range(0, 10)
        .Skip(2)
        .Where(i => i % 2 == 0)
        .Select(i => i.ToString())
        .ToArray();

string largestString = array.AsSpan()
    .Union(new[] { "apple", "banana" })
    .MaxBy(s => s.Length);

Install

SpanLinq can be installed from NuGet AndanteSoft.SpanLinq.

dotnet add package AndanteSoft.SpanLinq 

Unity

Supported version: 2021.2 or later. (API Compatibility Level: .NET Standard 2.1)

My test environment is 2022.2.17f1.

Use NuGetForUnity to install.

Fork

Build
dotnet build
Run tests
dotnet test
Run benchmarks
dotnet run -c Release --project SpanLinq.Benchmarks

Feature

Pros.

  • It covers ALL LINQ methods implemented up to .NET 9 Preview 6. If you're tied to the old .NET you can still benefit from the new methods.
  • It produces no GC garbage (except when allocation is unavoidable, such as ToList()), reducing pressure on the garbage collector and improving performance.
  • This requires .NET Standard 2.1, which means it can be used in Unity.
  • Like LINQ, it supports deferred execution, although it is somewhat limited by the nature of a Span<T>.

Cons.

  • No GC garbage is not magic. It utilizes memory pools, so memory consumption during execution can be relatively high. (TODO: citation needed)
  • Some methods are not fully optimized and may result in slower performance than LINQ.
    • Of course it depends on the method. Some are twice as fast, some are twice as slow. See Performance.

Performance

See docs/BenchmarkResult.md.

  • System*** are System.Linq.Enumerable.***
  • Span*** are SpanLinq.SpanEnumerable.***
  • Handcrafted*** are optimized codes that uses Span<T> directly.

Specific differences between System.Linq.Enumerable

SpanLinq is designed to be a nearly complete replacement for System.Linq, but there are some differences in behavior and implementation.

Cannot be enumerated more than once

Queries cannot be cached, for example:

var query = SpanEnumrable.Range(0, 10).Where(i => i % 2 == 0);

int min = query.Min();

// NG: Cannot use a query twice, results are undefined
int max = query.Max();

To avoid this, materialize query using ToArray() or CopyTo() etc.

AsEnumerable()

AsEnumerable() is a method that is almost meaningless in System.Linq, but since Span<T> is not IEnumerable<T>, this method converts Span<T> into a class that implements IEnumerable<T> and returns it.

Currently, it allocates 32 B per call.

Cast()

Casting from a struct to a interface incurs an allocation by boxing, which cannot be avoided in principle.

Chunk()

Since it returns an array, allocation occurs.

CopyTo()

This method copies the current sequence to an existing span, similar to Span<T>.CopyTo().

ToArray() inherently causes allocation, but this method does not.

ForEach()

This method does not exist in LINQ. This is the original implementation.

This method calls Action<T> on every element of a sequence. This method executes immediately.

SpanEnumerable.FromEnumerable()

This method converts an IEnumerable<T> into a SpanEnumerator<>.

This allows sequences implemented with yield return to be handled by a SpanEnumerator<>.

static IEnumerable<string> Sample()
{
    yield return "Alice";
    yield return "Barbara";
    yield return "Charlotte";
}

var spanEnumerable = SpanEnumerable.FromEnumerable(Sample());

GroupBy()

Since it returns IGrouping<TKey, TElement>, allocation occurs.

Shuffle()

This method does not exist in LINQ. This is the original implementation.

Shuffle() randomly shuffles the order of the elements of a sequence. If you need reproducibility, call it as Shuffle(random).

Sum()

It returns int/double/etc. instead of int?/double?.

ToArrayPool()

This method gets a shared array via ArrayPool<T>.Shared.Rent() and returns the array and a span.

When you're done with the data, you must return the array via ArrayPool<T>.Shared.Return().

ToArray() / ToDictionary() / ToHashSet() / ToList() / ToLookup()

It is by nature that allocation occurs.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.1 175 8/6/2024
1.0.0 80 8/3/2024
0.1.0-alpha2 67 8/3/2024