Skip to content

Commit

Permalink
Span-based values
Browse files Browse the repository at this point in the history
  • Loading branch information
jtmueller committed May 17, 2024
1 parent 5ce20ef commit 1184741
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/RustyOptions.Benchmarks/FirstOrNone.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ namespace RustyOptions.Benchmarks;
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
[Config(typeof(Config))]
public class FirstOrNoneBenchmarks
public class FirstOrNone
{
const int SIZE = 1000;

private readonly int[] intArray;
private readonly List<int> intList;

public FirstOrNoneBenchmarks()
public FirstOrNone()
{
intArray = [.. Enumerable.Range(1, SIZE)];
intList = [.. intArray];
Expand Down
4 changes: 2 additions & 2 deletions src/RustyOptions.Benchmarks/LastOrNone.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ namespace RustyOptions.Benchmarks;
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
[Config(typeof(Config))]
public class LastOrNoneBenchmarks
public class LastOrNone
{
const int SIZE = 1000;

private readonly int[] intArray;
private readonly List<int> intList;

public LastOrNoneBenchmarks()
public LastOrNone()
{
intArray = [.. Enumerable.Range(1, SIZE)];
intList = [.. intArray];
Expand Down
44 changes: 44 additions & 0 deletions src/RustyOptions.Benchmarks/NumericOptionValues.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using BenchmarkDotNet.Attributes;

namespace RustyOptions.Benchmarks;

[Config(typeof(Config))]
public class NumericOptionValues
{
const int SEED = 10007;
const int SIZE = 1000;

private readonly NumericOption<int>[] intArray;
private readonly int[] valuesBuffer;

public NumericOptionValues()
{
var rand = new Random(SEED);
intArray = [.. Enumerable.Range(1, SIZE)
.Select(_ => rand.NextDouble() < 0.45
? rand.Next().SomeNumeric() : NumericOption.None<int>())
];
valuesBuffer = new int[intArray.Length];
}

[Benchmark(Baseline = true)]
public void EnumerableValues()
{
int i = 0;
foreach (var _ in intArray.AsEnumerable().Values())
{
i++;
}
}

[Benchmark]
public void ArrayValues()
{
int i = 0;
int count = intArray.CopyValuesTo(valuesBuffer);
foreach (var _ in valuesBuffer.AsSpan(0, count))
{
i++;
}
}
}
80 changes: 80 additions & 0 deletions src/RustyOptions/NumericOptionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static System.ArgumentNullException;
using static RustyOptions.NumericOption;

Expand Down Expand Up @@ -265,6 +266,85 @@ public static IEnumerable<T> Values<T>(this IEnumerable<NumericOption<T>> self)
}
}

/// <summary>
/// Copies the inner values of all <see cref="NumericOption{T}"/> in a span to a destination span,
/// returning the number of values copied.
/// </summary>
/// <typeparam name="T">The type of the numeric option values.</typeparam>
/// <param name="self">The source span of numeric options.</param>
/// <param name="destination">The destination span to copy the values to.</param>
/// <returns>The number of values copied to the destination span.</returns>
/// <exception cref="System.IndexOutOfRangeException">
/// Thrown if the destination span is too small to hold all the values.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CopyValuesTo<T>(this NumericOption<T>[] self, Span<T> destination)
where T : struct, INumber<T>
{
return CopyValuesTo((ReadOnlySpan<NumericOption<T>>)self, destination);
}

/// <summary>
/// Copies the inner values of all <see cref="NumericOption{T}"/> in a span to a destination span,
/// returning the number of values copied.
/// </summary>
/// <typeparam name="T">The type of the numeric option values.</typeparam>
/// <param name="self">The source span of numeric options.</param>
/// <param name="destination">The destination span to copy the values to.</param>
/// <returns>The number of values copied to the destination span.</returns>
/// <exception cref="System.IndexOutOfRangeException">
/// Thrown if the destination span is too small to hold all the values.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CopyValuesTo<T>(this List<NumericOption<T>> self, Span<T> destination)
where T : struct, INumber<T>
{
return CollectionsMarshal.AsSpan(self).CopyValuesTo(destination);
}

/// <summary>
/// Copies the inner values of all <see cref="NumericOption{T}"/> in a span to a destination span,
/// returning the number of values copied.
/// </summary>
/// <typeparam name="T">The type of the numeric option values.</typeparam>
/// <param name="self">The source span of numeric options.</param>
/// <param name="destination">The destination span to copy the values to.</param>
/// <returns>The number of values copied to the destination span.</returns>
/// <exception cref="System.IndexOutOfRangeException">
/// Thrown if the destination span is too small to hold all the values.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CopyValuesTo<T>(this Span<NumericOption<T>> self, Span<T> destination)
where T : struct, INumber<T>
{
return CopyValuesTo((ReadOnlySpan<NumericOption<T>>)self, destination);
}

/// <summary>
/// Copies the inner values of all <see cref="NumericOption{T}"/> in a span to a destination span,
/// returning the number of values copied.
/// </summary>
/// <typeparam name="T">The type of the numeric option values.</typeparam>
/// <param name="self">The source span of numeric options.</param>
/// <param name="destination">The destination span to copy the values to.</param>
/// <returns>The number of values copied to the destination span.</returns>
/// <exception cref="System.IndexOutOfRangeException">
/// Thrown if the destination span is too small to hold all the values.
/// </exception>
public static int CopyValuesTo<T>(this ReadOnlySpan<NumericOption<T>> self, Span<T> destination)
where T : struct, INumber<T>
{
int j = 0;
for (int i = 0; i < self.Length; i++)
{
if (self[i].IsSome(out var value))
{
destination[j++] = value;
}
}
return j;
}

/// <summary>
/// Wraps the given number in a <see cref="NumericOption{T}"/>.
/// </summary>
Expand Down
82 changes: 81 additions & 1 deletion src/RustyOptions/OptionCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ namespace RustyOptions;
// TODO: test coverage:
// https://app.codecov.io/github/jtmueller/RustyOptions/blob/users%2Fjtm%2Fcollection-performance/src%2FRustyOptions%2FOptionCollectionExtensions.cs
// https://app.codecov.io/github/jtmueller/RustyOptions/blob/users%2Fjtm%2Fcollection-performance/src%2FRustyOptions%2FOptionCollectionExtensions.cs
// TODO: Update README with performance notes.
// https://app.codecov.io/github/jtmueller/RustyOptions/blob/users%2Fjtm%2Fcollection-performance/src%2FRustyOptions%2FNumericOptionExtensions.cs
// TODO: Update README with performance notes, JSON breaking change notes.

/// <summary>
/// Extension methods for using collections with <see cref="Option{T}"/>.
Expand Down Expand Up @@ -38,6 +39,85 @@ public static IEnumerable<T> Values<T>(this IEnumerable<Option<T>> self)
}
}

