Skip to content

Commit

Permalink
ImTools: optimize Match with the Array.Resize
Browse files Browse the repository at this point in the history
  • Loading branch information
dadhi committed Apr 21, 2023
1 parent 768ca33 commit ff330a0
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 199 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build_benchmark",
"program": "${workspaceFolder}/playground/Playground/bin/Debug/net6/Playground.dll",
"program": "${workspaceFolder}/playground/Playground/bin/Debug/net7.0/Playground.dll",
"args": [],
"cwd": "${workspaceFolder}/playground/Playground",
"stopAtEntry": false,
Expand Down
74 changes: 0 additions & 74 deletions playground/Playground/MatchCaseOrder.cs

This file was deleted.

224 changes: 224 additions & 0 deletions playground/Playground/MatchExperiments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Order;
using DryIoc.ImTools;

namespace Playground
{
[MemoryDiagnoser]
public class MatchExperiments
{
/*
## Match3
BenchmarkDotNet=v0.13.5, OS=Windows 10 (10.0.19042.928/20H2/October2020Update)
Intel Core i5-8350U CPU 1.70GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET SDK=7.0.100
[Host] : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
DefaultJob : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2
## 2 slices
```cs
private readonly int[] _items = { 42, 43, 44, 45 };
```
| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |---------:|---------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
| Match1 | 39.10 ns | 0.443 ns | 0.414 ns | 39.08 ns | 1.00 | 0.00 | 0.0204 | 64 B | 1.00 |
| Match2 | 44.66 ns | 0.988 ns | 1.138 ns | 44.56 ns | 1.14 | 0.03 | 0.0204 | 64 B | 1.00 |
| Match3 | 31.20 ns | 0.710 ns | 1.243 ns | 30.75 ns | 0.80 | 0.03 | 0.0229 | 72 B | 1.12 |
| Match4 | 29.19 ns | 0.632 ns | 1.539 ns | 28.67 ns | 0.74 | 0.03 | 0.0102 | 32 B | 0.50 |
## 5 slices
```cs
private readonly int[] _items = { 4, 1, 42, 44, 45, 46, 47, 48, 49, 52 };
```
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|------- |----------:|---------:|---------:|------:|--------:|-------:|----------:|------------:|
| Match1 | 111.13 ns | 2.275 ns | 3.862 ns | 1.00 | 0.00 | 0.0663 | 208 B | 1.00 |
| Match2 | 106.21 ns | 1.918 ns | 1.700 ns | 0.96 | 0.03 | 0.0663 | 208 B | 1.00 |
| Match3 | 54.01 ns | 1.053 ns | 1.126 ns | 0.49 | 0.02 | 0.0356 | 112 B | 0.54 |
| Match4 | 110.05 ns | 1.568 ns | 1.225 ns | 1.00 | 0.03 | 0.0433 | 136 B | 0.65 |
*/

private readonly int[] _items = { 42, 43, 44, 45 };
// private readonly int[] _items = { 4, 1, 42, 44, 45, 46, 47, 48, 49, 52 };

[Benchmark(Baseline = true)]
public object Match1() => _items.Match(x => (x & 1) == 0);

[Benchmark]
public object Match2() => _items.Match2(x => (x & 1) == 0);

[Benchmark]
public object Match3() => _items.Match3(x => (x & 1) == 0);

[Benchmark]
public object Match4() => _items.Match4(x => (x & 1) == 0);
}

public static class Arr
{
public static T[] Match4<T>(this T[] source, Func<T, bool> condition)
{
var inMatch = false;
var matchesCount = 0;

var firstMatchStart = 0;
var firstMatchCount = 0;
var secondMatchStart = 0;
var secondMatchCount = 0;
var nextMatchStart = 0;

T[] result = null;
for (var i = 0; (uint)i < source.Length; i++)
{
if (condition(source[i]))
{
if (!inMatch)
{
inMatch = true;
++matchesCount;
if (matchesCount == 1)
firstMatchStart = i;
else if (matchesCount == 2)
secondMatchStart = i;
else
nextMatchStart = i;
}
}
else if (inMatch)
{
inMatch = false;
if (matchesCount == 1)
firstMatchCount = i - firstMatchStart;
else if (matchesCount == 2)
secondMatchCount = i - secondMatchStart;
else if (result == null)
{
result = new T[firstMatchCount + secondMatchCount + i - nextMatchStart];
Array.Copy(source, firstMatchStart, result, 0, firstMatchCount);
Array.Copy(source, secondMatchStart, result, firstMatchCount, secondMatchCount);
Array.Copy(source, nextMatchStart, result, firstMatchCount + secondMatchCount, i - nextMatchStart);
}
else
result = source.AppendTo(result, nextMatchStart, i - nextMatchStart);
}
}

if (inMatch)
{
if (matchesCount == 1)
firstMatchCount = source.Length - firstMatchStart;
else if (matchesCount == 2)
secondMatchCount = source.Length - secondMatchStart;
else if (result == null)
{
result = new T[firstMatchCount + secondMatchCount + source.Length - nextMatchStart];
Array.Copy(source, firstMatchStart, result, 0, firstMatchCount);
Array.Copy(source, secondMatchStart, result, firstMatchCount, secondMatchCount);
Array.Copy(source, nextMatchStart, result, firstMatchCount + secondMatchCount, source.Length - nextMatchStart);
}
else
result = source.AppendTo(result, nextMatchStart, source.Length - nextMatchStart);
}

if (matchesCount < 3)
{
result = new T[firstMatchCount + secondMatchCount];

if (firstMatchCount == 1)
result[0] = source[firstMatchStart];
else
Array.Copy(source, firstMatchStart, result, 0, firstMatchCount);

if (matchesCount != 1)
{
if (secondMatchCount == 1)
result[firstMatchCount] = source[secondMatchStart];
else
Array.Copy(source, secondMatchStart, result, firstMatchCount, secondMatchCount);
}

return result;
}

return result ?? ArrayTools.Empty<T>();
}

public static T[] Match3<T>(this T[] source, Func<T, bool> condition)
{
var results = new T[source.Length];

int matched = 0;
for (var i = 0; (uint)i < source.Length; i++)
if (condition(source[i]))
results[matched++] = source[i];

Array.Resize(ref results, matched);
return results;
}

public static T[] Match2<T>(this T[] source, Func<T, bool> condition)
{
if (source == null || source.Length == 0)
return source;

if (source.Length > 2)
{
var matchStart = 0;
T[] matches = null;
var matchFound = false;

var i = 0;
while (i < source.Length)
{
matchFound = condition(source[i]);
if (!matchFound)
{
// for accumulated matched items
if (i != 0 && i > matchStart)
// matches = ArrayTools.AppendTo(source, matchStart, i - matchStart, matches);
matches = matches == null
? source.Copy(matchStart, i - matchStart)
: source.AppendTo(matches, matchStart, i - matchStart);
matchStart = i + 1; // guess the next match start will be after the non-matched item
}

++i;
}

// when last match was found but not all items are matched (hence matchStart != 0)
if (matchFound && matchStart != 0)
// return ArrayTools.AppendTo(source, matchStart, i - matchStart, matches);
return matches == null
? source.Copy(matchStart, i - matchStart)
: source.AppendTo(matches, matchStart, i - matchStart);

if (matches != null)
return matches;

if (matchStart != 0) // no matches
return ArrayTools.Empty<T>();

return source;
}

if (source.Length == 2)
{
var condition0 = condition(source[0]);
var condition1 = condition(source[1]);
return condition0 && condition1 ? new[] { source[0], source[1] }
: condition0 ? new[] { source[0] }
: condition1 ? new[] { source[1] }
: ArrayTools.Empty<T>();
}

return condition(source[0]) ? source : ArrayTools.Empty<T>();
}
}
}
18 changes: 9 additions & 9 deletions playground/Playground/Playground.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Description>Benchmarks, sandbox for experiments.</Description>
<NoWarn>1701;1702;AD0001;NU1608</NoWarn>
<TieredCompilation>false</TieredCompilation>
Expand All @@ -14,17 +14,17 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Autofac" Version="6.3.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.2.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="Autofac" Version="7.0.1" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="Grace" Version="7.2.1" />
<PackageReference Include="Grace.DependencyInjection.Extensions" Version="7.1.0" />
<PackageReference Include="LightInject" Version="6.4.1" />
<PackageReference Include="LightInject" Version="6.6.3" />
<PackageReference Include="Microsoft.Experimental.Collections" Version="1.0.6-e190117-3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="SimpleInjector" Version="5.3.3" />
<PackageReference Include="Lamar" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageReference Include="SimpleInjector" Version="5.4.1" />
<PackageReference Include="Lamar" Version="11.1.2" />
</ItemGroup>

<ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions playground/Playground/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static void Main()
//bm.SortViaInsertion();
//BenchmarkRunner.Run<ManualInsertionSortVsOrderBy>();

//BenchmarkRunner.Run<MatchCaseOrder>();
BenchmarkRunner.Run<MatchExperiments>();

//BenchmarkRunner.Run<ImMapBenchmarks.Populate>();
//BenchmarkRunner.Run<ImMapBenchmarks.Lookup>();
Expand All @@ -40,7 +40,7 @@ public static void Main()
//BenchmarkRunner.Run<RealisticUnitOfWorkBenchmark.CreateContainerAndRegisterServices>();
//BenchmarkRunner.Run<RealisticUnitOfWorkBenchmark.FirstTimeOpenScopeAndResolve>();
//BenchmarkRunner.Run<RealisticUnitOfWorkBenchmark.SecondTimeOpenScopeAndResolve>();
BenchmarkRunner.Run<RealisticUnitOfWorkBenchmark.CreateContainerAndRegisterServices_Then_FirstTimeOpenScopeAndResolve>();
// BenchmarkRunner.Run<RealisticUnitOfWorkBenchmark.CreateContainerAndRegisterServices_Then_FirstTimeOpenScopeAndResolve>();
// BenchmarkRunner.Run<RealisticUnitOfWorkBenchmark.CompileResolutionExpression>();
// BenchmarkRunner.Run<RealisticUnitOfWorkBenchmark.OpenScopeAndResolve>();

Expand Down
Loading

0 comments on commit ff330a0

Please sign in to comment.