├── .editorconfig ├── .github ├── dependabot.yaml └── workflows │ ├── build-debug.yml │ ├── build-release.yml │ └── stale.yml ├── .gitignore ├── Directory.Build.props ├── Icon.png ├── LICENSE.md ├── README.md ├── SimdLinq.sln ├── docs └── simdlinq_graph.xlsx ├── opensource.snk ├── sandbox └── Benchmark │ ├── Benchmark.csproj │ └── Program.cs ├── src └── SimdLinq │ ├── SimdLinq.csproj │ ├── SimdLinqExtensions.Average.cs │ ├── SimdLinqExtensions.Average.tt │ ├── SimdLinqExtensions.AverageCore.cs │ ├── SimdLinqExtensions.Contains.cs │ ├── SimdLinqExtensions.Contains.tt │ ├── SimdLinqExtensions.ContainsCore.cs │ ├── SimdLinqExtensions.LongSum.cs │ ├── SimdLinqExtensions.LongSum.tt │ ├── SimdLinqExtensions.Max.cs │ ├── SimdLinqExtensions.Max.tt │ ├── SimdLinqExtensions.Min.cs │ ├── SimdLinqExtensions.Min.tt │ ├── SimdLinqExtensions.MinMax.cs │ ├── SimdLinqExtensions.MinMax.tt │ ├── SimdLinqExtensions.MinMaxCore.cs │ ├── SimdLinqExtensions.SequenceEqual.cs │ ├── SimdLinqExtensions.SequenceEqual.tt │ ├── SimdLinqExtensions.Sum.cs │ ├── SimdLinqExtensions.Sum.tt │ ├── SimdLinqExtensions.SumCore.cs │ ├── SimdLinqExtensions.cs │ └── SimdLinqExtensions.ttinclude └── tests └── SimdLinq.Tests ├── AverageTest.cs ├── ContainsTest.cs ├── MinMaxTest.cs ├── SequenceEqualTest.cs ├── SimdLinq.Tests.csproj └── SumTest.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | insert_final_newline = true 6 | indent_style = space 7 | trim_trailing_whitespace = true 8 | 9 | [*.{sh,yaml,yml,rules}] 10 | charset = utf-8 11 | indent_size = 2 12 | end_of_line = lf 13 | 14 | [*{_AssemblyInfo.cs,.notsupported.cs}] 15 | generated_code = true 16 | 17 | # C# files 18 | [*.cs] 19 | charset = utf-8-bom 20 | indent_size = 4 21 | 22 | csharp_style_namespace_declarations = file_scoped 23 | dotnet_style_require_accessibility_modifiers = never 24 | 25 | # VSTHRD101: Avoid async void 26 | # VSTHRD101: Avoid unsupported async delegates 27 | dotnet_diagnostic.VSTHRD100.severity = none 28 | dotnet_diagnostic.VSTHRD101.severity = none 29 | 30 | # VSTHRD003: Avoid awaiting foreign Tasks 31 | dotnet_diagnostic.VSTHRD003.severity = none 32 | 33 | # VSTHRD111: Use ConfigureAwait(bool) 34 | dotnet_diagnostic.VSTHRD111.severity = error 35 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" # Check for updates to GitHub Actions every week 8 | ignore: 9 | # I just want update action when major/minor version is updated. patch updates are too noisy. 10 | - dependency-name: '*' 11 | update-types: 12 | - version-update:semver-patch 13 | -------------------------------------------------------------------------------- /.github/workflows/build-debug.yml: -------------------------------------------------------------------------------- 1 | name: Build-Debug 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - "main" 8 | pull_request: 9 | branches: 10 | - "main" 11 | 12 | jobs: 13 | build-dotnet: 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 10 16 | steps: 17 | - uses: Cysharp/Actions/.github/actions/checkout@main 18 | - uses: Cysharp/Actions/.github/actions/setup-dotnet@main 19 | - run: dotnet build -c Debug 20 | - run: dotnet test -c Debug --no-build 21 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: Build-Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: "tag: git tag you want create. (sample 1.0.0)" 8 | required: true 9 | dry-run: 10 | description: "dry-run: true will never create release/nuget." 11 | required: true 12 | default: false 13 | type: boolean 14 | 15 | jobs: 16 | build-dotnet: 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 10 19 | steps: 20 | - uses: Cysharp/Actions/.github/actions/checkout@main 21 | - uses: Cysharp/Actions/.github/actions/setup-dotnet@main 22 | # pack nuget 23 | - run: dotnet build -c Release -p:Version=${{ inputs.tag }} 24 | - run: dotnet test -c Release --no-build 25 | - run: dotnet pack -c Release --no-build -p:Version=${{ inputs.tag }} -p:PackageOutputPath=../../publish 26 | - uses: Cysharp/Actions/.github/actions/upload-artifact@main 27 | with: 28 | name: nuget 29 | path: ./publish 30 | retention-days: 1 31 | 32 | # release 33 | create-release: 34 | needs: [build-dotnet] 35 | uses: Cysharp/Actions/.github/workflows/create-release.yaml@main 36 | with: 37 | commit-id: '' 38 | dry-run: ${{ inputs.dry-run }} 39 | tag: ${{ inputs.tag }} 40 | nuget-push: true 41 | secrets: inherit 42 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | 8 | jobs: 9 | stale: 10 | uses: Cysharp/Actions/.github/workflows/stale-issue.yaml@main 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | *_i.c 20 | *_p.c 21 | *.ilk 22 | *.obj 23 | *.pch 24 | *.pdb 25 | *.pgc 26 | *.pgd 27 | *.rsp 28 | *.sbr 29 | *.tlb 30 | *.tli 31 | *.tlh 32 | *.tmp 33 | *.log 34 | *.vspscc 35 | *.vssscc 36 | .builds 37 | 38 | # Visual C++ cache files 39 | ipch/ 40 | *.aps 41 | *.ncb 42 | *.opensdf 43 | *.sdf 44 | 45 | # Visual Studio profiler 46 | *.psess 47 | *.vsp 48 | *.vspx 49 | 50 | # Guidance Automation Toolkit 51 | *.gpState 52 | 53 | # ReSharper is a .NET coding add-in 54 | _ReSharper* 55 | 56 | # NCrunch 57 | *.ncrunch* 58 | .*crunch*.local.xml 59 | 60 | # Installshield output folder 61 | [Ee]xpress 62 | 63 | # DocProject is a documentation generator add-in 64 | DocProject/buildhelp/ 65 | DocProject/Help/*.HxT 66 | DocProject/Help/*.HxC 67 | DocProject/Help/*.hhc 68 | DocProject/Help/*.hhk 69 | DocProject/Help/*.hhp 70 | DocProject/Help/Html2 71 | DocProject/Help/html 72 | 73 | # Click-Once directory 74 | publish 75 | 76 | # Publish Web Output 77 | *.Publish.xml 78 | 79 | # NuGet Packages Directory 80 | # packages # upm pacakge will use Packages 81 | 82 | # Windows Azure Build Output 83 | csx 84 | *.build.csdef 85 | 86 | # Windows Store app package directory 87 | AppPackages/ 88 | 89 | # Others 90 | [Bb]in 91 | [Oo]bj 92 | sql 93 | TestResults 94 | [Tt]est[Rr]esult* 95 | *.Cache 96 | ClientBin 97 | [Ss]tyle[Cc]op.* 98 | ~$* 99 | *.dbmdl 100 | Generated_Code #added for RIA/Silverlight projects 101 | 102 | # Backup & report files from converting an old project file to a newer 103 | # Visual Studio version. Backup files are not needed, because we have git ;-) 104 | _UpgradeReport_Files/ 105 | Backup*/ 106 | UpgradeLog*.XML 107 | .vs/config/applicationhost.config 108 | .vs/restore.dg 109 | 110 | nuget/tools/* 111 | nuget/*.nupkg 112 | nuget/*.unitypackage 113 | .vs/ 114 | 115 | # Jetbrains Rider 116 | .idea/ 117 | 118 | __packages/ 119 | 120 | sandbox/SandboxConsoleApp/MemoryPackLogs/ 121 | /sandbox/SandboxWebApp/node_modules 122 | 123 | # Unity 124 | 125 | src/*.Unity/Library/* 126 | src/*.Unity/Temp/* 127 | src/*.Unity/Logs/* 128 | 129 | src/*.Unity/*.csproj 130 | src/*.Unity/*.sln 131 | src/*.Unity/*.*.unitypackage 132 | 133 | src/StructureOfArraysGenerator.Unity/UserSettings/Layouts/default-2021.dwlt 134 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | latest 4 | enable 5 | true 6 | $(NoWarn);CS1591 7 | true 8 | $(MSBuildThisFileDirectory)opensource.snk 9 | 10 | 11 | $(Version) 12 | Cysharp 13 | Cysharp 14 | © Cysharp, Inc. 15 | https://github.com/Cysharp/SimdLinq 16 | $(PackageProjectUrl) 17 | git 18 | MIT 19 | Icon.png 20 | $(MSBuildThisFileDirectory)opensource.snk 21 | 22 | -------------------------------------------------------------------------------- /Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cysharp/SimdLinq/f00603b250e3eaeac31c5de292b6a5cbcbff85f7/Icon.png -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Cysharp, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > [Cysharp/ZLinq](https://github.com/Cysharp/ZLinq), a new zero allocation LINQ library, includes LINQ to SIMD capabilities, it now contains all SimdLinq features. Please migrate to ZLinq. 3 | 4 | # SimdLinq 5 | [![GitHub Actions](https://github.com/Cysharp/SimdLinq/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/SimdLinq/actions) [![Releases](https://img.shields.io/github/release/Cysharp/SimdLinq.svg)](https://github.com/Cysharp/SimdLinq/releases) 6 | 7 | SimdLinq is a drop-in replacement of LINQ aggregation operations(`Sum`, `Average`, `Min`, `Max`) extremely faster with SIMD. 8 | 9 | ![image](https://user-images.githubusercontent.com/46207/215410106-b68d8567-5abf-4aa4-a050-a803b1913187.png) 10 | 11 | [.NET 7 LINQ supports SIMD](https://devblogs.microsoft.com/dotnet/performance_improvements_in_net_7/#linq) but it is very limited, due to compatibility and safety issues, it is only enabled for `int[]` `Average`, `Min`, `Max` and `long[]` `Min`, `Max`. 12 | 13 | SimdLinq accelerates many methods (`Sum`, `LongSum`, `Average`, `Min`, `Max`, `MinMax`, `Contains`, `SequenceEqual`) and types (`byte`, `sbyte`, `short`, `ushort`, ` int`, `uint`, `long`, `ulong`, `float`, `double`). It can also be used with `List`, `Span`, `ReadOnlySpan`, `Memory`, `ReadOnlyMemory` in addition to `T[]`. 14 | 15 | Using the overload resolution priority, all target methods are automatically SIMD by simply referencing the library and setting global using. 16 | 17 | Quick Start 18 | --- 19 | This library is distributed via NuGet. 20 | 21 | > PM> Install-Package [SimdLinq](https://www.nuget.org/packages/SimdLinq) 22 | 23 | Note that this library requires **.NET 7** or above because this library uses static abstract members and the new cross platform **.NET 7** SIMD Api. 24 | 25 | ```csharp 26 | using SimdLinq; // enable SimdLinq extension methods 27 | 28 | var array = Enumerable.Range(1, 100000).ToArray(); 29 | 30 | var sum = array.Sum(); // uses SimdLinqExtensions.Sum 31 | ``` 32 | 33 | To enable SimdLinq per file, add the `using SimdLinq;` using directive. To enable SimdLinq across the project, use global usings in the csproj file. 34 | 35 | ```xml 36 | 37 | 38 | 39 | ``` 40 | 41 | `SimdLinqExtensions` has methods for concrete data types, like `int[]` or `Span`, with the same names as LINQ's methods. If a method is eligible for SIMD optimization (such as the Sum of int[]), the `SimdLinqExtensions` method will be used for improved performance. 42 | 43 | Unlike base LINQ, SimdLinq supports `Span`, allowing you to call methods such as `Sum`, `Min`, etc. on `Span` or `ReadOnlySpan` collections. 44 | 45 | ```csharp 46 | (double Min, double Max) GetMinMax(Span span) 47 | { 48 | return span.MinMax(); 49 | } 50 | ``` 51 | 52 | `MinMax` is an original SimdLinq extension, that returns a tuple of `Min` and `Max`. 53 | 54 | Compatibility 55 | --- 56 | One of the reasons why LINQ's SIMD support in .NET 7 is incomplete, is compatibility. SimdLinq prioritises performance over some safety features and full compatibility with LINQ. Please note the following differences. 57 | 58 | ### Sum/Average 59 | 60 | LINQ Sum is `checked` but SimdLinq is `unchecked`(SIMD operation is not supported overflow). To reduce the risk of overflow, `Sum` and `Average` only support types that are 32-bit or higher(`int`, `long`, `uint`, `ulong`, `double`, `float`). 61 | 62 | SimdLinq provides `LongSum` for `int` and `uint`, that returns `long`/`ulong` so avoid overflow. 63 | 64 | ### float/double 65 | 66 | Unlike LINQ, SimdLinq does not check for NaN in float/double Min/Max calculations. Additionally, the order of calculation for Sum is not sequential, leading to slight differences in floating-point operations compared to regular LINQ. For instance, LINQ returns 1.5710588F, while SimdLinq returns 1.5710589F. If compatibility is not a concern, this difference is not significant, but be aware of potential small tolerance differences. 67 | 68 | Supported collections 69 | --- 70 | `T[]`, `List`, `Span`, `Memory`, `ReadOnlyMemory`, `Span`, `ReadOnlySpan`. 71 | 72 | Supported methods 73 | --- 74 | * `Sum` for `int`, `uint`, `long`, `ulong`, `float`, `double` 75 | * `LongSum` for `int`, `uint` 76 | * `Average` for `int`, `uint`, `long`, `ulong`, `float`, `double` 77 | * `Min` for `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double` 78 | * `Max` for `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double` 79 | * `MinMax` for `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double` 80 | * `Contains` for `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double` 81 | * `SequenceEqual` for `byte`, `sbyte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double` 82 | 83 | SoA 84 | --- 85 | SIMD with LINQ requires primitive array(or Span). [Cysharp/StructureOfArraysGenerator](https://github.com/Cysharp/StructureOfArraysGenerator) makes easy to create SoA array to use SIMD easily. 86 | 87 | License 88 | --- 89 | This library is licensed under the MIT License. 90 | -------------------------------------------------------------------------------- /SimdLinq.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33122.133 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimdLinq", "src\SimdLinq\SimdLinq.csproj", "{AA3CCB48-DEFB-46AC-8E59-234735CB46FA}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{45E7884C-F0DE-4A7F-AD46-24D712E689D5}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sandbox", "sandbox", "{2E552713-81E9-456B-89CB-4AE514224FAA}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8F6AE842-1F39-42D9-935A-995F00132030}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimdLinq.Tests", "tests\SimdLinq.Tests\SimdLinq.Tests.csproj", "{152C1467-82AE-4EB5-A5D1-24ED455FE214}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Benchmark", "sandbox\Benchmark\Benchmark.csproj", "{B3A429CC-AC28-4A60-8DA0-9C542B602684}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {AA3CCB48-DEFB-46AC-8E59-234735CB46FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {AA3CCB48-DEFB-46AC-8E59-234735CB46FA}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {AA3CCB48-DEFB-46AC-8E59-234735CB46FA}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {AA3CCB48-DEFB-46AC-8E59-234735CB46FA}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {152C1467-82AE-4EB5-A5D1-24ED455FE214}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {152C1467-82AE-4EB5-A5D1-24ED455FE214}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {152C1467-82AE-4EB5-A5D1-24ED455FE214}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {152C1467-82AE-4EB5-A5D1-24ED455FE214}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {B3A429CC-AC28-4A60-8DA0-9C542B602684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {B3A429CC-AC28-4A60-8DA0-9C542B602684}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {B3A429CC-AC28-4A60-8DA0-9C542B602684}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {B3A429CC-AC28-4A60-8DA0-9C542B602684}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(NestedProjects) = preSolution 41 | {AA3CCB48-DEFB-46AC-8E59-234735CB46FA} = {45E7884C-F0DE-4A7F-AD46-24D712E689D5} 42 | {152C1467-82AE-4EB5-A5D1-24ED455FE214} = {8F6AE842-1F39-42D9-935A-995F00132030} 43 | {B3A429CC-AC28-4A60-8DA0-9C542B602684} = {2E552713-81E9-456B-89CB-4AE514224FAA} 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {85BA7882-CB7B-40DA-9B27-2CCF09AB188A} 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /docs/simdlinq_graph.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cysharp/SimdLinq/f00603b250e3eaeac31c5de292b6a5cbcbff85f7/docs/simdlinq_graph.xlsx -------------------------------------------------------------------------------- /opensource.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cysharp/SimdLinq/f00603b250e3eaeac31c5de292b6a5cbcbff85f7/opensource.snk -------------------------------------------------------------------------------- /sandbox/Benchmark/Benchmark.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | enable 7 | True 8 | enable 9 | true 10 | false 11 | 12 | 13 | $(WarningsAsErrors);NU1605; 14 | $(NoWarn);CS1591;CS8604;CS8032;CS8002;CS8600 15 | 16 | 17 | 18 | $(WarningsAsErrors);NU1605; 19 | $(NoWarn);CS1591;CS8604;CS8032;CS8002;CS8600 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /sandbox/Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CA1050 // Declare types in namespaces 2 | using BenchmarkDotNet.Configs; 3 | using BenchmarkDotNet.Diagnosers; 4 | using BenchmarkDotNet.Exporters; 5 | using BenchmarkDotNet.Jobs; 6 | using BenchmarkDotNet.Running; 7 | using JM.LinqFaster.SIMD; 8 | using NetFabric.Hyperlinq; 9 | using SimdLinq; 10 | using System.ComponentModel; 11 | using System.Linq; 12 | using System.Numerics; 13 | using System.Reflection; 14 | 15 | var config = ManualConfig.CreateMinimumViable() 16 | .AddDiagnoser(MemoryDiagnoser.Default) 17 | .AddExporter(MarkdownExporter.Default) 18 | .AddJob(Job.Default.WithWarmupCount(1).WithIterationCount(1)); 19 | 20 | #if !DEBUG 21 | 22 | 23 | 24 | 25 | //BenchmarkRunner.Run(config, args); 26 | //BenchmarkRunner.Run(config, args); 27 | //BenchmarkRunner.Run(config, args); 28 | //BenchmarkRunner.Run(config, args); 29 | //BenchmarkRunner.Run(config, args); 30 | BenchmarkRunner.Run(config, args); 31 | 32 | // BenchmarkSwitcher.FromAssembly(Assembly.GetEntryAssembly()!).RunAllJoined(config); 33 | 34 | #else 35 | 36 | 37 | // _= Enumerable.Max((int[])null!); 38 | //System.Linq.Enumerable.Min(Array.Empty()); 39 | //_ = new IntMinBenchmark().SimdLinq(); 40 | 41 | #endif 42 | 43 | public class IntSumBenchmark 44 | { 45 | readonly int[] source; 46 | 47 | public IntSumBenchmark() 48 | { 49 | source = Enumerable.Range(1, 10000).ToArray(); 50 | 51 | var check = new[] { For(), LINQ(), SimdLinq(), LinqFasterSIMD(), Hyperlinq() }.Distinct().Count() == 1; 52 | if (!check) 53 | { 54 | throw new Exception("invalid result exists"); 55 | } 56 | } 57 | 58 | [Benchmark] 59 | public int For() 60 | { 61 | var array = source; 62 | var sum = 0; 63 | for (int i = 0; i < array.Length; i++) 64 | { 65 | sum += array[i]; 66 | } 67 | return sum; 68 | } 69 | 70 | [Benchmark] 71 | public int LINQ() 72 | { 73 | return Enumerable.Sum(source); 74 | } 75 | 76 | [Benchmark] 77 | public int SimdLinq() 78 | { 79 | return SimdLinqExtensions.Sum(source); 80 | } 81 | 82 | [Benchmark] 83 | public int LinqFasterSIMD() 84 | { 85 | return source.SumS(); 86 | } 87 | 88 | [Benchmark] 89 | public int Hyperlinq() 90 | { 91 | return source.AsValueEnumerable().Sum(); 92 | } 93 | } 94 | 95 | public class IntMinBenchmark 96 | { 97 | readonly int[] source; 98 | 99 | public IntMinBenchmark() 100 | { 101 | var rand = new Random(123); 102 | source = Enumerable.Range(1, 10000).Select(_ => rand.Next()).ToArray(); 103 | 104 | var check = new[] { For(), LINQ(), SimdLinq(), LinqFasterSIMD(), Hyperlinq() }.Distinct().Count() == 1; 105 | if (!check) 106 | { 107 | throw new Exception("invalid result exists"); 108 | } 109 | } 110 | 111 | [Benchmark] 112 | public int For() 113 | { 114 | var array = source; 115 | var min = array[0]; 116 | for (int i = 1; i < array.Length; i++) 117 | { 118 | if (array[i] < min) 119 | { 120 | min = array[i]; 121 | } 122 | } 123 | return min; 124 | } 125 | 126 | [Benchmark] 127 | public int LINQ() 128 | { 129 | return Enumerable.Min(source); 130 | } 131 | 132 | [Benchmark] 133 | public int SimdLinq() 134 | { 135 | return SimdLinqExtensions.Min(source); 136 | } 137 | 138 | [Benchmark] 139 | public int LinqFasterSIMD() 140 | { 141 | return source.MinS(); 142 | } 143 | 144 | [Benchmark] 145 | public int Hyperlinq() 146 | { 147 | return source.AsValueEnumerable().Min(); 148 | } 149 | } 150 | 151 | public class LongMinBenchmark 152 | { 153 | readonly long[] source; 154 | 155 | public LongMinBenchmark() 156 | { 157 | var rand = new Random(123); 158 | source = Enumerable.Range(1, 10000).Select(_ => rand.NextInt64()).ToArray(); 159 | 160 | var check = new[] { For(), LINQ(), SimdLinq(), LinqFasterSIMD(), Hyperlinq() }.Distinct().Count() == 1; 161 | if (!check) 162 | { 163 | throw new Exception("invalid result exists"); 164 | } 165 | } 166 | 167 | [Benchmark] 168 | public long For() 169 | { 170 | var array = source; 171 | var min = array[0]; 172 | for (int i = 1; i < array.Length; i++) 173 | { 174 | if (array[i] < min) 175 | { 176 | min = array[i]; 177 | } 178 | } 179 | return min; 180 | } 181 | 182 | [Benchmark] 183 | public long LINQ() 184 | { 185 | return Enumerable.Min(source); 186 | } 187 | 188 | [Benchmark] 189 | public long SimdLinq() 190 | { 191 | return SimdLinqExtensions.Min(source); 192 | } 193 | 194 | [Benchmark] 195 | public long LinqFasterSIMD() 196 | { 197 | return source.MinS(); 198 | } 199 | 200 | [Benchmark] 201 | public long Hyperlinq() 202 | { 203 | return source.AsValueEnumerable().Min(); 204 | } 205 | } 206 | 207 | public class DoubleMinBenchmark 208 | { 209 | readonly double[] source; 210 | 211 | public DoubleMinBenchmark() 212 | { 213 | var rand = new Random(123); 214 | source = Enumerable.Range(1, 10000).Select(_ => rand.NextDouble()).ToArray(); 215 | 216 | var check = new[] { For(), LINQ(), SimdLinq(), LinqFasterSIMD(), Hyperlinq() }.Distinct().Count() == 1; 217 | if (!check) 218 | { 219 | throw new Exception("invalid result exists"); 220 | } 221 | } 222 | 223 | [Benchmark] 224 | public double For() 225 | { 226 | var array = source; 227 | var min = array[0]; 228 | for (int i = 1; i < array.Length; i++) 229 | { 230 | if (array[i] < min) 231 | { 232 | min = array[i]; 233 | } 234 | } 235 | return min; 236 | } 237 | 238 | [Benchmark] 239 | public double LINQ() 240 | { 241 | return Enumerable.Min(source); 242 | } 243 | 244 | [Benchmark] 245 | public double SimdLinq() 246 | { 247 | return SimdLinqExtensions.Min(source); 248 | } 249 | 250 | [Benchmark] 251 | public double LinqFasterSIMD() 252 | { 253 | return source.MinS(); 254 | } 255 | 256 | [Benchmark] 257 | public double Hyperlinq() 258 | { 259 | return source.AsValueEnumerable().Min(); 260 | } 261 | } 262 | 263 | public class IntContainsBenchmark 264 | { 265 | readonly int[] source; 266 | readonly int target; 267 | public IntContainsBenchmark() 268 | { 269 | source = Enumerable.Range(1, 10000).ToArray(); 270 | target = 8345; 271 | var check = new[] { For(), LINQ(), SimdLinq(), LinqFasterSIMD(), Hyperlinq() }.Distinct().Count() == 1; 272 | if (!check) 273 | { 274 | throw new Exception("invalid result exists"); 275 | } 276 | } 277 | 278 | [Benchmark] 279 | public bool For() 280 | { 281 | var array = source; 282 | var t = target; 283 | for (int i = 0; i < array.Length; i++) 284 | { 285 | if (array[i] == t) return true; 286 | } 287 | return false; 288 | } 289 | 290 | [Benchmark] 291 | public bool LINQ() 292 | { 293 | return Enumerable.Contains(source, target); 294 | } 295 | 296 | [Benchmark] 297 | public bool SimdLinq() 298 | { 299 | return SimdLinqExtensions.Contains(source, target); 300 | } 301 | 302 | [Benchmark] 303 | public bool LinqFasterSIMD() 304 | { 305 | return source.ContainsS(target); 306 | } 307 | 308 | [Benchmark] 309 | public bool Hyperlinq() 310 | { 311 | return source.AsValueEnumerable().Contains(target); 312 | } 313 | 314 | 315 | } 316 | 317 | 318 | public class SumVsLongSumBenchmark 319 | { 320 | readonly int[] source; 321 | 322 | public SumVsLongSumBenchmark() 323 | { 324 | source = Enumerable.Range(1, 10000).ToArray(); 325 | } 326 | 327 | [Benchmark] 328 | public int SimdSum() 329 | { 330 | return SimdLinqExtensions.Sum(source); 331 | } 332 | 333 | [Benchmark] 334 | public long SimdLongSum() 335 | { 336 | return SimdLinqExtensions.LongSum(source); 337 | } 338 | 339 | [Benchmark] 340 | public int LinqSum() 341 | { 342 | return Enumerable.Sum(source); 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinq.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | true 8 | $(NoWarn);CS1591;CA2255 9 | linq 10 | Drop-in replacement of LINQ aggregation operations extremely faster with SIMD. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | SimdLinqExtensions.LongSum.cs 20 | TextTemplatingFileGenerator 21 | 22 | 23 | SimdLinqExtensions.Max.cs 24 | TextTemplatingFileGenerator 25 | 26 | 27 | SimdLinqExtensions.Min.cs 28 | TextTemplatingFileGenerator 29 | 30 | 31 | SimdLinqExtensions.Sum.cs 32 | TextTemplatingFileGenerator 33 | 34 | 35 | SimdLinqExtensions.Contains.cs 36 | TextTemplatingFileGenerator 37 | 38 | 39 | SimdLinqExtensions.SequenceEqual.cs 40 | TextTemplatingFileGenerator 41 | 42 | 43 | TextTemplatingFileGenerator 44 | SimdLinqExtensions.MinMax.cs 45 | 46 | 47 | TextTemplatingFileGenerator 48 | SimdLinqExtensions.Average.cs 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | SimdLinqExtensions.LongSum.tt 59 | True 60 | True 61 | 62 | 63 | SimdLinqExtensions.Max.tt 64 | True 65 | True 66 | 67 | 68 | SimdLinqExtensions.Min.tt 69 | True 70 | True 71 | 72 | 73 | SimdLinqExtensions.Sum.tt 74 | True 75 | True 76 | 77 | 78 | SimdLinqExtensions.Contains.tt 79 | True 80 | True 81 | 82 | 83 | SimdLinqExtensions.SequenceEqual.tt 84 | True 85 | True 86 | 87 | 88 | True 89 | True 90 | SimdLinqExtensions.MinMax.tt 91 | 92 | 93 | True 94 | True 95 | SimdLinqExtensions.Average.tt 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.Average.cs: -------------------------------------------------------------------------------- 1 | namespace SimdLinq; 2 | 3 | using System.Runtime.InteropServices; 4 | 5 | public static partial class SimdLinqExtensions 6 | { 7 | /// 8 | /// Computes the average of a sequence of values using SIMD acceleration. 9 | /// 10 | /// A collection of values to calculate the average of. 11 | /// The average of the collection of values. 12 | /// is . 13 | /// contains no elements. 14 | public static double Average(this int[] source) 15 | { 16 | ArgumentNullException.ThrowIfNull(source); 17 | return AverageCore(new ReadOnlySpan(source)); 18 | } 19 | 20 | /// 21 | /// Computes the average of a sequence of values using SIMD acceleration. 22 | /// 23 | /// A collection of values to calculate the average of. 24 | /// The average of the collection of values. 25 | /// is . 26 | /// contains no elements. 27 | public static double Average(this List source) 28 | { 29 | ArgumentNullException.ThrowIfNull(source); 30 | return AverageCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 31 | } 32 | 33 | /// 34 | /// Computes the average of a sequence of values using SIMD acceleration. 35 | /// 36 | /// A collection of values to calculate the average of. 37 | /// The average of the collection of values. 38 | /// contains no elements. 39 | public static double Average(this Memory source) 40 | { 41 | return AverageCore((ReadOnlySpan)source.Span); 42 | } 43 | 44 | /// 45 | /// Computes the average of a sequence of values using SIMD acceleration. 46 | /// 47 | /// A collection of values to calculate the average of. 48 | /// The average of the collection of values. 49 | /// contains no elements. 50 | public static double Average(this ReadOnlyMemory source) 51 | { 52 | return AverageCore(source.Span); 53 | } 54 | 55 | /// 56 | /// Computes the average of a sequence of values using SIMD acceleration. 57 | /// 58 | /// A collection of values to calculate the average of. 59 | /// The average of the collection of values. 60 | /// contains no elements. 61 | public static double Average(this Span source) 62 | { 63 | return AverageCore((ReadOnlySpan)source); 64 | } 65 | 66 | /// 67 | /// Computes the average of a sequence of values using SIMD acceleration. 68 | /// 69 | /// A collection of values to calculate the average of. 70 | /// The average of the collection of values. 71 | /// contains no elements. 72 | public static double Average(this ReadOnlySpan source) 73 | { 74 | return AverageCore(source); 75 | } 76 | 77 | /// 78 | /// Computes the average of a sequence of values using SIMD acceleration. 79 | /// 80 | /// A collection of values to calculate the average of. 81 | /// The average of the collection of values. 82 | /// is . 83 | /// contains no elements. 84 | public static double Average(this long[] source) 85 | { 86 | ArgumentNullException.ThrowIfNull(source); 87 | return AverageCore(new ReadOnlySpan(source)); 88 | } 89 | 90 | /// 91 | /// Computes the average of a sequence of values using SIMD acceleration. 92 | /// 93 | /// A collection of values to calculate the average of. 94 | /// The average of the collection of values. 95 | /// is . 96 | /// contains no elements. 97 | public static double Average(this List source) 98 | { 99 | ArgumentNullException.ThrowIfNull(source); 100 | return AverageCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 101 | } 102 | 103 | /// 104 | /// Computes the average of a sequence of values using SIMD acceleration. 105 | /// 106 | /// A collection of values to calculate the average of. 107 | /// The average of the collection of values. 108 | /// contains no elements. 109 | public static double Average(this Memory source) 110 | { 111 | return AverageCore((ReadOnlySpan)source.Span); 112 | } 113 | 114 | /// 115 | /// Computes the average of a sequence of values using SIMD acceleration. 116 | /// 117 | /// A collection of values to calculate the average of. 118 | /// The average of the collection of values. 119 | /// contains no elements. 120 | public static double Average(this ReadOnlyMemory source) 121 | { 122 | return AverageCore(source.Span); 123 | } 124 | 125 | /// 126 | /// Computes the average of a sequence of values using SIMD acceleration. 127 | /// 128 | /// A collection of values to calculate the average of. 129 | /// The average of the collection of values. 130 | /// contains no elements. 131 | public static double Average(this Span source) 132 | { 133 | return AverageCore((ReadOnlySpan)source); 134 | } 135 | 136 | /// 137 | /// Computes the average of a sequence of values using SIMD acceleration. 138 | /// 139 | /// A collection of values to calculate the average of. 140 | /// The average of the collection of values. 141 | /// contains no elements. 142 | public static double Average(this ReadOnlySpan source) 143 | { 144 | return AverageCore(source); 145 | } 146 | 147 | /// 148 | /// Computes the average of a sequence of values using SIMD acceleration. 149 | /// 150 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 151 | /// A collection of values to calculate the average of. 152 | /// The average of the collection of values. 153 | /// is . 154 | /// contains no elements. 155 | public static double Average(this double[] source) 156 | { 157 | ArgumentNullException.ThrowIfNull(source); 158 | return AverageCore(new ReadOnlySpan(source)); 159 | } 160 | 161 | /// 162 | /// Computes the average of a sequence of values using SIMD acceleration. 163 | /// 164 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 165 | /// A collection of values to calculate the average of. 166 | /// The average of the collection of values. 167 | /// is . 168 | /// contains no elements. 169 | public static double Average(this List source) 170 | { 171 | ArgumentNullException.ThrowIfNull(source); 172 | return AverageCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 173 | } 174 | 175 | /// 176 | /// Computes the average of a sequence of values using SIMD acceleration. 177 | /// 178 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 179 | /// A collection of values to calculate the average of. 180 | /// The average of the collection of values. 181 | /// contains no elements. 182 | public static double Average(this Memory source) 183 | { 184 | return AverageCore((ReadOnlySpan)source.Span); 185 | } 186 | 187 | /// 188 | /// Computes the average of a sequence of values using SIMD acceleration. 189 | /// 190 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 191 | /// A collection of values to calculate the average of. 192 | /// The average of the collection of values. 193 | /// contains no elements. 194 | public static double Average(this ReadOnlyMemory source) 195 | { 196 | return AverageCore(source.Span); 197 | } 198 | 199 | /// 200 | /// Computes the average of a sequence of values using SIMD acceleration. 201 | /// 202 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 203 | /// A collection of values to calculate the average of. 204 | /// The average of the collection of values. 205 | /// contains no elements. 206 | public static double Average(this Span source) 207 | { 208 | return AverageCore((ReadOnlySpan)source); 209 | } 210 | 211 | /// 212 | /// Computes the average of a sequence of values using SIMD acceleration. 213 | /// 214 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 215 | /// A collection of values to calculate the average of. 216 | /// The average of the collection of values. 217 | /// contains no elements. 218 | public static double Average(this ReadOnlySpan source) 219 | { 220 | return AverageCore(source); 221 | } 222 | 223 | /// 224 | /// Computes the average of a sequence of values using SIMD acceleration. 225 | /// 226 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 227 | /// A collection of values to calculate the average of. 228 | /// The average of the collection of values. 229 | /// is . 230 | /// contains no elements. 231 | public static float Average(this float[] source) 232 | { 233 | ArgumentNullException.ThrowIfNull(source); 234 | return AverageCore(new ReadOnlySpan(source)); 235 | } 236 | 237 | /// 238 | /// Computes the average of a sequence of values using SIMD acceleration. 239 | /// 240 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 241 | /// A collection of values to calculate the average of. 242 | /// The average of the collection of values. 243 | /// is . 244 | /// contains no elements. 245 | public static float Average(this List source) 246 | { 247 | ArgumentNullException.ThrowIfNull(source); 248 | return AverageCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 249 | } 250 | 251 | /// 252 | /// Computes the average of a sequence of values using SIMD acceleration. 253 | /// 254 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 255 | /// A collection of values to calculate the average of. 256 | /// The average of the collection of values. 257 | /// contains no elements. 258 | public static float Average(this Memory source) 259 | { 260 | return AverageCore((ReadOnlySpan)source.Span); 261 | } 262 | 263 | /// 264 | /// Computes the average of a sequence of values using SIMD acceleration. 265 | /// 266 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 267 | /// A collection of values to calculate the average of. 268 | /// The average of the collection of values. 269 | /// contains no elements. 270 | public static float Average(this ReadOnlyMemory source) 271 | { 272 | return AverageCore(source.Span); 273 | } 274 | 275 | /// 276 | /// Computes the average of a sequence of values using SIMD acceleration. 277 | /// 278 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 279 | /// A collection of values to calculate the average of. 280 | /// The average of the collection of values. 281 | /// contains no elements. 282 | public static float Average(this Span source) 283 | { 284 | return AverageCore((ReadOnlySpan)source); 285 | } 286 | 287 | /// 288 | /// Computes the average of a sequence of values using SIMD acceleration. 289 | /// 290 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 291 | /// A collection of values to calculate the average of. 292 | /// The average of the collection of values. 293 | /// contains no elements. 294 | public static float Average(this ReadOnlySpan source) 295 | { 296 | return AverageCore(source); 297 | } 298 | 299 | /// 300 | /// Computes the average of a sequence of values using SIMD acceleration. 301 | /// 302 | /// A collection of values to calculate the average of. 303 | /// The average of the collection of values. 304 | /// is . 305 | /// contains no elements. 306 | public static double Average(this uint[] source) 307 | { 308 | ArgumentNullException.ThrowIfNull(source); 309 | return AverageCore(new ReadOnlySpan(source)); 310 | } 311 | 312 | /// 313 | /// Computes the average of a sequence of values using SIMD acceleration. 314 | /// 315 | /// A collection of values to calculate the average of. 316 | /// The average of the collection of values. 317 | /// is . 318 | /// contains no elements. 319 | public static double Average(this List source) 320 | { 321 | ArgumentNullException.ThrowIfNull(source); 322 | return AverageCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 323 | } 324 | 325 | /// 326 | /// Computes the average of a sequence of values using SIMD acceleration. 327 | /// 328 | /// A collection of values to calculate the average of. 329 | /// The average of the collection of values. 330 | /// contains no elements. 331 | public static double Average(this Memory source) 332 | { 333 | return AverageCore((ReadOnlySpan)source.Span); 334 | } 335 | 336 | /// 337 | /// Computes the average of a sequence of values using SIMD acceleration. 338 | /// 339 | /// A collection of values to calculate the average of. 340 | /// The average of the collection of values. 341 | /// contains no elements. 342 | public static double Average(this ReadOnlyMemory source) 343 | { 344 | return AverageCore(source.Span); 345 | } 346 | 347 | /// 348 | /// Computes the average of a sequence of values using SIMD acceleration. 349 | /// 350 | /// A collection of values to calculate the average of. 351 | /// The average of the collection of values. 352 | /// contains no elements. 353 | public static double Average(this Span source) 354 | { 355 | return AverageCore((ReadOnlySpan)source); 356 | } 357 | 358 | /// 359 | /// Computes the average of a sequence of values using SIMD acceleration. 360 | /// 361 | /// A collection of values to calculate the average of. 362 | /// The average of the collection of values. 363 | /// contains no elements. 364 | public static double Average(this ReadOnlySpan source) 365 | { 366 | return AverageCore(source); 367 | } 368 | 369 | /// 370 | /// Computes the average of a sequence of values using SIMD acceleration. 371 | /// 372 | /// A collection of values to calculate the average of. 373 | /// The average of the collection of values. 374 | /// is . 375 | /// contains no elements. 376 | public static double Average(this ulong[] source) 377 | { 378 | ArgumentNullException.ThrowIfNull(source); 379 | return AverageCore(new ReadOnlySpan(source)); 380 | } 381 | 382 | /// 383 | /// Computes the average of a sequence of values using SIMD acceleration. 384 | /// 385 | /// A collection of values to calculate the average of. 386 | /// The average of the collection of values. 387 | /// is . 388 | /// contains no elements. 389 | public static double Average(this List source) 390 | { 391 | ArgumentNullException.ThrowIfNull(source); 392 | return AverageCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 393 | } 394 | 395 | /// 396 | /// Computes the average of a sequence of values using SIMD acceleration. 397 | /// 398 | /// A collection of values to calculate the average of. 399 | /// The average of the collection of values. 400 | /// contains no elements. 401 | public static double Average(this Memory source) 402 | { 403 | return AverageCore((ReadOnlySpan)source.Span); 404 | } 405 | 406 | /// 407 | /// Computes the average of a sequence of values using SIMD acceleration. 408 | /// 409 | /// A collection of values to calculate the average of. 410 | /// The average of the collection of values. 411 | /// contains no elements. 412 | public static double Average(this ReadOnlyMemory source) 413 | { 414 | return AverageCore(source.Span); 415 | } 416 | 417 | /// 418 | /// Computes the average of a sequence of values using SIMD acceleration. 419 | /// 420 | /// A collection of values to calculate the average of. 421 | /// The average of the collection of values. 422 | /// contains no elements. 423 | public static double Average(this Span source) 424 | { 425 | return AverageCore((ReadOnlySpan)source); 426 | } 427 | 428 | /// 429 | /// Computes the average of a sequence of values using SIMD acceleration. 430 | /// 431 | /// A collection of values to calculate the average of. 432 | /// The average of the collection of values. 433 | /// contains no elements. 434 | public static double Average(this ReadOnlySpan source) 435 | { 436 | return AverageCore(source); 437 | } 438 | 439 | } 440 | 441 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.Average.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <# 8 | string summary(string x, bool nullable) 9 | { 10 | var remark = ""; 11 | if(x is "double" or "float") 12 | { 13 | remark += """ 14 | 15 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Average. 16 | """; 17 | } 18 | var exception = ""; 19 | if (nullable) 20 | { 21 | exception += """ 22 | 23 | /// is . 24 | """; 25 | } 26 | 27 | return $""" 28 | /// 29 | /// Computes the average of a sequence of values using SIMD acceleration. 30 | /// {remark} 31 | /// A collection of values to calculate the average of. 32 | /// The average of the collection of values.{exception} 33 | /// contains no elements. 34 | """; 35 | } 36 | 37 | var retType = (string x) => x is "double" or "float" ? x : "double"; 38 | 39 | var methodName = "Average"; 40 | var types = new [] 41 | { 42 | "int", "long", "double", "float", "uint", "ulong" 43 | }; 44 | #> 45 | namespace SimdLinq; 46 | 47 | using System.Runtime.InteropServices; 48 | 49 | <#@ include file=".\SimdLinqExtensions.ttinclude" #> 50 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.AverageCore.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Numerics; 3 | 4 | namespace SimdLinq; 5 | 6 | public static partial class SimdLinqExtensions 7 | { 8 | static T AverageCore(ReadOnlySpan source) 9 | where T : struct, INumber 10 | { 11 | if (source.IsEmpty) ThrowNoElements(); 12 | return T.CreateChecked(SumCore(source)) / T.CreateChecked(source.Length); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.Contains.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <# 8 | string summary(string x, bool nullable) 9 | { 10 | var remark = ""; 11 | if(x is "double" or "float") 12 | { 13 | remark += """ 14 | 15 | /// This method does not check for / values and should not be used when may contain one. 16 | """; 17 | } 18 | var exception = ""; 19 | if (nullable) 20 | { 21 | exception += """ 22 | 23 | /// is . 24 | """; 25 | } 26 | 27 | return $""" 28 | /// 29 | /// Determines whether a sequence of contains a specified element using SIMD acceleration. 30 | /// {remark} 31 | /// A sequence of values in which to locate a value. 32 | /// The value to locate in the sequence. 33 | /// if the source sequence contains an element that has the specified value; otherwise, .{exception} 34 | /// contains no elements. 35 | """; 36 | } 37 | 38 | var types = new [] 39 | { 40 | "byte", "sbyte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double" 41 | }; 42 | #> 43 | namespace SimdLinq; 44 | 45 | using System.Runtime.InteropServices; 46 | 47 | public static partial class SimdLinqExtensions 48 | { 49 | <# foreach(var type in types) { #> 50 | <#= summary(type, true) #> 51 | public static bool Contains(this <#= type #>[] source, <#= type #> value) 52 | { 53 | ArgumentNullException.ThrowIfNull(source); 54 | return ContainsCore(new ReadOnlySpan<<#= type #>>(source), value); 55 | } 56 | 57 | <#= summary(type, true) #> 58 | public static bool Contains(this List<<#= type #>> source, <#= type #> value) 59 | { 60 | ArgumentNullException.ThrowIfNull(source); 61 | return ContainsCore((ReadOnlySpan<<#= type #>>)CollectionsMarshal.AsSpan(source), value); 62 | } 63 | 64 | <#= summary(type, true) #> 65 | public static bool Contains(this Memory<<#= type #>> source, <#= type #> value) 66 | { 67 | return ContainsCore((ReadOnlySpan<<#= type #>>)source.Span, value); 68 | } 69 | 70 | <#= summary(type, true) #> 71 | public static bool Contains(this ReadOnlyMemory<<#= type #>> source, <#= type #> value) 72 | { 73 | return ContainsCore(source.Span, value); 74 | } 75 | 76 | <#= summary(type, true) #> 77 | public static bool Contains(this Span<<#= type #>> source, <#= type #> value) 78 | { 79 | return ContainsCore((ReadOnlySpan<<#= type #>>)source, value); 80 | } 81 | 82 | <#= summary(type, true) #> 83 | public static bool Contains(this ReadOnlySpan<<#= type #>> source, <#= type #> value) 84 | { 85 | return ContainsCore(source, value); 86 | } 87 | <# } #> 88 | } 89 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.ContainsCore.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System.Runtime.Intrinsics; 5 | 6 | namespace SimdLinq; 7 | 8 | public static partial class SimdLinqExtensions 9 | { 10 | static bool ContainsCore(ReadOnlySpan source, T value) 11 | where T : struct, INumber 12 | { 13 | T sum = T.Zero; 14 | 15 | if (!Vector128.IsHardwareAccelerated || source.Length < Vector128.Count) 16 | { 17 | // Not SIMD supported or small source. 18 | for (int i = 0; i < source.Length; i++) 19 | { 20 | if (source[i] == value) 21 | { 22 | return true; 23 | } 24 | } 25 | } 26 | else if (!Vector256.IsHardwareAccelerated || source.Length < Vector256.Count) 27 | { 28 | // Only 128bit SIMD supported or small source. 29 | ref var begin = ref MemoryMarshal.GetReference(source); 30 | ref var last = ref Unsafe.Add(ref begin, source.Length); 31 | ref var current = ref begin; 32 | 33 | var vectorValue = Vector128.Create(value); 34 | ref var to = ref Unsafe.Add(ref begin, source.Length - Vector128.Count); 35 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 36 | { 37 | if (Vector128.EqualsAny(Vector128.LoadUnsafe(ref current), vectorValue)) 38 | { 39 | return true; 40 | } 41 | current = ref Unsafe.Add(ref current, Vector128.Count); 42 | } 43 | while (Unsafe.IsAddressLessThan(ref current, ref last)) 44 | { 45 | if (current == value) 46 | { 47 | return true; 48 | } 49 | current = ref Unsafe.Add(ref current, 1); 50 | } 51 | } 52 | else 53 | { 54 | // 256bit SIMD supported 55 | ref var begin = ref MemoryMarshal.GetReference(source); 56 | ref var last = ref Unsafe.Add(ref begin, source.Length); 57 | ref var current = ref begin; 58 | 59 | var vectorValue = Vector256.Create(value); 60 | ref var to = ref Unsafe.Add(ref begin, source.Length - Vector256.Count); 61 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 62 | { 63 | if (Vector256.EqualsAny(Vector256.LoadUnsafe(ref current), vectorValue)) 64 | { 65 | return true; 66 | } 67 | current = ref Unsafe.Add(ref current, Vector256.Count); 68 | } 69 | while (Unsafe.IsAddressLessThan(ref current, ref last)) 70 | { 71 | if (current == value) 72 | { 73 | return true; 74 | } 75 | current = ref Unsafe.Add(ref current, 1); 76 | } 77 | } 78 | 79 | return false; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.LongSum.cs: -------------------------------------------------------------------------------- 1 | namespace SimdLinq; 2 | 3 | using System.Runtime.InteropServices; 4 | 5 | public static partial class SimdLinqExtensions 6 | { 7 | /// 8 | /// Computes the long sum of a sequence of values using SIMD acceleration. 9 | /// 10 | /// 11 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 12 | /// 13 | /// A sequence of values to calculate the long sum of. 14 | /// The sum of the values in the sequence. 15 | /// is . 16 | public static long LongSum(this int[] source) 17 | { 18 | ArgumentNullException.ThrowIfNull(source); 19 | return LongSumCore(new ReadOnlySpan(source)); 20 | } 21 | 22 | /// 23 | /// Computes the long sum of a sequence of values using SIMD acceleration. 24 | /// 25 | /// 26 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 27 | /// 28 | /// A sequence of values to calculate the long sum of. 29 | /// The sum of the values in the sequence. 30 | /// is . 31 | public static long LongSum(this List source) 32 | { 33 | ArgumentNullException.ThrowIfNull(source); 34 | return LongSumCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 35 | } 36 | 37 | /// 38 | /// Computes the long sum of a sequence of values using SIMD acceleration. 39 | /// 40 | /// 41 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 42 | /// 43 | /// A sequence of values to calculate the long sum of. 44 | /// The sum of the values in the sequence. 45 | public static long LongSum(this Memory source) 46 | { 47 | return LongSumCore((ReadOnlySpan)source.Span); 48 | } 49 | 50 | /// 51 | /// Computes the long sum of a sequence of values using SIMD acceleration. 52 | /// 53 | /// 54 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 55 | /// 56 | /// A sequence of values to calculate the long sum of. 57 | /// The sum of the values in the sequence. 58 | public static long LongSum(this ReadOnlyMemory source) 59 | { 60 | return LongSumCore(source.Span); 61 | } 62 | 63 | /// 64 | /// Computes the long sum of a sequence of values using SIMD acceleration. 65 | /// 66 | /// 67 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 68 | /// 69 | /// A sequence of values to calculate the long sum of. 70 | /// The sum of the values in the sequence. 71 | public static long LongSum(this Span source) 72 | { 73 | return LongSumCore((ReadOnlySpan)source); 74 | } 75 | 76 | /// 77 | /// Computes the long sum of a sequence of values using SIMD acceleration. 78 | /// 79 | /// 80 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 81 | /// 82 | /// A sequence of values to calculate the long sum of. 83 | /// The sum of the values in the sequence. 84 | public static long LongSum(this ReadOnlySpan source) 85 | { 86 | return LongSumCore(source); 87 | } 88 | 89 | /// 90 | /// Computes the long sum of a sequence of values using SIMD acceleration. 91 | /// 92 | /// 93 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 94 | /// 95 | /// A sequence of values to calculate the long sum of. 96 | /// The sum of the values in the sequence. 97 | /// is . 98 | public static ulong LongSum(this uint[] source) 99 | { 100 | ArgumentNullException.ThrowIfNull(source); 101 | return LongSumCore(new ReadOnlySpan(source)); 102 | } 103 | 104 | /// 105 | /// Computes the long sum of a sequence of values using SIMD acceleration. 106 | /// 107 | /// 108 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 109 | /// 110 | /// A sequence of values to calculate the long sum of. 111 | /// The sum of the values in the sequence. 112 | /// is . 113 | public static ulong LongSum(this List source) 114 | { 115 | ArgumentNullException.ThrowIfNull(source); 116 | return LongSumCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 117 | } 118 | 119 | /// 120 | /// Computes the long sum of a sequence of values using SIMD acceleration. 121 | /// 122 | /// 123 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 124 | /// 125 | /// A sequence of values to calculate the long sum of. 126 | /// The sum of the values in the sequence. 127 | public static ulong LongSum(this Memory source) 128 | { 129 | return LongSumCore((ReadOnlySpan)source.Span); 130 | } 131 | 132 | /// 133 | /// Computes the long sum of a sequence of values using SIMD acceleration. 134 | /// 135 | /// 136 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 137 | /// 138 | /// A sequence of values to calculate the long sum of. 139 | /// The sum of the values in the sequence. 140 | public static ulong LongSum(this ReadOnlyMemory source) 141 | { 142 | return LongSumCore(source.Span); 143 | } 144 | 145 | /// 146 | /// Computes the long sum of a sequence of values using SIMD acceleration. 147 | /// 148 | /// 149 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 150 | /// 151 | /// A sequence of values to calculate the long sum of. 152 | /// The sum of the values in the sequence. 153 | public static ulong LongSum(this Span source) 154 | { 155 | return LongSumCore((ReadOnlySpan)source); 156 | } 157 | 158 | /// 159 | /// Computes the long sum of a sequence of values using SIMD acceleration. 160 | /// 161 | /// 162 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 163 | /// 164 | /// A sequence of values to calculate the long sum of. 165 | /// The sum of the values in the sequence. 166 | public static ulong LongSum(this ReadOnlySpan source) 167 | { 168 | return LongSumCore(source); 169 | } 170 | 171 | } 172 | 173 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.LongSum.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <# 8 | string summary(string x, bool nullable) 9 | { 10 | var exception = ""; 11 | if (nullable) 12 | { 13 | exception += """ 14 | 15 | /// is . 16 | """; 17 | } 18 | 19 | return $""" 20 | /// 21 | /// Computes the long sum of a sequence of values using SIMD acceleration. 22 | /// 23 | /// 24 | /// Note that unlike Linq.Sum this method is unchecked and will not throw an . 25 | /// 26 | /// A sequence of values to calculate the long sum of. 27 | /// The sum of the values in the sequence.{exception} 28 | """; 29 | } 30 | 31 | var retType = (string x) => x is "int" ? "long" : "ulong";; 32 | 33 | var methodName = "LongSum"; 34 | var types = new [] 35 | { 36 | "int", "uint" 37 | }; 38 | #> 39 | namespace SimdLinq; 40 | 41 | using System.Runtime.InteropServices; 42 | 43 | <#@ include file=".\SimdLinqExtensions.ttinclude" #> 44 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.Max.cs: -------------------------------------------------------------------------------- 1 | namespace SimdLinq; 2 | 3 | using System.Runtime.InteropServices; 4 | 5 | public static partial class SimdLinqExtensions 6 | { 7 | /// 8 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 9 | /// 10 | /// A sequence of values to determine the maximum value of. 11 | /// The maximum value in the sequence. 12 | /// is . 13 | /// contains no elements. 14 | public static byte Max(this byte[] source) 15 | { 16 | ArgumentNullException.ThrowIfNull(source); 17 | return MaxCore(new ReadOnlySpan(source)); 18 | } 19 | 20 | /// 21 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 22 | /// 23 | /// A sequence of values to determine the maximum value of. 24 | /// The maximum value in the sequence. 25 | /// is . 26 | /// contains no elements. 27 | public static byte Max(this List source) 28 | { 29 | ArgumentNullException.ThrowIfNull(source); 30 | return MaxCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 31 | } 32 | 33 | /// 34 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 35 | /// 36 | /// A sequence of values to determine the maximum value of. 37 | /// The maximum value in the sequence. 38 | /// contains no elements. 39 | public static byte Max(this Memory source) 40 | { 41 | return MaxCore((ReadOnlySpan)source.Span); 42 | } 43 | 44 | /// 45 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 46 | /// 47 | /// A sequence of values to determine the maximum value of. 48 | /// The maximum value in the sequence. 49 | /// contains no elements. 50 | public static byte Max(this ReadOnlyMemory source) 51 | { 52 | return MaxCore(source.Span); 53 | } 54 | 55 | /// 56 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 57 | /// 58 | /// A sequence of values to determine the maximum value of. 59 | /// The maximum value in the sequence. 60 | /// contains no elements. 61 | public static byte Max(this Span source) 62 | { 63 | return MaxCore((ReadOnlySpan)source); 64 | } 65 | 66 | /// 67 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 68 | /// 69 | /// A sequence of values to determine the maximum value of. 70 | /// The maximum value in the sequence. 71 | /// contains no elements. 72 | public static byte Max(this ReadOnlySpan source) 73 | { 74 | return MaxCore(source); 75 | } 76 | 77 | /// 78 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 79 | /// 80 | /// A sequence of values to determine the maximum value of. 81 | /// The maximum value in the sequence. 82 | /// is . 83 | /// contains no elements. 84 | public static sbyte Max(this sbyte[] source) 85 | { 86 | ArgumentNullException.ThrowIfNull(source); 87 | return MaxCore(new ReadOnlySpan(source)); 88 | } 89 | 90 | /// 91 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 92 | /// 93 | /// A sequence of values to determine the maximum value of. 94 | /// The maximum value in the sequence. 95 | /// is . 96 | /// contains no elements. 97 | public static sbyte Max(this List source) 98 | { 99 | ArgumentNullException.ThrowIfNull(source); 100 | return MaxCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 101 | } 102 | 103 | /// 104 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 105 | /// 106 | /// A sequence of values to determine the maximum value of. 107 | /// The maximum value in the sequence. 108 | /// contains no elements. 109 | public static sbyte Max(this Memory source) 110 | { 111 | return MaxCore((ReadOnlySpan)source.Span); 112 | } 113 | 114 | /// 115 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 116 | /// 117 | /// A sequence of values to determine the maximum value of. 118 | /// The maximum value in the sequence. 119 | /// contains no elements. 120 | public static sbyte Max(this ReadOnlyMemory source) 121 | { 122 | return MaxCore(source.Span); 123 | } 124 | 125 | /// 126 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 127 | /// 128 | /// A sequence of values to determine the maximum value of. 129 | /// The maximum value in the sequence. 130 | /// contains no elements. 131 | public static sbyte Max(this Span source) 132 | { 133 | return MaxCore((ReadOnlySpan)source); 134 | } 135 | 136 | /// 137 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 138 | /// 139 | /// A sequence of values to determine the maximum value of. 140 | /// The maximum value in the sequence. 141 | /// contains no elements. 142 | public static sbyte Max(this ReadOnlySpan source) 143 | { 144 | return MaxCore(source); 145 | } 146 | 147 | /// 148 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 149 | /// 150 | /// A sequence of values to determine the maximum value of. 151 | /// The maximum value in the sequence. 152 | /// is . 153 | /// contains no elements. 154 | public static short Max(this short[] source) 155 | { 156 | ArgumentNullException.ThrowIfNull(source); 157 | return MaxCore(new ReadOnlySpan(source)); 158 | } 159 | 160 | /// 161 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 162 | /// 163 | /// A sequence of values to determine the maximum value of. 164 | /// The maximum value in the sequence. 165 | /// is . 166 | /// contains no elements. 167 | public static short Max(this List source) 168 | { 169 | ArgumentNullException.ThrowIfNull(source); 170 | return MaxCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 171 | } 172 | 173 | /// 174 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 175 | /// 176 | /// A sequence of values to determine the maximum value of. 177 | /// The maximum value in the sequence. 178 | /// contains no elements. 179 | public static short Max(this Memory source) 180 | { 181 | return MaxCore((ReadOnlySpan)source.Span); 182 | } 183 | 184 | /// 185 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 186 | /// 187 | /// A sequence of values to determine the maximum value of. 188 | /// The maximum value in the sequence. 189 | /// contains no elements. 190 | public static short Max(this ReadOnlyMemory source) 191 | { 192 | return MaxCore(source.Span); 193 | } 194 | 195 | /// 196 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 197 | /// 198 | /// A sequence of values to determine the maximum value of. 199 | /// The maximum value in the sequence. 200 | /// contains no elements. 201 | public static short Max(this Span source) 202 | { 203 | return MaxCore((ReadOnlySpan)source); 204 | } 205 | 206 | /// 207 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 208 | /// 209 | /// A sequence of values to determine the maximum value of. 210 | /// The maximum value in the sequence. 211 | /// contains no elements. 212 | public static short Max(this ReadOnlySpan source) 213 | { 214 | return MaxCore(source); 215 | } 216 | 217 | /// 218 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 219 | /// 220 | /// A sequence of values to determine the maximum value of. 221 | /// The maximum value in the sequence. 222 | /// is . 223 | /// contains no elements. 224 | public static ushort Max(this ushort[] source) 225 | { 226 | ArgumentNullException.ThrowIfNull(source); 227 | return MaxCore(new ReadOnlySpan(source)); 228 | } 229 | 230 | /// 231 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 232 | /// 233 | /// A sequence of values to determine the maximum value of. 234 | /// The maximum value in the sequence. 235 | /// is . 236 | /// contains no elements. 237 | public static ushort Max(this List source) 238 | { 239 | ArgumentNullException.ThrowIfNull(source); 240 | return MaxCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 241 | } 242 | 243 | /// 244 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 245 | /// 246 | /// A sequence of values to determine the maximum value of. 247 | /// The maximum value in the sequence. 248 | /// contains no elements. 249 | public static ushort Max(this Memory source) 250 | { 251 | return MaxCore((ReadOnlySpan)source.Span); 252 | } 253 | 254 | /// 255 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 256 | /// 257 | /// A sequence of values to determine the maximum value of. 258 | /// The maximum value in the sequence. 259 | /// contains no elements. 260 | public static ushort Max(this ReadOnlyMemory source) 261 | { 262 | return MaxCore(source.Span); 263 | } 264 | 265 | /// 266 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 267 | /// 268 | /// A sequence of values to determine the maximum value of. 269 | /// The maximum value in the sequence. 270 | /// contains no elements. 271 | public static ushort Max(this Span source) 272 | { 273 | return MaxCore((ReadOnlySpan)source); 274 | } 275 | 276 | /// 277 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 278 | /// 279 | /// A sequence of values to determine the maximum value of. 280 | /// The maximum value in the sequence. 281 | /// contains no elements. 282 | public static ushort Max(this ReadOnlySpan source) 283 | { 284 | return MaxCore(source); 285 | } 286 | 287 | /// 288 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 289 | /// 290 | /// A sequence of values to determine the maximum value of. 291 | /// The maximum value in the sequence. 292 | /// is . 293 | /// contains no elements. 294 | public static int Max(this int[] source) 295 | { 296 | ArgumentNullException.ThrowIfNull(source); 297 | return MaxCore(new ReadOnlySpan(source)); 298 | } 299 | 300 | /// 301 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 302 | /// 303 | /// A sequence of values to determine the maximum value of. 304 | /// The maximum value in the sequence. 305 | /// is . 306 | /// contains no elements. 307 | public static int Max(this List source) 308 | { 309 | ArgumentNullException.ThrowIfNull(source); 310 | return MaxCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 311 | } 312 | 313 | /// 314 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 315 | /// 316 | /// A sequence of values to determine the maximum value of. 317 | /// The maximum value in the sequence. 318 | /// contains no elements. 319 | public static int Max(this Memory source) 320 | { 321 | return MaxCore((ReadOnlySpan)source.Span); 322 | } 323 | 324 | /// 325 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 326 | /// 327 | /// A sequence of values to determine the maximum value of. 328 | /// The maximum value in the sequence. 329 | /// contains no elements. 330 | public static int Max(this ReadOnlyMemory source) 331 | { 332 | return MaxCore(source.Span); 333 | } 334 | 335 | /// 336 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 337 | /// 338 | /// A sequence of values to determine the maximum value of. 339 | /// The maximum value in the sequence. 340 | /// contains no elements. 341 | public static int Max(this Span source) 342 | { 343 | return MaxCore((ReadOnlySpan)source); 344 | } 345 | 346 | /// 347 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 348 | /// 349 | /// A sequence of values to determine the maximum value of. 350 | /// The maximum value in the sequence. 351 | /// contains no elements. 352 | public static int Max(this ReadOnlySpan source) 353 | { 354 | return MaxCore(source); 355 | } 356 | 357 | /// 358 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 359 | /// 360 | /// A sequence of values to determine the maximum value of. 361 | /// The maximum value in the sequence. 362 | /// is . 363 | /// contains no elements. 364 | public static uint Max(this uint[] source) 365 | { 366 | ArgumentNullException.ThrowIfNull(source); 367 | return MaxCore(new ReadOnlySpan(source)); 368 | } 369 | 370 | /// 371 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 372 | /// 373 | /// A sequence of values to determine the maximum value of. 374 | /// The maximum value in the sequence. 375 | /// is . 376 | /// contains no elements. 377 | public static uint Max(this List source) 378 | { 379 | ArgumentNullException.ThrowIfNull(source); 380 | return MaxCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 381 | } 382 | 383 | /// 384 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 385 | /// 386 | /// A sequence of values to determine the maximum value of. 387 | /// The maximum value in the sequence. 388 | /// contains no elements. 389 | public static uint Max(this Memory source) 390 | { 391 | return MaxCore((ReadOnlySpan)source.Span); 392 | } 393 | 394 | /// 395 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 396 | /// 397 | /// A sequence of values to determine the maximum value of. 398 | /// The maximum value in the sequence. 399 | /// contains no elements. 400 | public static uint Max(this ReadOnlyMemory source) 401 | { 402 | return MaxCore(source.Span); 403 | } 404 | 405 | /// 406 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 407 | /// 408 | /// A sequence of values to determine the maximum value of. 409 | /// The maximum value in the sequence. 410 | /// contains no elements. 411 | public static uint Max(this Span source) 412 | { 413 | return MaxCore((ReadOnlySpan)source); 414 | } 415 | 416 | /// 417 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 418 | /// 419 | /// A sequence of values to determine the maximum value of. 420 | /// The maximum value in the sequence. 421 | /// contains no elements. 422 | public static uint Max(this ReadOnlySpan source) 423 | { 424 | return MaxCore(source); 425 | } 426 | 427 | /// 428 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 429 | /// 430 | /// A sequence of values to determine the maximum value of. 431 | /// The maximum value in the sequence. 432 | /// is . 433 | /// contains no elements. 434 | public static long Max(this long[] source) 435 | { 436 | ArgumentNullException.ThrowIfNull(source); 437 | return MaxCore(new ReadOnlySpan(source)); 438 | } 439 | 440 | /// 441 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 442 | /// 443 | /// A sequence of values to determine the maximum value of. 444 | /// The maximum value in the sequence. 445 | /// is . 446 | /// contains no elements. 447 | public static long Max(this List source) 448 | { 449 | ArgumentNullException.ThrowIfNull(source); 450 | return MaxCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 451 | } 452 | 453 | /// 454 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 455 | /// 456 | /// A sequence of values to determine the maximum value of. 457 | /// The maximum value in the sequence. 458 | /// contains no elements. 459 | public static long Max(this Memory source) 460 | { 461 | return MaxCore((ReadOnlySpan)source.Span); 462 | } 463 | 464 | /// 465 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 466 | /// 467 | /// A sequence of values to determine the maximum value of. 468 | /// The maximum value in the sequence. 469 | /// contains no elements. 470 | public static long Max(this ReadOnlyMemory source) 471 | { 472 | return MaxCore(source.Span); 473 | } 474 | 475 | /// 476 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 477 | /// 478 | /// A sequence of values to determine the maximum value of. 479 | /// The maximum value in the sequence. 480 | /// contains no elements. 481 | public static long Max(this Span source) 482 | { 483 | return MaxCore((ReadOnlySpan)source); 484 | } 485 | 486 | /// 487 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 488 | /// 489 | /// A sequence of values to determine the maximum value of. 490 | /// The maximum value in the sequence. 491 | /// contains no elements. 492 | public static long Max(this ReadOnlySpan source) 493 | { 494 | return MaxCore(source); 495 | } 496 | 497 | /// 498 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 499 | /// 500 | /// A sequence of values to determine the maximum value of. 501 | /// The maximum value in the sequence. 502 | /// is . 503 | /// contains no elements. 504 | public static ulong Max(this ulong[] source) 505 | { 506 | ArgumentNullException.ThrowIfNull(source); 507 | return MaxCore(new ReadOnlySpan(source)); 508 | } 509 | 510 | /// 511 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 512 | /// 513 | /// A sequence of values to determine the maximum value of. 514 | /// The maximum value in the sequence. 515 | /// is . 516 | /// contains no elements. 517 | public static ulong Max(this List source) 518 | { 519 | ArgumentNullException.ThrowIfNull(source); 520 | return MaxCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 521 | } 522 | 523 | /// 524 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 525 | /// 526 | /// A sequence of values to determine the maximum value of. 527 | /// The maximum value in the sequence. 528 | /// contains no elements. 529 | public static ulong Max(this Memory source) 530 | { 531 | return MaxCore((ReadOnlySpan)source.Span); 532 | } 533 | 534 | /// 535 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 536 | /// 537 | /// A sequence of values to determine the maximum value of. 538 | /// The maximum value in the sequence. 539 | /// contains no elements. 540 | public static ulong Max(this ReadOnlyMemory source) 541 | { 542 | return MaxCore(source.Span); 543 | } 544 | 545 | /// 546 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 547 | /// 548 | /// A sequence of values to determine the maximum value of. 549 | /// The maximum value in the sequence. 550 | /// contains no elements. 551 | public static ulong Max(this Span source) 552 | { 553 | return MaxCore((ReadOnlySpan)source); 554 | } 555 | 556 | /// 557 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 558 | /// 559 | /// A sequence of values to determine the maximum value of. 560 | /// The maximum value in the sequence. 561 | /// contains no elements. 562 | public static ulong Max(this ReadOnlySpan source) 563 | { 564 | return MaxCore(source); 565 | } 566 | 567 | /// 568 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 569 | /// 570 | /// This method does not check for / values and should not be used when may contain one. 571 | /// A sequence of values to determine the maximum value of. 572 | /// The maximum value in the sequence. 573 | /// is . 574 | /// contains no elements. 575 | public static float Max(this float[] source) 576 | { 577 | ArgumentNullException.ThrowIfNull(source); 578 | return MaxCore(new ReadOnlySpan(source)); 579 | } 580 | 581 | /// 582 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 583 | /// 584 | /// This method does not check for / values and should not be used when may contain one. 585 | /// A sequence of values to determine the maximum value of. 586 | /// The maximum value in the sequence. 587 | /// is . 588 | /// contains no elements. 589 | public static float Max(this List source) 590 | { 591 | ArgumentNullException.ThrowIfNull(source); 592 | return MaxCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 593 | } 594 | 595 | /// 596 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 597 | /// 598 | /// This method does not check for / values and should not be used when may contain one. 599 | /// A sequence of values to determine the maximum value of. 600 | /// The maximum value in the sequence. 601 | /// contains no elements. 602 | public static float Max(this Memory source) 603 | { 604 | return MaxCore((ReadOnlySpan)source.Span); 605 | } 606 | 607 | /// 608 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 609 | /// 610 | /// This method does not check for / values and should not be used when may contain one. 611 | /// A sequence of values to determine the maximum value of. 612 | /// The maximum value in the sequence. 613 | /// contains no elements. 614 | public static float Max(this ReadOnlyMemory source) 615 | { 616 | return MaxCore(source.Span); 617 | } 618 | 619 | /// 620 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 621 | /// 622 | /// This method does not check for / values and should not be used when may contain one. 623 | /// A sequence of values to determine the maximum value of. 624 | /// The maximum value in the sequence. 625 | /// contains no elements. 626 | public static float Max(this Span source) 627 | { 628 | return MaxCore((ReadOnlySpan)source); 629 | } 630 | 631 | /// 632 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 633 | /// 634 | /// This method does not check for / values and should not be used when may contain one. 635 | /// A sequence of values to determine the maximum value of. 636 | /// The maximum value in the sequence. 637 | /// contains no elements. 638 | public static float Max(this ReadOnlySpan source) 639 | { 640 | return MaxCore(source); 641 | } 642 | 643 | /// 644 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 645 | /// 646 | /// This method does not check for / values and should not be used when may contain one. 647 | /// A sequence of values to determine the maximum value of. 648 | /// The maximum value in the sequence. 649 | /// is . 650 | /// contains no elements. 651 | public static double Max(this double[] source) 652 | { 653 | ArgumentNullException.ThrowIfNull(source); 654 | return MaxCore(new ReadOnlySpan(source)); 655 | } 656 | 657 | /// 658 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 659 | /// 660 | /// This method does not check for / values and should not be used when may contain one. 661 | /// A sequence of values to determine the maximum value of. 662 | /// The maximum value in the sequence. 663 | /// is . 664 | /// contains no elements. 665 | public static double Max(this List source) 666 | { 667 | ArgumentNullException.ThrowIfNull(source); 668 | return MaxCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 669 | } 670 | 671 | /// 672 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 673 | /// 674 | /// This method does not check for / values and should not be used when may contain one. 675 | /// A sequence of values to determine the maximum value of. 676 | /// The maximum value in the sequence. 677 | /// contains no elements. 678 | public static double Max(this Memory source) 679 | { 680 | return MaxCore((ReadOnlySpan)source.Span); 681 | } 682 | 683 | /// 684 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 685 | /// 686 | /// This method does not check for / values and should not be used when may contain one. 687 | /// A sequence of values to determine the maximum value of. 688 | /// The maximum value in the sequence. 689 | /// contains no elements. 690 | public static double Max(this ReadOnlyMemory source) 691 | { 692 | return MaxCore(source.Span); 693 | } 694 | 695 | /// 696 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 697 | /// 698 | /// This method does not check for / values and should not be used when may contain one. 699 | /// A sequence of values to determine the maximum value of. 700 | /// The maximum value in the sequence. 701 | /// contains no elements. 702 | public static double Max(this Span source) 703 | { 704 | return MaxCore((ReadOnlySpan)source); 705 | } 706 | 707 | /// 708 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 709 | /// 710 | /// This method does not check for / values and should not be used when may contain one. 711 | /// A sequence of values to determine the maximum value of. 712 | /// The maximum value in the sequence. 713 | /// contains no elements. 714 | public static double Max(this ReadOnlySpan source) 715 | { 716 | return MaxCore(source); 717 | } 718 | 719 | } 720 | 721 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.Max.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <# 8 | string summary(string x, bool nullable) 9 | { 10 | var remark = ""; 11 | if(x is "double" or "float") 12 | { 13 | remark += """ 14 | 15 | /// This method does not check for / values and should not be used when may contain one. 16 | """; 17 | } 18 | var exception = ""; 19 | if (nullable) 20 | { 21 | exception += """ 22 | 23 | /// is . 24 | """; 25 | } 26 | 27 | return $""" 28 | /// 29 | /// Returns the maximum value in a sequence of values using SIMD acceleration. 30 | /// {remark} 31 | /// A sequence of values to determine the maximum value of. 32 | /// The maximum value in the sequence.{exception} 33 | /// contains no elements. 34 | """; 35 | } 36 | 37 | var retType = (string x) => x; 38 | 39 | var methodName = "Max"; 40 | var types = new [] 41 | { 42 | "byte", "sbyte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double" 43 | }; 44 | #> 45 | namespace SimdLinq; 46 | 47 | using System.Runtime.InteropServices; 48 | 49 | <#@ include file=".\SimdLinqExtensions.ttinclude" #> 50 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.Min.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <# 8 | string summary(string x, bool nullable) 9 | { 10 | var remark = ""; 11 | if(x is "double" or "float") 12 | { 13 | remark += """ 14 | 15 | /// This method does not check for / values and should not be used when may contain one. 16 | """; 17 | } 18 | var exception = ""; 19 | if (nullable) 20 | { 21 | exception += """ 22 | 23 | /// is . 24 | """; 25 | } 26 | 27 | return $""" 28 | /// 29 | /// Returns the minimum value in a sequence of values using SIMD acceleration. 30 | /// {remark} 31 | /// A sequence of values to determine the minimum value of. 32 | /// The minimum value in the sequence.{exception} 33 | /// contains no elements. 34 | """; 35 | } 36 | 37 | var retType = (string x) => x; 38 | 39 | var methodName = "Min"; 40 | var types = new [] 41 | { 42 | "byte", "sbyte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double" 43 | }; 44 | #> 45 | namespace SimdLinq; 46 | 47 | using System.Runtime.InteropServices; 48 | 49 | <#@ include file=".\SimdLinqExtensions.ttinclude" #> 50 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.MinMax.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <# 8 | string summary(string x, bool nullable) 9 | { 10 | var remark = ""; 11 | if(x is "double" or "float") 12 | { 13 | remark += """ 14 | 15 | /// This method does not check for / values and should not be used when may contain one. 16 | """; 17 | } 18 | var exception = ""; 19 | if (nullable) 20 | { 21 | exception += """ 22 | 23 | /// is . 24 | """; 25 | } 26 | 27 | return $""" 28 | /// 29 | /// Returns the minimum and maximum values in a sequence of values using SIMD acceleration. 30 | /// {remark} 31 | /// A sequence of values to determine the minimum and maximum value of. 32 | /// A tuple of the minimum and maximum values in the sequence.{exception} 33 | /// contains no elements. 34 | """; 35 | } 36 | 37 | var retType = (string x) => $"({x} Min, {x} Max)"; 38 | 39 | var methodName = "MinMax"; 40 | var types = new [] 41 | { 42 | "byte", "sbyte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double" 43 | }; 44 | #> 45 | namespace SimdLinq; 46 | 47 | using System.Runtime.InteropServices; 48 | 49 | <#@ include file=".\SimdLinqExtensions.ttinclude" #> 50 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.MinMaxCore.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Numerics; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Runtime.Intrinsics; 6 | using System.Runtime.Intrinsics.X86; 7 | 8 | namespace SimdLinq; 9 | 10 | public static partial class SimdLinqExtensions 11 | { 12 | static T MinCore(ReadOnlySpan source) 13 | where T : struct, INumber 14 | { 15 | if (source.IsEmpty) ThrowNoElements(); 16 | 17 | if (!Vector128.IsHardwareAccelerated || source.Length < Vector128.Count) 18 | { 19 | // Not SIMD supported or small source. 20 | var min = source[0]; 21 | for (int i = 1; i < source.Length; i++) 22 | { 23 | if (source[i] < min) 24 | { 25 | min = source[i]; 26 | } 27 | } 28 | return min; 29 | } 30 | else if (!Vector256.IsHardwareAccelerated || source.Length < Vector256.Count) 31 | { 32 | // Only 128bit SIMD supported or small source. 33 | ref var current = ref MemoryMarshal.GetReference(source); 34 | ref var to = ref Unsafe.Add(ref current, source.Length - Vector128.Count); 35 | 36 | var vectorMin = Vector128.LoadUnsafe(ref current); 37 | current = ref Unsafe.Add(ref current, Vector128.Count); 38 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 39 | { 40 | vectorMin = Vector128.Min(vectorMin, Vector128.LoadUnsafe(ref current)); 41 | current = ref Unsafe.Add(ref current, Vector128.Count); 42 | } 43 | vectorMin = Vector128.Min(vectorMin, Vector128.LoadUnsafe(ref to)); 44 | 45 | var min = vectorMin[0]; 46 | for (int i = 1; i < Vector128.Count; i++) 47 | { 48 | if (vectorMin[i] < min) 49 | { 50 | min = vectorMin[i]; 51 | } 52 | } 53 | return min; 54 | } 55 | else 56 | { 57 | // 256bit SIMD supported 58 | ref var current = ref MemoryMarshal.GetReference(source); 59 | ref var to = ref Unsafe.Add(ref current, source.Length - Vector256.Count); 60 | 61 | var vectorMin = Vector256.LoadUnsafe(ref current); 62 | current = ref Unsafe.Add(ref current, Vector256.Count); 63 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 64 | { 65 | vectorMin = Vector256.Min(vectorMin, Vector256.LoadUnsafe(ref current)); 66 | current = ref Unsafe.Add(ref current, Vector256.Count); 67 | } 68 | vectorMin = Vector256.Min(vectorMin, Vector256.LoadUnsafe(ref to)); 69 | 70 | var min = vectorMin[0]; 71 | for (int i = 1; i < Vector256.Count; i++) 72 | { 73 | if (vectorMin[i] < min) 74 | { 75 | min = vectorMin[i]; 76 | } 77 | } 78 | return min; 79 | } 80 | } 81 | 82 | static T MaxCore(ReadOnlySpan source) 83 | where T : struct, INumber 84 | { 85 | if (source.IsEmpty) ThrowNoElements(); 86 | 87 | if (!Vector128.IsHardwareAccelerated || source.Length < Vector128.Count) 88 | { 89 | // Not SIMD supported or small source. 90 | var max = source[0]; 91 | for (int i = 1; i < source.Length; i++) 92 | { 93 | if (source[i] > max) 94 | { 95 | max = source[i]; 96 | } 97 | } 98 | return max; 99 | } 100 | else if (!Vector256.IsHardwareAccelerated || source.Length < Vector256.Count) 101 | { 102 | // Only 128bit SIMD supported or small source. 103 | ref var current = ref MemoryMarshal.GetReference(source); 104 | ref var to = ref Unsafe.Add(ref current, source.Length - Vector128.Count); 105 | 106 | var vectorMax = Vector128.LoadUnsafe(ref current); 107 | current = ref Unsafe.Add(ref current, Vector128.Count); 108 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 109 | { 110 | vectorMax = Vector128.Max(vectorMax, Vector128.LoadUnsafe(ref current)); 111 | current = ref Unsafe.Add(ref current, Vector128.Count); 112 | } 113 | vectorMax = Vector128.Max(vectorMax, Vector128.LoadUnsafe(ref to)); 114 | 115 | var max = vectorMax[0]; 116 | for (int i = 1; i < Vector128.Count; i++) 117 | { 118 | if (vectorMax[i] > max) 119 | { 120 | max = vectorMax[i]; 121 | } 122 | } 123 | return max; 124 | } 125 | else 126 | { 127 | // 256bit SIMD supported 128 | ref var current = ref MemoryMarshal.GetReference(source); 129 | ref var to = ref Unsafe.Add(ref current, source.Length - Vector256.Count); 130 | 131 | var vectorMax = Vector256.LoadUnsafe(ref current); 132 | current = ref Unsafe.Add(ref current, Vector256.Count); 133 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 134 | { 135 | vectorMax = Vector256.Max(vectorMax, Vector256.LoadUnsafe(ref current)); 136 | current = ref Unsafe.Add(ref current, Vector256.Count); 137 | } 138 | vectorMax = Vector256.Max(vectorMax, Vector256.LoadUnsafe(ref to)); 139 | 140 | var max = vectorMax[0]; 141 | for (int i = 1; i < Vector256.Count; i++) 142 | { 143 | if (vectorMax[i] > max) 144 | { 145 | max = vectorMax[i]; 146 | } 147 | } 148 | return max; 149 | } 150 | } 151 | 152 | static (T Min, T Max) MinMaxCore(ReadOnlySpan source) 153 | where T : struct, INumber 154 | { 155 | if (source.IsEmpty) ThrowNoElements(); 156 | 157 | if (!Vector128.IsHardwareAccelerated || source.Length < Vector128.Count) 158 | { 159 | // Not SIMD supported or small source. 160 | var min = source[0]; 161 | var max = min; 162 | for (int i = 1; i < source.Length; i++) 163 | { 164 | if (source[i] < min) 165 | { 166 | min = source[i]; 167 | } 168 | if (source[i] > max) 169 | { 170 | max = source[i]; 171 | } 172 | } 173 | return (min, max); 174 | } 175 | else if (!Vector256.IsHardwareAccelerated || source.Length < Vector256.Count) 176 | { 177 | // Only 128bit SIMD supported or small source. 178 | ref var current = ref MemoryMarshal.GetReference(source); 179 | ref var to = ref Unsafe.Add(ref current, source.Length - Vector128.Count); 180 | 181 | var vectorMin = Vector128.LoadUnsafe(ref current); 182 | var vectorMax = vectorMin; 183 | current = ref Unsafe.Add(ref current, Vector128.Count); 184 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 185 | { 186 | vectorMin = Vector128.Min(vectorMin, Vector128.LoadUnsafe(ref current)); 187 | vectorMax = Vector128.Max(vectorMax, Vector128.LoadUnsafe(ref current)); 188 | current = ref Unsafe.Add(ref current, Vector128.Count); 189 | } 190 | vectorMin = Vector128.Min(vectorMin, Vector128.LoadUnsafe(ref to)); 191 | vectorMax = Vector128.Max(vectorMax, Vector128.LoadUnsafe(ref to)); 192 | 193 | var min = vectorMin[0]; 194 | var max = vectorMax[0]; 195 | for (int i = 1; i < Vector128.Count; i++) 196 | { 197 | if (vectorMin[i] < min) 198 | { 199 | min = vectorMin[i]; 200 | } 201 | if (vectorMax[i] > max) 202 | { 203 | max = vectorMax[i]; 204 | } 205 | } 206 | return (min, max); 207 | } 208 | else 209 | { 210 | // 256bit SIMD supported 211 | ref var current = ref MemoryMarshal.GetReference(source); 212 | ref var to = ref Unsafe.Add(ref current, source.Length - Vector256.Count); 213 | 214 | var vectorMin = Vector256.LoadUnsafe(ref current); 215 | var vectorMax = vectorMin; 216 | current = ref Unsafe.Add(ref current, Vector256.Count); 217 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 218 | { 219 | vectorMin = Vector256.Min(vectorMin, Vector256.LoadUnsafe(ref current)); 220 | vectorMax = Vector256.Max(vectorMax, Vector256.LoadUnsafe(ref current)); 221 | current = ref Unsafe.Add(ref current, Vector256.Count); 222 | } 223 | vectorMin = Vector256.Min(vectorMin, Vector256.LoadUnsafe(ref to)); 224 | vectorMax = Vector256.Max(vectorMax, Vector256.LoadUnsafe(ref to)); 225 | 226 | var min = vectorMin[0]; 227 | var max = vectorMax[0]; 228 | for (int i = 1; i < Vector256.Count; i++) 229 | { 230 | if (vectorMin[i] < min) 231 | { 232 | min = vectorMin[i]; 233 | } 234 | if (vectorMax[i] > max) 235 | { 236 | max = vectorMax[i]; 237 | } 238 | } 239 | return (min, max); 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.SequenceEqual.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <# 8 | string summary(string x, bool firstNull, bool secondNull) 9 | { 10 | var exception = ""; 11 | if (firstNull) 12 | { 13 | exception += """ 14 | 15 | /// is . 16 | """; 17 | } 18 | if (secondNull) 19 | { 20 | exception += """ 21 | 22 | /// is . 23 | """; 24 | } 25 | 26 | return $$""" 27 | /// 28 | /// Determines whether two sequences are equal by comparing the elements using the default . 29 | /// 30 | /// The first sequence to compare. 31 | /// The second sequence to compare. 32 | /// if the two sequences are equal; otherwise, .{{exception}} 33 | """; 34 | } 35 | var types = new [] 36 | { 37 | "byte", "sbyte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double" 38 | }; 39 | #> 40 | namespace SimdLinq; 41 | 42 | using System.Runtime.InteropServices; 43 | 44 | // SequenceEqual(this ReadOnlySpan span, ReadOnlySpan other) and if T is bitwise equatable, using SIMD. 45 | 46 | public static partial class SimdLinqExtensions 47 | { 48 | <# foreach(var type in types) { #> 49 | <#= summary(type, true, true) #> 50 | public static bool SequenceEqual(<#= type #>[] first, <#= type #>[] second) 51 | { 52 | ArgumentNullException.ThrowIfNull(first); 53 | ArgumentNullException.ThrowIfNull(second); 54 | return MemoryExtensions.SequenceEqual(first.AsSpan(), second.AsSpan()); 55 | } 56 | 57 | <#= summary(type, true, false) #> 58 | public static bool SequenceEqual(<#= type #>[] first, ReadOnlySpan<<#= type #>> second) 59 | { 60 | ArgumentNullException.ThrowIfNull(first); 61 | return MemoryExtensions.SequenceEqual(first.AsSpan(), second); 62 | } 63 | 64 | <#= summary(type, true, true) #> 65 | public static bool SequenceEqual(List<<#= type #>> first, List<<#= type #>> second) 66 | { 67 | ArgumentNullException.ThrowIfNull(first); 68 | ArgumentNullException.ThrowIfNull(second); 69 | return MemoryExtensions.SequenceEqual((ReadOnlySpan<<#= type #>>)CollectionsMarshal.AsSpan(first), (ReadOnlySpan<<#= type #>>)CollectionsMarshal.AsSpan(second)); 70 | } 71 | 72 | <#= summary(type, true, false) #> 73 | public static bool SequenceEqual(List<<#= type #>> first, ReadOnlySpan<<#= type #>> second) 74 | { 75 | ArgumentNullException.ThrowIfNull(first); 76 | return MemoryExtensions.SequenceEqual((ReadOnlySpan<<#= type #>>)CollectionsMarshal.AsSpan(first), second); 77 | } 78 | 79 | <#= summary(type, false, false) #> 80 | public static bool SequenceEqual(Memory<<#= type #>> first, Memory<<#= type #>> second) 81 | { 82 | return MemoryExtensions.SequenceEqual((ReadOnlySpan<<#= type #>>)first.Span, (ReadOnlySpan<<#= type #>>)second.Span); 83 | } 84 | 85 | <#= summary(type, false, false) #> 86 | public static bool SequenceEqual(Memory<<#= type #>> first, ReadOnlySpan<<#= type #>> second) 87 | { 88 | return MemoryExtensions.SequenceEqual((ReadOnlySpan<<#= type #>>)first.Span, second); 89 | } 90 | 91 | <#= summary(type, false, false) #> 92 | public static bool SequenceEqual(ReadOnlyMemory<<#= type #>> first, ReadOnlyMemory<<#= type #>> second) 93 | { 94 | return MemoryExtensions.SequenceEqual(first.Span, second.Span); 95 | } 96 | 97 | <#= summary(type, false, false) #> 98 | public static bool SequenceEqual(ReadOnlyMemory<<#= type #>> first, ReadOnlySpan<<#= type #>> second) 99 | { 100 | return MemoryExtensions.SequenceEqual(first.Span, second); 101 | } 102 | 103 | <#= summary(type, false, false) #> 104 | public static bool SequenceEqual(Span<<#= type #>> first, Span<<#= type #>> second) 105 | { 106 | return MemoryExtensions.SequenceEqual((ReadOnlySpan<<#= type #>>)first, (ReadOnlySpan<<#= type #>>)second); 107 | } 108 | 109 | <#= summary(type, false, false) #> 110 | public static bool SequenceEqual(Span<<#= type #>> first, ReadOnlySpan<<#= type #>> second) 111 | { 112 | return MemoryExtensions.SequenceEqual((ReadOnlySpan<<#= type #>>)first, second); 113 | } 114 | 115 | <# } #> 116 | } 117 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.Sum.cs: -------------------------------------------------------------------------------- 1 | namespace SimdLinq; 2 | 3 | using System.Runtime.InteropServices; 4 | 5 | public static partial class SimdLinqExtensions 6 | { 7 | /// 8 | /// Computes the sum of a sequence of values using SIMD acceleration. 9 | /// 10 | /// 11 | /// Note that unlike this method is unchecked and will not throw an . 12 | /// 13 | /// A sequence of values to calculate the sum of. 14 | /// The sum of the values in the sequence. 15 | /// is . 16 | public static int Sum(this int[] source) 17 | { 18 | ArgumentNullException.ThrowIfNull(source); 19 | return SumCore(new ReadOnlySpan(source)); 20 | } 21 | 22 | /// 23 | /// Computes the sum of a sequence of values using SIMD acceleration. 24 | /// 25 | /// 26 | /// Note that unlike this method is unchecked and will not throw an . 27 | /// 28 | /// A sequence of values to calculate the sum of. 29 | /// The sum of the values in the sequence. 30 | /// is . 31 | public static int Sum(this List source) 32 | { 33 | ArgumentNullException.ThrowIfNull(source); 34 | return SumCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 35 | } 36 | 37 | /// 38 | /// Computes the sum of a sequence of values using SIMD acceleration. 39 | /// 40 | /// 41 | /// Note that unlike this method is unchecked and will not throw an . 42 | /// 43 | /// A sequence of values to calculate the sum of. 44 | /// The sum of the values in the sequence. 45 | public static int Sum(this Memory source) 46 | { 47 | return SumCore((ReadOnlySpan)source.Span); 48 | } 49 | 50 | /// 51 | /// Computes the sum of a sequence of values using SIMD acceleration. 52 | /// 53 | /// 54 | /// Note that unlike this method is unchecked and will not throw an . 55 | /// 56 | /// A sequence of values to calculate the sum of. 57 | /// The sum of the values in the sequence. 58 | public static int Sum(this ReadOnlyMemory source) 59 | { 60 | return SumCore(source.Span); 61 | } 62 | 63 | /// 64 | /// Computes the sum of a sequence of values using SIMD acceleration. 65 | /// 66 | /// 67 | /// Note that unlike this method is unchecked and will not throw an . 68 | /// 69 | /// A sequence of values to calculate the sum of. 70 | /// The sum of the values in the sequence. 71 | public static int Sum(this Span source) 72 | { 73 | return SumCore((ReadOnlySpan)source); 74 | } 75 | 76 | /// 77 | /// Computes the sum of a sequence of values using SIMD acceleration. 78 | /// 79 | /// 80 | /// Note that unlike this method is unchecked and will not throw an . 81 | /// 82 | /// A sequence of values to calculate the sum of. 83 | /// The sum of the values in the sequence. 84 | public static int Sum(this ReadOnlySpan source) 85 | { 86 | return SumCore(source); 87 | } 88 | 89 | /// 90 | /// Computes the sum of a sequence of values using SIMD acceleration. 91 | /// 92 | /// 93 | /// Note that unlike this method is unchecked and will not throw an . 94 | /// 95 | /// A sequence of values to calculate the sum of. 96 | /// The sum of the values in the sequence. 97 | /// is . 98 | public static uint Sum(this uint[] source) 99 | { 100 | ArgumentNullException.ThrowIfNull(source); 101 | return SumCore(new ReadOnlySpan(source)); 102 | } 103 | 104 | /// 105 | /// Computes the sum of a sequence of values using SIMD acceleration. 106 | /// 107 | /// 108 | /// Note that unlike this method is unchecked and will not throw an . 109 | /// 110 | /// A sequence of values to calculate the sum of. 111 | /// The sum of the values in the sequence. 112 | /// is . 113 | public static uint Sum(this List source) 114 | { 115 | ArgumentNullException.ThrowIfNull(source); 116 | return SumCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 117 | } 118 | 119 | /// 120 | /// Computes the sum of a sequence of values using SIMD acceleration. 121 | /// 122 | /// 123 | /// Note that unlike this method is unchecked and will not throw an . 124 | /// 125 | /// A sequence of values to calculate the sum of. 126 | /// The sum of the values in the sequence. 127 | public static uint Sum(this Memory source) 128 | { 129 | return SumCore((ReadOnlySpan)source.Span); 130 | } 131 | 132 | /// 133 | /// Computes the sum of a sequence of values using SIMD acceleration. 134 | /// 135 | /// 136 | /// Note that unlike this method is unchecked and will not throw an . 137 | /// 138 | /// A sequence of values to calculate the sum of. 139 | /// The sum of the values in the sequence. 140 | public static uint Sum(this ReadOnlyMemory source) 141 | { 142 | return SumCore(source.Span); 143 | } 144 | 145 | /// 146 | /// Computes the sum of a sequence of values using SIMD acceleration. 147 | /// 148 | /// 149 | /// Note that unlike this method is unchecked and will not throw an . 150 | /// 151 | /// A sequence of values to calculate the sum of. 152 | /// The sum of the values in the sequence. 153 | public static uint Sum(this Span source) 154 | { 155 | return SumCore((ReadOnlySpan)source); 156 | } 157 | 158 | /// 159 | /// Computes the sum of a sequence of values using SIMD acceleration. 160 | /// 161 | /// 162 | /// Note that unlike this method is unchecked and will not throw an . 163 | /// 164 | /// A sequence of values to calculate the sum of. 165 | /// The sum of the values in the sequence. 166 | public static uint Sum(this ReadOnlySpan source) 167 | { 168 | return SumCore(source); 169 | } 170 | 171 | /// 172 | /// Computes the sum of a sequence of values using SIMD acceleration. 173 | /// 174 | /// 175 | /// Note that unlike this method is unchecked and will not throw an . 176 | /// 177 | /// A sequence of values to calculate the sum of. 178 | /// The sum of the values in the sequence. 179 | /// is . 180 | public static long Sum(this long[] source) 181 | { 182 | ArgumentNullException.ThrowIfNull(source); 183 | return SumCore(new ReadOnlySpan(source)); 184 | } 185 | 186 | /// 187 | /// Computes the sum of a sequence of values using SIMD acceleration. 188 | /// 189 | /// 190 | /// Note that unlike this method is unchecked and will not throw an . 191 | /// 192 | /// A sequence of values to calculate the sum of. 193 | /// The sum of the values in the sequence. 194 | /// is . 195 | public static long Sum(this List source) 196 | { 197 | ArgumentNullException.ThrowIfNull(source); 198 | return SumCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 199 | } 200 | 201 | /// 202 | /// Computes the sum of a sequence of values using SIMD acceleration. 203 | /// 204 | /// 205 | /// Note that unlike this method is unchecked and will not throw an . 206 | /// 207 | /// A sequence of values to calculate the sum of. 208 | /// The sum of the values in the sequence. 209 | public static long Sum(this Memory source) 210 | { 211 | return SumCore((ReadOnlySpan)source.Span); 212 | } 213 | 214 | /// 215 | /// Computes the sum of a sequence of values using SIMD acceleration. 216 | /// 217 | /// 218 | /// Note that unlike this method is unchecked and will not throw an . 219 | /// 220 | /// A sequence of values to calculate the sum of. 221 | /// The sum of the values in the sequence. 222 | public static long Sum(this ReadOnlyMemory source) 223 | { 224 | return SumCore(source.Span); 225 | } 226 | 227 | /// 228 | /// Computes the sum of a sequence of values using SIMD acceleration. 229 | /// 230 | /// 231 | /// Note that unlike this method is unchecked and will not throw an . 232 | /// 233 | /// A sequence of values to calculate the sum of. 234 | /// The sum of the values in the sequence. 235 | public static long Sum(this Span source) 236 | { 237 | return SumCore((ReadOnlySpan)source); 238 | } 239 | 240 | /// 241 | /// Computes the sum of a sequence of values using SIMD acceleration. 242 | /// 243 | /// 244 | /// Note that unlike this method is unchecked and will not throw an . 245 | /// 246 | /// A sequence of values to calculate the sum of. 247 | /// The sum of the values in the sequence. 248 | public static long Sum(this ReadOnlySpan source) 249 | { 250 | return SumCore(source); 251 | } 252 | 253 | /// 254 | /// Computes the sum of a sequence of values using SIMD acceleration. 255 | /// 256 | /// 257 | /// Note that unlike this method is unchecked and will not throw an . 258 | /// 259 | /// A sequence of values to calculate the sum of. 260 | /// The sum of the values in the sequence. 261 | /// is . 262 | public static ulong Sum(this ulong[] source) 263 | { 264 | ArgumentNullException.ThrowIfNull(source); 265 | return SumCore(new ReadOnlySpan(source)); 266 | } 267 | 268 | /// 269 | /// Computes the sum of a sequence of values using SIMD acceleration. 270 | /// 271 | /// 272 | /// Note that unlike this method is unchecked and will not throw an . 273 | /// 274 | /// A sequence of values to calculate the sum of. 275 | /// The sum of the values in the sequence. 276 | /// is . 277 | public static ulong Sum(this List source) 278 | { 279 | ArgumentNullException.ThrowIfNull(source); 280 | return SumCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 281 | } 282 | 283 | /// 284 | /// Computes the sum of a sequence of values using SIMD acceleration. 285 | /// 286 | /// 287 | /// Note that unlike this method is unchecked and will not throw an . 288 | /// 289 | /// A sequence of values to calculate the sum of. 290 | /// The sum of the values in the sequence. 291 | public static ulong Sum(this Memory source) 292 | { 293 | return SumCore((ReadOnlySpan)source.Span); 294 | } 295 | 296 | /// 297 | /// Computes the sum of a sequence of values using SIMD acceleration. 298 | /// 299 | /// 300 | /// Note that unlike this method is unchecked and will not throw an . 301 | /// 302 | /// A sequence of values to calculate the sum of. 303 | /// The sum of the values in the sequence. 304 | public static ulong Sum(this ReadOnlyMemory source) 305 | { 306 | return SumCore(source.Span); 307 | } 308 | 309 | /// 310 | /// Computes the sum of a sequence of values using SIMD acceleration. 311 | /// 312 | /// 313 | /// Note that unlike this method is unchecked and will not throw an . 314 | /// 315 | /// A sequence of values to calculate the sum of. 316 | /// The sum of the values in the sequence. 317 | public static ulong Sum(this Span source) 318 | { 319 | return SumCore((ReadOnlySpan)source); 320 | } 321 | 322 | /// 323 | /// Computes the sum of a sequence of values using SIMD acceleration. 324 | /// 325 | /// 326 | /// Note that unlike this method is unchecked and will not throw an . 327 | /// 328 | /// A sequence of values to calculate the sum of. 329 | /// The sum of the values in the sequence. 330 | public static ulong Sum(this ReadOnlySpan source) 331 | { 332 | return SumCore(source); 333 | } 334 | 335 | /// 336 | /// Computes the sum of a sequence of values using SIMD acceleration. 337 | /// 338 | /// 339 | /// Note that unlike this method is unchecked and will not throw an . 340 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 341 | /// 342 | /// A sequence of values to calculate the sum of. 343 | /// The sum of the values in the sequence. 344 | /// is . 345 | public static float Sum(this float[] source) 346 | { 347 | ArgumentNullException.ThrowIfNull(source); 348 | return SumCore(new ReadOnlySpan(source)); 349 | } 350 | 351 | /// 352 | /// Computes the sum of a sequence of values using SIMD acceleration. 353 | /// 354 | /// 355 | /// Note that unlike this method is unchecked and will not throw an . 356 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 357 | /// 358 | /// A sequence of values to calculate the sum of. 359 | /// The sum of the values in the sequence. 360 | /// is . 361 | public static float Sum(this List source) 362 | { 363 | ArgumentNullException.ThrowIfNull(source); 364 | return SumCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 365 | } 366 | 367 | /// 368 | /// Computes the sum of a sequence of values using SIMD acceleration. 369 | /// 370 | /// 371 | /// Note that unlike this method is unchecked and will not throw an . 372 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 373 | /// 374 | /// A sequence of values to calculate the sum of. 375 | /// The sum of the values in the sequence. 376 | public static float Sum(this Memory source) 377 | { 378 | return SumCore((ReadOnlySpan)source.Span); 379 | } 380 | 381 | /// 382 | /// Computes the sum of a sequence of values using SIMD acceleration. 383 | /// 384 | /// 385 | /// Note that unlike this method is unchecked and will not throw an . 386 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 387 | /// 388 | /// A sequence of values to calculate the sum of. 389 | /// The sum of the values in the sequence. 390 | public static float Sum(this ReadOnlyMemory source) 391 | { 392 | return SumCore(source.Span); 393 | } 394 | 395 | /// 396 | /// Computes the sum of a sequence of values using SIMD acceleration. 397 | /// 398 | /// 399 | /// Note that unlike this method is unchecked and will not throw an . 400 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 401 | /// 402 | /// A sequence of values to calculate the sum of. 403 | /// The sum of the values in the sequence. 404 | public static float Sum(this Span source) 405 | { 406 | return SumCore((ReadOnlySpan)source); 407 | } 408 | 409 | /// 410 | /// Computes the sum of a sequence of values using SIMD acceleration. 411 | /// 412 | /// 413 | /// Note that unlike this method is unchecked and will not throw an . 414 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 415 | /// 416 | /// A sequence of values to calculate the sum of. 417 | /// The sum of the values in the sequence. 418 | public static float Sum(this ReadOnlySpan source) 419 | { 420 | return SumCore(source); 421 | } 422 | 423 | /// 424 | /// Computes the sum of a sequence of values using SIMD acceleration. 425 | /// 426 | /// 427 | /// Note that unlike this method is unchecked and will not throw an . 428 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 429 | /// 430 | /// A sequence of values to calculate the sum of. 431 | /// The sum of the values in the sequence. 432 | /// is . 433 | public static double Sum(this double[] source) 434 | { 435 | ArgumentNullException.ThrowIfNull(source); 436 | return SumCore(new ReadOnlySpan(source)); 437 | } 438 | 439 | /// 440 | /// Computes the sum of a sequence of values using SIMD acceleration. 441 | /// 442 | /// 443 | /// Note that unlike this method is unchecked and will not throw an . 444 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 445 | /// 446 | /// A sequence of values to calculate the sum of. 447 | /// The sum of the values in the sequence. 448 | /// is . 449 | public static double Sum(this List source) 450 | { 451 | ArgumentNullException.ThrowIfNull(source); 452 | return SumCore((ReadOnlySpan)CollectionsMarshal.AsSpan(source)); 453 | } 454 | 455 | /// 456 | /// Computes the sum of a sequence of values using SIMD acceleration. 457 | /// 458 | /// 459 | /// Note that unlike this method is unchecked and will not throw an . 460 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 461 | /// 462 | /// A sequence of values to calculate the sum of. 463 | /// The sum of the values in the sequence. 464 | public static double Sum(this Memory source) 465 | { 466 | return SumCore((ReadOnlySpan)source.Span); 467 | } 468 | 469 | /// 470 | /// Computes the sum of a sequence of values using SIMD acceleration. 471 | /// 472 | /// 473 | /// Note that unlike this method is unchecked and will not throw an . 474 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 475 | /// 476 | /// A sequence of values to calculate the sum of. 477 | /// The sum of the values in the sequence. 478 | public static double Sum(this ReadOnlyMemory source) 479 | { 480 | return SumCore(source.Span); 481 | } 482 | 483 | /// 484 | /// Computes the sum of a sequence of values using SIMD acceleration. 485 | /// 486 | /// 487 | /// Note that unlike this method is unchecked and will not throw an . 488 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 489 | /// 490 | /// A sequence of values to calculate the sum of. 491 | /// The sum of the values in the sequence. 492 | public static double Sum(this Span source) 493 | { 494 | return SumCore((ReadOnlySpan)source); 495 | } 496 | 497 | /// 498 | /// Computes the sum of a sequence of values using SIMD acceleration. 499 | /// 500 | /// 501 | /// Note that unlike this method is unchecked and will not throw an . 502 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 503 | /// 504 | /// A sequence of values to calculate the sum of. 505 | /// The sum of the values in the sequence. 506 | public static double Sum(this ReadOnlySpan source) 507 | { 508 | return SumCore(source); 509 | } 510 | 511 | } 512 | 513 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.Sum.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | <# 8 | string summary(string x, bool nullable) 9 | { 10 | var remark = ""; 11 | if(x is "double" or "float") 12 | { 13 | remark += """ 14 | 15 | /// Unlike Linq, the order for the sum calculation is not sequential, leading to slight differences in floating-point operations and the resulting value. While this difference is minimal you should not use this method if you require the same result as Linq.Sum. 16 | """; 17 | } 18 | var exception = ""; 19 | if (nullable) 20 | { 21 | exception += """ 22 | 23 | /// is . 24 | """; 25 | } 26 | 27 | var cnvType = (string x) => x switch{ "uint" => "int", "ulong" => "long", _=> x}; 28 | 29 | return $$""" 30 | /// 31 | /// Computes the sum of a sequence of values using SIMD acceleration. 32 | /// 33 | /// 34 | /// Note that unlike this method is unchecked and will not throw an .{{remark}} 35 | /// 36 | /// A sequence of values to calculate the sum of. 37 | /// The sum of the values in the sequence.{{exception}} 38 | """; 39 | } 40 | 41 | var retType = (string x) => x; 42 | 43 | var methodName = "Sum"; 44 | var types = new [] 45 | { 46 | "int", "uint", "long", "ulong", "float", "double" 47 | }; 48 | #> 49 | namespace SimdLinq; 50 | 51 | using System.Runtime.InteropServices; 52 | 53 | <#@ include file=".\SimdLinqExtensions.ttinclude" #> 54 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.SumCore.cs: -------------------------------------------------------------------------------- 1 | using System.Numerics; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System.Runtime.Intrinsics; 5 | 6 | namespace SimdLinq; 7 | 8 | public static partial class SimdLinqExtensions 9 | { 10 | // Enumerable Sum => int, long, double, float, decimal 11 | // Additional: uint, ulong 12 | // Drop: decimal 13 | 14 | static T SumCore(ReadOnlySpan source) 15 | where T : struct, INumber 16 | { 17 | T sum = T.Zero; 18 | 19 | if (!Vector128.IsHardwareAccelerated || source.Length < Vector128.Count) 20 | { 21 | // Not SIMD supported or small source. 22 | unchecked // SIMD operation is unchecked so keep same behaviour 23 | { 24 | for (int i = 0; i < source.Length; i++) 25 | { 26 | sum += source[i]; 27 | } 28 | } 29 | } 30 | else if (!Vector256.IsHardwareAccelerated || source.Length < Vector256.Count) 31 | { 32 | // Only 128bit SIMD supported or small source. 33 | ref var begin = ref MemoryMarshal.GetReference(source); 34 | ref var last = ref Unsafe.Add(ref begin, source.Length); 35 | ref var current = ref begin; 36 | var vectorSum = Vector128.Zero; 37 | 38 | ref var to = ref Unsafe.Add(ref begin, source.Length - Vector128.Count); 39 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 40 | { 41 | vectorSum += Vector128.LoadUnsafe(ref current); 42 | current = ref Unsafe.Add(ref current, Vector128.Count); 43 | } 44 | while (Unsafe.IsAddressLessThan(ref current, ref last)) 45 | { 46 | unchecked // SIMD operation is unchecked so keep same behaviour 47 | { 48 | sum += current; 49 | } 50 | current = ref Unsafe.Add(ref current, 1); 51 | } 52 | 53 | sum += Vector128.Sum(vectorSum); 54 | } 55 | else 56 | { 57 | // 256bit SIMD supported 58 | ref var begin = ref MemoryMarshal.GetReference(source); 59 | ref var last = ref Unsafe.Add(ref begin, source.Length); 60 | ref var current = ref begin; 61 | var vectorSum = Vector256.Zero; 62 | 63 | ref var to = ref Unsafe.Add(ref begin, source.Length - Vector256.Count); 64 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 65 | { 66 | vectorSum += Vector256.LoadUnsafe(ref current); 67 | current = ref Unsafe.Add(ref current, Vector256.Count); 68 | } 69 | while (Unsafe.IsAddressLessThan(ref current, ref last)) 70 | { 71 | unchecked // SIMD operation is unchecked so keep same behaviour 72 | { 73 | sum += current; 74 | } 75 | current = ref Unsafe.Add(ref current, 1); 76 | } 77 | 78 | sum += Vector256.Sum(vectorSum); 79 | } 80 | 81 | return sum; 82 | } 83 | 84 | static long LongSumCore(ReadOnlySpan source) 85 | { 86 | long sum = 0; 87 | 88 | if (!Vector128.IsHardwareAccelerated || source.Length < Vector128.Count) 89 | { 90 | // Not SIMD supported or small source. 91 | unchecked 92 | { 93 | for (int i = 0; i < source.Length; i++) 94 | { 95 | sum += source[i]; 96 | } 97 | } 98 | } 99 | else if (!Vector256.IsHardwareAccelerated || source.Length < Vector256.Count) 100 | { 101 | // Only 128bit SIMD supported or small source. 102 | ref var begin = ref MemoryMarshal.GetReference(source); 103 | ref var last = ref Unsafe.Add(ref begin, source.Length); 104 | ref var current = ref begin; 105 | var vectorSum = Vector128.Zero; 106 | 107 | ref var to = ref Unsafe.Add(ref begin, source.Length - Vector128.Count); 108 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 109 | { 110 | var vector = Vector128.LoadUnsafe(ref current); 111 | (var lower, var upper) = Vector128.Widen(vector); 112 | 113 | vectorSum += lower; 114 | vectorSum += upper; 115 | 116 | current = ref Unsafe.Add(ref current, Vector128.Count); 117 | } 118 | while (Unsafe.IsAddressLessThan(ref current, ref last)) 119 | { 120 | unchecked 121 | { 122 | sum += current; 123 | } 124 | current = ref Unsafe.Add(ref current, 1); 125 | } 126 | 127 | sum += Vector128.Sum(vectorSum); 128 | } 129 | else 130 | { 131 | // 256bit SIMD supported 132 | ref var begin = ref MemoryMarshal.GetReference(source); 133 | ref var last = ref Unsafe.Add(ref begin, source.Length); 134 | ref var current = ref begin; 135 | 136 | var vectorSum = Vector256.Zero; 137 | 138 | ref var to = ref Unsafe.Add(ref begin, source.Length - Vector256.Count); 139 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 140 | { 141 | var vector = Vector256.LoadUnsafe(ref current); 142 | (var lower, var upper) = Vector256.Widen(vector); 143 | 144 | vectorSum += lower; 145 | vectorSum += upper; 146 | 147 | current = ref Unsafe.Add(ref current, Vector256.Count); 148 | } 149 | while (Unsafe.IsAddressLessThan(ref current, ref last)) 150 | { 151 | unchecked 152 | { 153 | sum += current; 154 | } 155 | current = ref Unsafe.Add(ref current, 1); 156 | } 157 | 158 | sum += Vector256.Sum(vectorSum); 159 | } 160 | 161 | return sum; 162 | } 163 | 164 | static ulong LongSumCore(ReadOnlySpan source) 165 | { 166 | ulong sum = 0; 167 | 168 | if (!Vector128.IsHardwareAccelerated || source.Length < Vector128.Count) 169 | { 170 | // Not SIMD supported or small source. 171 | unchecked 172 | { 173 | for (int i = 0; i < source.Length; i++) 174 | { 175 | sum += source[i]; 176 | } 177 | } 178 | } 179 | else if (!Vector256.IsHardwareAccelerated || source.Length < Vector256.Count) 180 | { 181 | // Only 128bit SIMD supported or small source. 182 | ref var begin = ref MemoryMarshal.GetReference(source); 183 | ref var last = ref Unsafe.Add(ref begin, source.Length); 184 | ref var current = ref begin; 185 | var vectorSum = Vector128.Zero; 186 | 187 | ref var to = ref Unsafe.Add(ref begin, source.Length - Vector128.Count); 188 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 189 | { 190 | var vector = Vector128.LoadUnsafe(ref current); 191 | (var lower, var upper) = Vector128.Widen(vector); 192 | 193 | vectorSum += lower; 194 | vectorSum += upper; 195 | 196 | current = ref Unsafe.Add(ref current, Vector128.Count); 197 | } 198 | while (Unsafe.IsAddressLessThan(ref current, ref last)) 199 | { 200 | unchecked 201 | { 202 | sum += current; 203 | } 204 | current = ref Unsafe.Add(ref current, 1); 205 | } 206 | 207 | sum += Vector128.Sum(vectorSum); 208 | } 209 | else 210 | { 211 | // 256bit SIMD supported 212 | ref var begin = ref MemoryMarshal.GetReference(source); 213 | ref var last = ref Unsafe.Add(ref begin, source.Length); 214 | ref var current = ref begin; 215 | 216 | var vectorSum = Vector256.Zero; 217 | 218 | ref var to = ref Unsafe.Add(ref begin, source.Length - Vector256.Count); 219 | while (Unsafe.IsAddressLessThan(ref current, ref to)) 220 | { 221 | var vector = Vector256.LoadUnsafe(ref current); 222 | (var lower, var upper) = Vector256.Widen(vector); 223 | 224 | vectorSum += lower; 225 | vectorSum += upper; 226 | 227 | current = ref Unsafe.Add(ref current, Vector256.Count); 228 | } 229 | while (Unsafe.IsAddressLessThan(ref current, ref last)) 230 | { 231 | unchecked 232 | { 233 | sum += current; 234 | } 235 | current = ref Unsafe.Add(ref current, 1); 236 | } 237 | 238 | sum += Vector256.Sum(vectorSum); 239 | } 240 | 241 | return sum; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace SimdLinq; 4 | 5 | public static partial class SimdLinqExtensions 6 | { 7 | [DoesNotReturn] 8 | static void ThrowNoElements() 9 | { 10 | throw new InvalidOperationException("Sequence contains no elements"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/SimdLinq/SimdLinqExtensions.ttinclude: -------------------------------------------------------------------------------- 1 | public static partial class SimdLinqExtensions 2 | { 3 | <# foreach(var type in types) { #> 4 | <#= summary(type, true) #> 5 | public static <#= retType(type) #> <#= methodName #>(this <#= type #>[] source) 6 | { 7 | ArgumentNullException.ThrowIfNull(source); 8 | return <#= methodName #>Core(new ReadOnlySpan<<#= type #>>(source)); 9 | } 10 | 11 | <#= summary(type, true) #> 12 | public static <#= retType(type) #> <#= methodName #>(this List<<#= type #>> source) 13 | { 14 | ArgumentNullException.ThrowIfNull(source); 15 | return <#= methodName #>Core((ReadOnlySpan<<#= type #>>)CollectionsMarshal.AsSpan(source)); 16 | } 17 | 18 | <#= summary(type, false) #> 19 | public static <#= retType(type) #> <#= methodName #>(this Memory<<#= type #>> source) 20 | { 21 | return <#= methodName #>Core((ReadOnlySpan<<#= type #>>)source.Span); 22 | } 23 | 24 | <#= summary(type, false) #> 25 | public static <#= retType(type) #> <#= methodName #>(this ReadOnlyMemory<<#= type #>> source) 26 | { 27 | return <#= methodName #>Core(source.Span); 28 | } 29 | 30 | <#= summary(type, false) #> 31 | public static <#= retType(type) #> <#= methodName #>(this Span<<#= type #>> source) 32 | { 33 | return <#= methodName #>Core((ReadOnlySpan<<#= type #>>)source); 34 | } 35 | 36 | <#= summary(type, false) #> 37 | public static <#= retType(type) #> <#= methodName #>(this ReadOnlySpan<<#= type #>> source) 38 | { 39 | return <#= methodName #>Core(source); 40 | } 41 | 42 | <# } #> 43 | } 44 | -------------------------------------------------------------------------------- /tests/SimdLinq.Tests/AverageTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace SimdLinq.Tests; 5 | 6 | public class AverageTest 7 | { 8 | [Fact] 9 | public void AverageHandlesNull() 10 | { 11 | // Arrange 12 | int[]? arr = null; 13 | 14 | // Act Assert 15 | Assert.Throws(() => arr!.Average()); 16 | } 17 | 18 | [Fact] 19 | public void AverageHandlesEmpty() 20 | { 21 | // Arrange 22 | var arr = Array.Empty(); 23 | 24 | // Act Assert 25 | Assert.ThrowsAny(() => arr.Average()); 26 | } 27 | 28 | [Fact] 29 | public void Average() 30 | { 31 | AverageTest(x => x.Next(-100000, 100000), Enumerable.Average, SimdLinqExtensions.Average); 32 | AverageTest(x => x.NextInt64(-100000000, 10000000), Enumerable.Average, SimdLinqExtensions.Average); 33 | AverageTestSingle(x => x.NextSingle(), Enumerable.Average, SimdLinqExtensions.Average); 34 | AverageTestDouble(x => x.NextDouble(), Enumerable.Average, SimdLinqExtensions.Average); 35 | 36 | static void AverageTest(Func rng, Func referenceFunc, Func simdFunc) 37 | { 38 | var rand = new Random(); 39 | for (int i = 1; i < 1024; i++) 40 | { 41 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 42 | 43 | var simd = simdFunc(source); 44 | var reference = referenceFunc(source); 45 | Assert.Equal((double)simd, (double)reference, 1.0); 46 | } 47 | } 48 | 49 | static void AverageTestSingle(Func rng, Func referenceFunc, Func simdFunc) 50 | { 51 | var rand = new Random(); 52 | for (int i = 1; i < 1024; i++) 53 | { 54 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 55 | 56 | var simd = simdFunc(source); 57 | var reference = referenceFunc(source); 58 | 59 | Assert.Equal((double)simd, (double)reference, 1.0); 60 | } 61 | } 62 | 63 | static void AverageTestDouble(Func rng, Func referenceFunc, Func simdFunc) 64 | { 65 | var rand = new Random(); 66 | for (int i = 1; i < 1024; i++) 67 | { 68 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 69 | 70 | var simd = simdFunc(source); 71 | var reference = referenceFunc(source); 72 | 73 | Assert.Equal((double)simd, (double)reference, 1.0); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/SimdLinq.Tests/ContainsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace SimdLinq.Tests; 5 | 6 | public class ContainsTest 7 | { 8 | [Fact] 9 | public void ContainsHandlesNull() 10 | { 11 | // Arrange 12 | int[]? arr = null; 13 | 14 | // Act Assert 15 | Assert.Throws(() => arr!.Contains(0)); 16 | } 17 | 18 | [Fact] 19 | public void Contains() 20 | { 21 | ContainsTest(x => x.Next(-100000, 100000), Enumerable.Contains, SimdLinqExtensions.Contains); 22 | ContainsTest(x => x.NextInt64(-1000000, 100000), Enumerable.Contains, SimdLinqExtensions.Contains); 23 | ContainsTest(x => x.NextSingle(), Enumerable.Contains, SimdLinqExtensions.Contains); 24 | ContainsTest(x => x.NextDouble(), Enumerable.Contains, SimdLinqExtensions.Contains); 25 | 26 | static void ContainsTest(Func rng, Func referenceFunc, Func simdFunc) 27 | { 28 | var rand = new Random(); 29 | for (int i = 1; i < 1024; i++) 30 | { 31 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 32 | var v = source[rand.Next(0, i)]; 33 | 34 | var simd1 = simdFunc(source, v); 35 | var ref1 = referenceFunc(source, v); 36 | simd1.Should().Be(ref1); 37 | simd1.Should().BeTrue(); 38 | 39 | var v2 = rng(rand); 40 | var simd2 = simdFunc(source, v); 41 | var ref2 = referenceFunc(source, v); 42 | simd2.Should().Be(ref2); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/SimdLinq.Tests/MinMaxTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace SimdLinq.Tests; 5 | 6 | public class MinMaxTest 7 | { 8 | [Fact] 9 | public void MinHandlesNull() 10 | { 11 | // Arrange 12 | int[]? arr = null; 13 | 14 | // Act Assert 15 | Assert.Throws(() => arr!.Min()); 16 | } 17 | 18 | [Fact] 19 | public void MinHandlesEmpty() 20 | { 21 | // Arrange 22 | var arr = Array.Empty(); 23 | 24 | // Act Assert 25 | Assert.ThrowsAny(() => arr!.Min()); 26 | } 27 | 28 | [Fact] 29 | public void Min() 30 | { 31 | MinTest(r => r.Next(), SimdLinqExtensions.Min); 32 | MinTest(r => r.NextInt64(), SimdLinqExtensions.Min); 33 | MinTest(r => r.NextSingle(), SimdLinqExtensions.Min); 34 | MinTest(r => r.NextDouble(), SimdLinqExtensions.Min); 35 | 36 | static void MinTest(Func rng, Func simdMin) 37 | { 38 | var rand = new Random(); 39 | for (int i = 1; i < 1024; i++) 40 | { 41 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 42 | 43 | var simd = simdMin(source); 44 | var reference = System.Linq.Enumerable.Min(source); 45 | simd.Should().Be(reference); 46 | } 47 | } 48 | } 49 | 50 | [Fact] 51 | public void MaxHandlesNull() 52 | { 53 | // Arrange 54 | int[]? arr = null; 55 | 56 | // Act Assert 57 | Assert.Throws(() => arr!.Max()); 58 | } 59 | 60 | [Fact] 61 | public void MaxHandlesEmpty() 62 | { 63 | // Arrange 64 | var arr = Array.Empty(); 65 | 66 | // Act Assert 67 | Assert.ThrowsAny(() => arr!.Max()); 68 | } 69 | 70 | [Fact] 71 | public void Max() 72 | { 73 | MaxTest(r => r.Next(), SimdLinqExtensions.Max); 74 | MaxTest(r => r.NextInt64(), SimdLinqExtensions.Max); 75 | MaxTest(r => r.NextSingle(), SimdLinqExtensions.Max); 76 | MaxTest(r => r.NextDouble(), SimdLinqExtensions.Max); 77 | 78 | static void MaxTest(Func rng, Func simdMax) 79 | { 80 | var rand = new Random(); 81 | for (int i = 1; i < 1024; i++) 82 | { 83 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 84 | 85 | var simd = simdMax(source); 86 | var reference = System.Linq.Enumerable.Max(source); 87 | simd.Should().Be(reference); 88 | } 89 | } 90 | } 91 | 92 | [Fact] 93 | public void MinMaxHandlesNull() 94 | { 95 | // Arrange 96 | int[]? arr = null; 97 | 98 | // Act Assert 99 | Assert.Throws(() => arr!.MinMax()); 100 | } 101 | 102 | [Fact] 103 | public void MinMaxHandlesEmpty() 104 | { 105 | // Arrange 106 | var arr = Array.Empty(); 107 | 108 | // Act Assert 109 | Assert.ThrowsAny(() => arr!.MinMax()); 110 | } 111 | 112 | [Fact] 113 | public void MinMax() 114 | { 115 | MinMaxTest(r => r.Next(), SimdLinqExtensions.MinMax); 116 | MinMaxTest(r => r.NextInt64(), SimdLinqExtensions.MinMax); 117 | MinMaxTest(r => r.NextSingle(), SimdLinqExtensions.MinMax); 118 | MinMaxTest(r => r.NextDouble(), SimdLinqExtensions.MinMax); 119 | 120 | static void MinMaxTest(Func rng, Func simdMinMax) 121 | { 122 | var rand = new Random(); 123 | for (int i = 1; i < 1024; i++) 124 | { 125 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 126 | 127 | var simd = simdMinMax(source); 128 | var refMin = System.Linq.Enumerable.Min(source); 129 | var refMax = System.Linq.Enumerable.Max(source); 130 | simd.Should().Be((refMin, refMax)!); 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /tests/SimdLinq.Tests/SequenceEqualTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace SimdLinq.Tests; 5 | 6 | public class SequenceEqualTest 7 | { 8 | [Fact] 9 | public void SequenceEqualsHandlesNull() 10 | { 11 | // Arrange 12 | int[]? first = null; 13 | int[]? second = Array.Empty(); 14 | 15 | // Act Assert 16 | Assert.Throws(() => SimdLinqExtensions.SequenceEqual(first!, second)); 17 | 18 | first = Array.Empty(); 19 | second = null; 20 | Assert.Throws(() => SimdLinqExtensions.SequenceEqual(first, second!)); 21 | } 22 | 23 | [Fact] 24 | public void SequenceEqual() 25 | { 26 | ContainsTest(x => x.Next(-100000, 100000), Enumerable.SequenceEqual, SimdLinqExtensions.SequenceEqual); 27 | 28 | static void ContainsTest(Func rng, Func referenceFunc, Func simdFunc) 29 | { 30 | var rand = new Random(); 31 | for (int i = 1; i < 1024; i++) 32 | { 33 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 34 | var source2 = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 35 | 36 | var simd1 = simdFunc(source, source); 37 | var ref1 = referenceFunc(source, source); 38 | simd1.Should().Be(ref1); 39 | 40 | var v2 = rng(rand); 41 | var simd2 = simdFunc(source, source2); 42 | var ref2 = referenceFunc(source, source2); 43 | simd2.Should().Be(ref2); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/SimdLinq.Tests/SimdLinq.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | all 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/SimdLinq.Tests/SumTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace SimdLinq.Tests; 5 | 6 | public class SumTest 7 | { 8 | [Fact] 9 | public void SumHandlesNull() 10 | { 11 | // Arrange 12 | int[]? arr = null; 13 | 14 | // Act Assert 15 | Assert.Throws(() => arr!.Sum()); 16 | } 17 | 18 | [Fact] 19 | public void Sum() 20 | { 21 | SumTest(x => x.Next(-100000, 100000), Enumerable.Sum, SimdLinqExtensions.Sum); 22 | SumTest(x => (uint)x.Next(0, 100000), x => (uint)x.Sum(y => (int)y), SimdLinqExtensions.Sum); 23 | SumTest(x => x.NextInt64(-100000000, 10000000), Enumerable.Sum, SimdLinqExtensions.Sum); 24 | SumTest(x => (ulong)x.NextInt64(-0, 10000000), x => (ulong)x.Sum(y => (long)y), SimdLinqExtensions.Sum); 25 | SumTestSingle(x => x.NextSingle(), Enumerable.Sum, SimdLinqExtensions.Sum); 26 | SumTestDouble(x => x.NextDouble(), Enumerable.Sum, SimdLinqExtensions.Sum); 27 | 28 | static void SumTest(Func rng, Func referenceFunc, Func simdFunc) 29 | { 30 | var rand = new Random(); 31 | for (int i = 1; i < 1024; i++) 32 | { 33 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 34 | 35 | var simd = simdFunc(source); 36 | var reference = referenceFunc(source); 37 | simd.Should().Be(reference); 38 | } 39 | } 40 | 41 | static void SumTestSingle(Func rng, Func referenceFunc, Func simdFunc) 42 | { 43 | var rand = new Random(); 44 | for (int i = 1; i < 1024; i++) 45 | { 46 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 47 | 48 | var simd = simdFunc(source); 49 | var reference = referenceFunc(source); 50 | 51 | Assert.Equal((double)simd, (double)reference, 1.0); 52 | } 53 | } 54 | 55 | static void SumTestDouble(Func rng, Func referenceFunc, Func simdFunc) 56 | { 57 | var rand = new Random(); 58 | for (int i = 1; i < 1024; i++) 59 | { 60 | var source = Enumerable.Range(1, i).Select(x => rng(rand)).ToArray(); 61 | 62 | var simd = simdFunc(source); 63 | var reference = referenceFunc(source); 64 | 65 | Assert.Equal((double)simd, (double)reference, 1.0); 66 | } 67 | } 68 | } 69 | } 70 | --------------------------------------------------------------------------------