/// <summary>
/// Copies the inner values of all <see cref="NumericOption{T}"/> in a span to a destination span,

Check warning on line 43 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-ubuntu-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved

Check warning on line 43 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-ubuntu-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved

Check warning on line 43 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-windows-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved

Check warning on line 43 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-windows-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved

Check warning on line 43 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-macOS-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved

Check warning on line 43 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-macOS-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved
/// returning the number of values copied.
/// </summary>
/// <typeparam name="T">The type of the numeric option values.</typeparam>
/// <param name="self">The source span of numeric options.</param>
/// <param name="destination">The destination span to copy the values to.</param>
/// <returns>The number of values copied to the destination span.</returns>
/// <exception cref="System.IndexOutOfRangeException">
/// Thrown if the destination span is too small to hold all the values.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CopyValuesTo<T>(this Option<T>[] self, Span<T> destination)
where T : struct
{
return CopyValuesTo((ReadOnlySpan<Option<T>>)self, destination);
}

/// <summary>
/// Copies the inner values of all <see cref="NumericOption{T}"/> in a span to a destination span,

Check warning on line 61 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-ubuntu-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved

Check warning on line 61 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-ubuntu-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved

Check warning on line 61 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-windows-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved

Check warning on line 61 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-windows-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved

Check warning on line 61 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-macOS-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved

Check warning on line 61 in src/RustyOptions/OptionCollectionExtensions.cs

View workflow job for this annotation

GitHub Actions / build-and-test-macOS-latest

XML comment has cref attribute 'NumericOption{T}' that could not be resolved
/// returning the number of values copied.
/// </summary>
/// <typeparam name="T">The type of the numeric option values.</typeparam>
/// <param name="self">The source span of numeric options.</param>
/// <param name="destination">The destination span to copy the values to.</param>
/// <returns>The number of values copied to the destination span.</returns>
/// <exception cref="System.IndexOutOfRangeException">
/// Thrown if the destination span is too small to hold all the values.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CopyValuesTo<T>(this List<Option<T>> self, Span<T> destination)
where T : struct
{
return CollectionsMarshal.AsSpan(self).CopyValuesTo(destination);
}

