Skip to content

Commit

Permalink
Update to net8.0 (#110)
Browse files Browse the repository at this point in the history
* Updated packages.

* Removed NET6 and added NET8 improvements for parsing.

* Slight improvement in VariableSpecificiation writing.

* Removed unnecessary array and list conversion.

* Initial support for Corvus URI template table matching.

* Optimizing for a large table.

* Optimizations for the Corvus Tavis API.

* Fall back to Regex for match without collection as it is faster.

* Optimization for lookahead of literal strings

* Unnecessary conversion of strings to ReadOnlyMemory.

* Improve JSON resolution perf

    - Removed the ineffective json serialization for property names.

* Removed unused parameter.

* Added a UriTemplateAndVerbTable
- This supports matching a UriTemplate and a Verb
- The verb is CASE SENSITIVE and so you should ensure you convert appropriately before calling.

* Removed offensive term in the tests ported from Tavis.

* Updated to dotnet8.0

* Added Corvus icon.
  • Loading branch information
mwadams authored Nov 15, 2023
1 parent cd785ca commit b6b7acc
Show file tree
Hide file tree
Showing 46 changed files with 1,360 additions and 2,044 deletions.
Binary file modified PackageIcon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Corvus.UriTemplates
Low-allocation URI Template parsing and resolution, supporting the [Tavis.UriTemplates](https://github.com/tavis-software/Tavis.UriTemplates) API

This is a netstandard2.1 and net7.0+ implementation of the [URI Template Spec RFC6570](http://tools.ietf.org/html/rfc6570).
This is a net8.0+ implementation of the [URI Template Spec RFC6570](http://tools.ietf.org/html/rfc6570).

The library implements Level 4 compliance and is tested against test cases from [UriTemplate test suite](https://github.com/uri-templates/uritemplate-test).

Expand All @@ -18,18 +18,27 @@ There is a standard benchmark testing basic parameter extraction and resolution
As you can see, there is a significant benefit to using the Corvus implementation, even without dropping down the low-level zero allocation API.

### Apply parameters to a URI template to resolve a URI
| Method | Mean | Error | Ratio | Gen0 | Allocated | Alloc Ratio |
|---------------------- |---------:|------:|------:|-------:|----------:|------------:|
| ResolveUriTavis | 694.0 ns | NA | 1.00 | 0.4377 | 1832 B | 1.00 |
| ResolveUriCorvusTavis | 640.5 ns | NA | 0.92 | 0.0515 | 216 B | 0.12 |
| ResolveUriCorvus | 214.9 ns | NA | 0.31 | - | - | 0.00 |
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|--------------------------- |---------:|--------:|--------:|------:|-------:|----------:|------------:|
| ResolveUriTavis | 356.4 ns | 2.59 ns | 2.29 ns | 1.00 | 0.1459 | 1832 B | 1.00 |
| ResolveUriCorvusTavis | 308.6 ns | 1.81 ns | 1.51 ns | 0.87 | 0.0172 | 216 B | 0.12 |
| ResolveUriCorvusJson | 439.5 ns | 2.75 ns | 2.44 ns | 1.23 | 0.0076 | 96 B | 0.05 |
| ResolveUriCorvusDictionary | 197.5 ns | 1.48 ns | 1.38 ns | 0.55 | 0.0069 | 88 B | 0.05 |

### Extract parameters from a URI by using a URI template
| Method | Mean | Error | Ratio | Gen0 | Allocated | Alloc Ratio |
|----------------------------- |---------:|------:|------:|-------:|----------:|------------:|
| ExtractParametersTavis | 980.6 ns | NA | 1.00 | 0.2613 | 1096 B | 1.00 |
| ExtractParametersCorvusTavis | 495.2 ns | NA | 0.50 | 0.1450 | 608 B | 0.55 |
| ExtractParametersCorvus | 174.6 ns | NA | 0.18 | - | - | 0.00 |
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|----------------------------------------------- |----------:|----------:|----------:|------:|-------:|----------:|------------:|
| ExtractParametersTavis | 775.49 ns | 15.076 ns | 19.066 ns | 1.00 | 0.0873 | 1096 B | 1.00 |
| ExtractParametersCorvusTavis | 231.16 ns | 4.362 ns | 3.867 ns | 0.30 | 0.0482 | 608 B | 0.55 |
| ExtractParametersCorvusTavisWithParameterCache | 133.24 ns | 0.322 ns | 0.301 ns | 0.17 | - | - | 0.00 |
| ExtractParametersCorvus | 75.53 ns | 0.507 ns | 0.474 ns | 0.10 | - | - | 0.00 |

### Match a URI Template Table
| Method | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio |
|----------------- |---------:|---------:|---------:|------:|-------:|----------:|------------:|
| MatchTavis | 48.65 us | 0.671 us | 0.560 us | 1.00 | 5.7983 | 72808 B | 1.000 |
| MatchCorvusTavis | 27.31 us | 0.255 us | 0.239 us | 0.56 | - | 376 B | 0.005 |
| MatchCorvus | 22.43 us | 0.297 us | 0.263 us | 0.46 | - | - | 0.000 |

## Tavis API
### Parameter Extraction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(EndjinProjectPropsPath)" Condition="$(EndjinProjectPropsPath) != ''" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
Expand All @@ -23,18 +23,22 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.5" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.10" />
<PackageReference Include="Endjin.RecommendedPractices.GitHub" Version="2.1.7">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Tavis.UriTemplates" Version="1.1.1" />
<PackageReference Include="Tavis.UriTemplates" Version="2.0.0" />
</ItemGroup>

<PropertyGroup>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<RestoreLockedMode Condition="$(ContinuousIntegrationBuild) == 'true'">true</RestoreLockedMode>
</PropertyGroup>

<ItemGroup>
<PackageReference Update="Roslynator.Analyzers" Version="4.6.2" />
</ItemGroup>

</Project>
7 changes: 3 additions & 4 deletions Solutions/Corvus.UriTemplate.Benchmarking/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).RunAll(
ManualConfig.Create(DefaultConfig.Instance)
.AddJob(Job.Dry
.WithRuntime(CoreRuntime.Core70)
.AddJob(Job.Default
.WithRuntime(CoreRuntime.Core80)
.WithOutlierMode(OutlierMode.RemoveAll)
.WithStrategy(RunStrategy.Throughput)
.WithIterationCount(1)));
.WithStrategy(RunStrategy.Throughput)));
158 changes: 158 additions & 0 deletions Solutions/Corvus.UriTemplate.Benchmarking/UriTemplateTableMatching.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// <copyright file="UriTemplateTableMatching.cs" company="Endjin Limited">
// Copyright (c) Endjin Limited. All rights reserved.
// </copyright>

using System;
using BenchmarkDotNet.Attributes;

namespace Corvus.UriTemplates.Benchmarking;

/// <summary>
/// Matches tables.
/// </summary>
[MemoryDiagnoser]
public class UriTemplateTableMatching
{
private const string Uri = "/baz/fod/blob";
private static readonly Uri TavisUri = new(Uri, UriKind.RelativeOrAbsolute);
private Tavis.UriTemplates.UriTemplateTable? tavisTemplateTable;
private TavisApi.UriTemplateTable? corvusTavisTemplateTable;
private UriTemplateTable<string>? corvusTemplateTable;

/// <summary>
/// Global setup.
/// </summary>
/// <returns>A <see cref="Task"/> which completes once cleanup is complete.</returns>
[GlobalSetup]
public Task GlobalSetup()
{
UriTemplateTable<string>.Builder builder = UriTemplateTable.CreateBuilder<string>();
this.corvusTavisTemplateTable = new TavisApi.UriTemplateTable();
this.tavisTemplateTable = new Tavis.UriTemplates.UriTemplateTable();

this.corvusTavisTemplateTable.Add("root", new TavisApi.UriTemplate("/"));
this.tavisTemplateTable.Add("root", new Tavis.UriTemplates.UriTemplate("/"));
builder.Add("/", "root");

for (int i = 0; i < 300; ++i)
{
string guid = Guid.NewGuid().ToString();
string uri1 = $"/{guid}/{{bar}}";
string uri2 = $"/baz/{guid}";
string uri3 = $"/{{goo}}/{{bar}}/{guid}";

this.corvusTavisTemplateTable.Add(guid, new TavisApi.UriTemplate(uri1));
this.corvusTavisTemplateTable.Add($"baz_{guid}", new TavisApi.UriTemplate(uri2));
this.corvusTavisTemplateTable.Add($"goo_{guid}", new TavisApi.UriTemplate(uri3));
this.tavisTemplateTable.Add(guid, new Tavis.UriTemplates.UriTemplate(uri1));
this.tavisTemplateTable.Add($"baz_{guid}", new Tavis.UriTemplates.UriTemplate(uri2));
this.tavisTemplateTable.Add($"goo_{guid}", new Tavis.UriTemplates.UriTemplate(uri3));
builder.Add(uri1, guid);
builder.Add(uri2, $"baz_{guid}");
builder.Add(uri3, $"goo_{guid}");
}

// Add the real matches
this.corvusTavisTemplateTable.Add("blob", new TavisApi.UriTemplate("/baz/{bar}/blob"));
this.tavisTemplateTable.Add("blob", new Tavis.UriTemplates.UriTemplate("/baz/{bar}/blob"));
builder.Add("/baz/{bar}/blob", "blob");

this.corvusTemplateTable = builder.ToTable();

// Warm up to create all the parsers etc.
this.MatchTavis();
this.MatchCorvusTavis();
this.MatchCorvus();

return Task.CompletedTask;
}

/// <summary>
/// Global cleanup.
/// </summary>
/// <returns>A <see cref="Task"/> which completes once cleanup is complete.</returns>
[GlobalCleanup]
public Task GlobalCleanup()
{
return Task.CompletedTask;
}

/// <summary>
/// Extract parameters from a URI template using Tavis types.
/// </summary>
/// <returns>
/// A result, to ensure that the code under test does not get optimized out of existence.
/// </returns>
[Benchmark(Baseline = true)]
public bool MatchTavis()
{
Tavis.UriTemplates.TemplateMatch? result = this.tavisTemplateTable!.Match(TavisUri);
if (result?.Parameters is IDictionary<string, object> parameters)
{
int count = 0;
foreach (KeyValuePair<string, object> param in parameters)
{
count++;
}

return true;
}

return false;
}

/// <summary>
/// Extract parameters from a URI template using the Corvus implementation of the Tavis API.
/// </summary>
/// <returns>
/// A result, to ensure that the code under test does not get optimized out of existence.
/// </returns>
[Benchmark]
public bool MatchCorvusTavis()
{
TavisApi.TemplateMatch? result = this.corvusTavisTemplateTable!.Match(TavisUri);
if (result?.Parameters is IDictionary<string, object> parameters)
{
int count = 0;
foreach (KeyValuePair<string, object> param in parameters)
{
count++;
}

return true;
}

return false;
}

/// <summary>
/// Extract parameters from a URI template using the Corvus implementation of the Tavis API.
/// </summary>
/// <returns>
/// A result, to ensure that the code under test does not get optimized out of existence.
/// </returns>
[Benchmark]
public bool MatchCorvus()
{
if (this.corvusTemplateTable!.TryMatch(Uri, out TemplateMatchResult<string> match))
{
int count = 0;
match.Parser.ParseUri(Uri, Count, ref count);
return true;
}

return false;

static void Count(bool reset, ReadOnlySpan<char> name, ReadOnlySpan<char> value, ref int state)
{
if (reset)
{
state = 0;
}
else
{
state++;
}
}
}
}
Loading

0 comments on commit b6b7acc

Please sign in to comment.