/// <summary>
/// Copies the inner values of all <see cref="Option{T}"/> in a span to a destination span,
/// returning the number of values copied.
/// </summary>
/// <typeparam name="T">The type of the numeric option values.</typeparam>
/// <param name="self">The source span of numeric options.</param>
/// <param name="destination">The destination span to copy the values to.</param>
/// <returns>The number of values copied to the destination span.</returns>
/// <exception cref="System.IndexOutOfRangeException">
/// Thrown if the destination span is too small to hold all the values.
/// </exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CopyValuesTo<T>(this Span<Option<T>> self, Span<T> destination)
where T : struct
{
return CopyValuesTo((ReadOnlySpan<Option<T>>)self, destination);
}

/// <summary>
/// Copies the inner values of all <see cref="Option{T}"/> in a span to a destination span,
/// returning the number of values copied.
/// </summary>
/// <typeparam name="T">The type of the numeric option values.</typeparam>
/// <param name="self">The source span of numeric options.</param>
/// <param name="destination">The destination span to copy the values to.</param>
/// <returns>The number of values copied to the destination span.</returns>
/// <exception cref="System.IndexOutOfRangeException">
/// Thrown if the destination span is too small to hold all the values.
/// </exception>
public static int CopyValuesTo<T>(this ReadOnlySpan<Option<T>> self, Span<T> destination)
where T : struct
{
int j = 0;
for (int i = 0; i < self.Length; i++)
{
if (self[i].IsSome(out var value))
{
destination[j++] = value;
}
}
return j;
}

/// <summary>
/// Gets the value associated with the given <paramref name="key"/> from the dictionary as an <see cref="Option{T}"/>.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/RustyOptions/ResultCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public static IEnumerable<T> Values<T, TErr>(this IEnumerable<Result<T, TErr>> s
}
}

// TODO: Span overload for Values?

/// <summary>
/// Flattens a sequence of <see cref="Result{T, TErr}"/> into a sequence containing all error values.
/// Ok results are discarded.
Expand Down
21 changes: 17 additions & 4 deletions src/RustyOptions/ResultJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ public override Result<T, TErr> Read(ref Utf8JsonReader reader, Type typeToConve
{
if (reader.TokenType == JsonTokenType.True)
{
if (reader.Read() && reader.TokenType == JsonTokenType.PropertyName && reader.ValueSpan.SequenceEqual("value"u8) && reader.Read())
if (reader.Read() &&
reader.TokenType == JsonTokenType.PropertyName &&
reader.ValueSpan.SequenceEqual("value"u8) &&
reader.Read())
{
output = Result.Ok<T, TErr>(_valueConverter.Read(ref reader, _valueType, options)!);
}
Expand All @@ -89,7 +92,10 @@ public override Result<T, TErr> Read(ref Utf8JsonReader reader, Type typeToConve
}
else if (reader.TokenType == JsonTokenType.False)
{
if (reader.Read() && reader.TokenType == JsonTokenType.PropertyName && reader.ValueSpan.SequenceEqual("error"u8) && reader.Read())
if (reader.Read() &&
reader.TokenType == JsonTokenType.PropertyName &&
reader.ValueSpan.SequenceEqual("error"u8) &&
reader.Read())
{
output = Result.Err<T, TErr>(_errConverter.Read(ref reader, _errType, options)!);
}
Expand All @@ -107,7 +113,11 @@ public override Result<T, TErr> Read(ref Utf8JsonReader reader, Type typeToConve
{
var value = _valueConverter.Read(ref reader, _valueType, options);

if (reader.Read() && reader.TokenType == JsonTokenType.PropertyName && reader.ValueSpan.SequenceEqual("ok"u8) && reader.Read() && reader.TokenType == JsonTokenType.True)
if (reader.Read() &&
reader.TokenType == JsonTokenType.PropertyName &&
reader.ValueSpan.SequenceEqual("ok"u8) &&
reader.Read() &&
reader.TokenType == JsonTokenType.True)
{
output = Result.Ok<T, TErr>(value!);
}
Expand All @@ -120,7 +130,10 @@ public override Result<T, TErr> Read(ref Utf8JsonReader reader, Type typeToConve
{
var err = _errConverter.Read(ref reader, _errType, options);

if (reader.Read() && reader.TokenType == JsonTokenType.PropertyName && reader.ValueSpan.SequenceEqual("ok"u8) && reader.Read() && reader.TokenType == JsonTokenType.False)
if (reader.Read() &&
reader.TokenType == JsonTokenType.PropertyName &&
reader.ValueSpan.SequenceEqual("ok"u8) &&
reader.Read() && reader.TokenType == JsonTokenType.False)
{
output = Result.Err<T, TErr>(err!);
}
Expand Down

0 comments on commit 1184741

Please sign in to comment.