Skip to content

Commit

Permalink
Span optimizations (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
josephmoresena authored Sep 25, 2024
1 parent 20d6bf3 commit 7478b30
Show file tree
Hide file tree
Showing 22 changed files with 276 additions and 77 deletions.
21 changes: 18 additions & 3 deletions src/Intermediate/Rxmxnx.PInvoke.CString.Intermediate/CString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ public sealed partial class CString : ICloneable, IEquatable<CString>, IEquatabl
/// <summary>
/// Represents an empty UTF-8 string. This field is read-only.
/// </summary>
public static readonly CString Empty = new(new Byte[] { default, }, true);
public static readonly CString Empty = new([default,], true);
/// <summary>
/// Represents an null-pointer UTF-8 string. This field is read-only.
/// Represents a null-pointer UTF-8 string. This field is read-only.
/// </summary>
public static readonly CString Zero = new(IntPtr.Zero, 0, true);

Expand Down Expand Up @@ -238,11 +238,26 @@ public override String ToString()
/// </returns>
[return: NotNullIfNotNull(nameof(bytes))]
public static CString? Create(Byte[]? bytes) => bytes is not null ? new(bytes, false) : default;
/// <summary>
/// Constructs a new instance of the <see cref="CString"/> class using <paramref name="state"/>.
/// </summary>
/// <param name="state">Function state parameter.</param>
/// <returns>
/// A new instance of the <see cref="CString"/> class.
/// </returns>
#pragma warning disable CA2252
public static CString Create<TState>(TState state) where TState : struct, IUtf8FunctionState<TState>
{
ValueRegion<Byte> data = ValueRegion<Byte>.Create(state, TState.GetSpan);
Int32 length = TState.GetLength(state);
return new(data, true, state.IsNullTerminated, length);
}
#pragma warning restore CA2252
/// <summary>
/// Constructs a new instance of the <see cref="CString"/> class using the pointer to a UTF-8
/// character array and length provided.
/// </summary>
/// <param name="ptr">A pointer to a array of UTF-8 characters.</param>
/// <param name="ptr">A pointer to an array of UTF-8 characters.</param>
/// <param name="length">The number of <see cref="Byte"/> within value to use.</param>
/// <param name="useFullLength">Indicates whether the total length should be used.</param>
/// <returns>A new instance of the <see cref="CString"/> class.</returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,23 @@ private CString(String utf16Text)
this._data = helper.AsRegion();
this._isNullTerminated = true;
}
/// <summary>
/// Initializes a new instance of the <see cref="CString"/> class to the value
/// indicated by <paramref name="data"/>
/// </summary>
/// <param name="data">Internal instance data.</param>
/// <param name="isFunction">Indicates whether the UTF-8 data is retrieved using a function.</param>
/// <param name="isNullTerminated">Indicates whether the UTF-8 text is null-terminated.</param>
/// <param name="length">UTF-8 text length.</param>
private CString(ValueRegion<Byte> data, Boolean isFunction, Boolean isNullTerminated, Int32 length)
{
this._isLocal = false;
this._isFunction = isFunction;
this._data = data;

this._isNullTerminated = isNullTerminated;
this._length = length;
}

/// <summary>
/// Helper class for UTF-16 to UTF-8 conversion.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public partial class CString
/// <param name="sequence">A <see cref="CStringSequence"/> instance.</param>
/// <param name="index">The zero-based index of the element into the sequence.</param>
private readonly struct SequenceItemState(CStringSequence sequence, Int32 index)
: IUtf8FunctionState<SequenceItemState>
{
/// <summary>
/// Internal sequence.
Expand All @@ -18,12 +19,22 @@ private readonly struct SequenceItemState(CStringSequence sequence, Int32 index)
/// </summary>
private readonly Int32 _index = index;

[ExcludeFromCodeCoverage]
Boolean IUtf8FunctionState<SequenceItemState>.IsNullTerminated => true;

/// <summary>
/// Retrieves the span from <paramref name="state"/>.
/// </summary>
/// <param name="state">Current item sequence state.</param>
/// <returns>The binary span for the specified state.</returns>
public static ReadOnlySpan<Byte> GetSpan(SequenceItemState state)
=> CStringSequence.GetItemSpan(state._sequence, state._index);

[ExcludeFromCodeCoverage]
#if NET6_0
[RequiresPreviewFeatures]
#endif
static ReadOnlySpan<Byte> IUtf8FunctionState<SequenceItemState>.GetSpan(SequenceItemState state)
=> SequenceItemState.GetSpan(state);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,54 @@ public sealed partial class CStringSequence : ICloneable, IEquatable<CStringSequ
/// from a collection of strings.
/// </summary>
/// <param name="values">The collection of strings.</param>
public CStringSequence(params String?[] values)
{
List<CString?> list = new(values.Length);
this._lengths = new Int32?[values.Length];
for (Int32 i = 0; i < values.Length; i++)
{
CString? cstr = CStringSequence.GetCString(values[i]);
list.Add(cstr);
this._lengths[i] = cstr?.Length;
}
this._cache = CStringSequence.CreateCache(this._lengths);
this._value = CStringSequence.CreateBuffer(list);
}
public CStringSequence(params String?[] values) : this(values.AsSpan()) { }
/// <summary>
/// Initializes a new instance of the <see cref="CStringSequence"/> class from a
/// collection of <see cref="CString"/> instances.
/// </summary>
/// <param name="values">The collection of <see cref="CString"/> instances.</param>
public CStringSequence(params CString?[] values)
public CStringSequence(params CString?[] values) : this(values.AsSpan()) { }
/// <summary>
/// Initializes a new instance of the <see cref="CStringSequence"/> class from a
/// collection of <see cref="CString"/> instances.
/// </summary>
/// <param name="values">The collection of <see cref="CString"/> instances.</param>
public CStringSequence(ReadOnlySpan<CString?> values)
{
this._lengths = CStringSequence.GetLengthArray(values);
this._value = CStringSequence.CreateBuffer(values);
this._cache = CStringSequence.CreateCache(this._lengths);
}
/// <summary>
/// Initializes a new instance of the <see cref="CStringSequence"/> class from an
/// enumerable collection of strings.
/// Initializes a new instance of the <see cref="CStringSequence"/> class
/// from a collection of strings.
/// </summary>
/// <param name="values">The enumerable collection of strings.</param>
public CStringSequence(IEnumerable<String?> values)
/// <param name="values">The collection of strings.</param>
public CStringSequence(ReadOnlySpan<String?> values)
{
List<CString?> list = values.Select(CStringSequence.GetCString).ToList();
this._lengths = CStringSequence.GetLengthArray(list);
this._value = CStringSequence.CreateBuffer(list);
CString?[] list = new CString?[values.Length];
this._lengths = new Int32?[values.Length];
for (Int32 i = 0; i < values.Length; i++)
{
CString? cstr = CStringSequence.GetCString(values[i]);
list[i] = cstr;
this._lengths[i] = cstr?.Length;
}
this._cache = CStringSequence.CreateCache(this._lengths);
this._value = CStringSequence.CreateBuffer(list);
}
/// <summary>
/// Initializes a new instance of the <see cref="CStringSequence"/> class from an
/// enumerable collection of strings.
/// </summary>
/// <param name="values">The enumerable collection of strings.</param>
public CStringSequence(IEnumerable<String?> values) : this(values.Select(CStringSequence.GetCString).ToArray()) { }
/// <summary>
/// Initializes a new instance of the <see cref="CStringSequence"/> class from an
/// enumerable collection of <see cref="CString"/> instances.
/// </summary>
/// <param name="values">The enumerable collection of <see cref="CString"/> instances.</param>
public CStringSequence(IEnumerable<CString?> values) : this(CStringSequence.FromArray(values, out CString?[] arr))
{
this._lengths = CStringSequence.GetLengthArray(arr);
this._value = CStringSequence.CreateBuffer(arr);
this._cache = CStringSequence.CreateCache(this._lengths);
}
public CStringSequence(IEnumerable<CString?> values) : this(values.ToArray()) { }
/// <summary>
/// Creates a copy of this instance of <see cref="CStringSequence"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Rxmxnx.PInvoke;

[SuppressMessage(SuppressMessageConstants.CSharpSquid, SuppressMessageConstants.CheckIdS6640)]
public unsafe partial class CStringSequence
{
/// <summary>
/// State for pinned <see cref="CString"/> span.
/// </summary>
internal readonly struct CStringSpanState
{
#pragma warning disable CS8500
/// <summary>
/// <see cref="CString"/> pointer.
/// </summary>
public CString?* Ptr { get; init; }
/// <summary>
/// Span length.
/// </summary>
public Int32 Length { get; init; }
#pragma warning restore CS8500
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace Rxmxnx.PInvoke;

public partial class CStringSequence
{
/// <summary>
/// State for temporal functional <see cref="CString"/>.
/// </summary>
internal readonly struct CStringStringState(String value) : IUtf8FunctionState<CStringStringState>
{
/// <summary>
/// Internal text value.
/// </summary>
private readonly String _value = value;
/// <summary>
/// Internal text UTF-8 length.
/// </summary>
private readonly Int32 _utf8Length = Encoding.UTF8.GetByteCount(value);

Boolean IUtf8FunctionState<CStringStringState>.IsNullTerminated => false;
#if NET6_0
[RequiresPreviewFeatures]
#endif
static ReadOnlySpan<Byte> IUtf8FunctionState<CStringStringState>.GetSpan(CStringStringState state)
=> Encoding.UTF8.GetBytes(state._value);
#if NET6_0
[RequiresPreviewFeatures]
#endif
static Int32 IUtf8FunctionState<CStringStringState>.GetLength(CStringStringState state) => state._utf8Length;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ public unsafe partial class CStringSequence
/// A <see cref="String"/> instance that contains the binary information of the UTF-8 text sequence.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static String CreateBuffer(IReadOnlyList<CString?> values)
private static String CreateBuffer(ReadOnlySpan<CString?> values)
{
Int32 totalBytes = CStringSequence.GetTotalBytes(values);
Int32 totalChars = totalBytes / CStringSequence.sizeOfChar + totalBytes % CStringSequence.sizeOfChar;
return String.Create(totalChars, values, CStringSequence.CopyText);
#pragma warning disable CS8500
fixed (CString?* valuesPtr = values)
{
CStringSpanState state = new() { Ptr = valuesPtr, Length = values.Length, };
return String.Create(totalChars, state, CStringSequence.CopyText);
}
#pragma warning restore CS8500
}
/// <summary>
/// Creates buffer using <paramref name="ptrSpan"/> and <paramref name="lengths"/>.
Expand Down Expand Up @@ -74,23 +80,24 @@ private static void CreateBuffer(Span<Char> charSpan, SpanCreationInfo info)
}
}
/// <summary>
/// Copies the content of <see cref="CString"/> items from the provided <paramref name="values"/>
/// Copies the content of <see cref="CString"/> items from the provided <paramref name="state"/>
/// to the given <paramref name="charSpan"/>.
/// </summary>
/// <param name="charSpan">
/// The writable <see cref="Char"/> span that is the destination of the copy operation.
/// </param>
/// <param name="values">
/// <param name="state">
/// The collection of <see cref="CString"/> items whose contents are to be copied.
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CopyText(Span<Char> charSpan, IReadOnlyList<CString?> values)
private static void CopyText(Span<Char> charSpan, CStringSpanState state)
{
Int32 position = 0;
ReadOnlySpan<CString?> values =
MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef<CString?>(state.Ptr), state.Length);
Span<Byte> byteSpan = MemoryMarshal.AsBytes(charSpan);
for (Int32 index = 0; index < values.Count; index++)
foreach (CString? value in values)
{
CString? value = values[index];
if (value is null || value.Length == 0) continue;
ReadOnlySpan<Byte> valueSpan = value;
valueSpan.CopyTo(byteSpan[position..]);
Expand Down Expand Up @@ -192,7 +199,7 @@ private static void CreateCStringSequence<TState>(Span<Byte> buffer, SequenceCre
/// </returns>
[return: NotNullIfNotNull(nameof(str))]
private static CString? GetCString(String? str)
=> str is not null ? CString.Create(Encoding.UTF8.GetBytes(str)) : default;
=> str is not null ? CString.Create(new CStringStringState(str)) : default;
/// <summary>
/// Gets the length of the byte span representing a <see cref="CString"/> of the given <paramref name="length"/>.
/// </summary>
Expand All @@ -203,24 +210,13 @@ private static void CreateCStringSequence<TState>(Span<Byte> buffer, SequenceCre
/// </returns>
private static Int32 GetSpanLength(Int32? length) => length is > 0 ? length.Value + 1 : 0;
/// <summary>
/// Creates an array of <see cref="CString"/> values from given enumeration.
/// </summary>
/// <param name="values">Enumeration of <see cref="CString"/> instances.</param>
/// <param name="array">Output. Resulting array as output parameter.</param>
/// <returns>Resulting array.</returns>
private static CString?[] FromArray(IEnumerable<CString?> values, out CString?[] array)
{
array = values.ToArray();
return array;
}
/// <summary>
/// Retrieves the length array for a given collection of UTF-8 texts.
/// </summary>
/// <param name="list">A collection of UTF-8 texts.</param>
/// <returns>An array representing the length of each UTF-8 text in the collection.</returns>
private static Int32?[] GetLengthArray(IReadOnlyList<CString?> list)
private static Int32?[] GetLengthArray(ReadOnlySpan<CString?> list)
{
Int32?[] result = new Int32?[list.Count];
Int32?[] result = new Int32?[list.Length];
for (Int32 i = 0; i < result.Length; i++)
result[i] = CStringSequence.GetLength(list[i]);
return result;
Expand Down Expand Up @@ -386,12 +382,11 @@ private static CStringSequence CreateFrom(String buffer)
/// </summary>
/// <param name="values">The collection of text.</param>
/// <returns>Number of required bytes</returns>
private static Int32 GetTotalBytes(IReadOnlyList<CString?> values)
private static Int32 GetTotalBytes(ReadOnlySpan<CString?> values)
{
Int32 totalBytes = 0;
for (Int32 index = 0; index < values.Count; index++)
foreach (CString? value in values)
{
CString? value = values[index];
if (value is not null && value.Length > 0)
totalBytes += value.Length + 1;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
namespace Rxmxnx.PInvoke;

/// <summary>
/// Interface representing a value state for functional <see cref="CString"/> creation.
/// </summary>
/// <typeparam name="TSelf">Current state type.</typeparam>
public interface IUtf8FunctionState<in TSelf> where TSelf : struct, IUtf8FunctionState<TSelf>
{
/// <summary>
/// Indicates whether resulting UTF-8 text is null-terminated.
/// </summary>
Boolean IsNullTerminated { get; }

/// <summary>
/// Retrieves the span from <paramref name="state"/>.
/// </summary>
/// <param name="state">Current item sequence state.</param>
/// <returns>The binary span for the specified state.</returns>
#if NET6_0
[RequiresPreviewFeatures]
#endif
static abstract ReadOnlySpan<Byte> GetSpan(TSelf state);
/// <summary>
/// Retrieves the span length from <paramref name="state"/>.
/// </summary>
/// <param name="state">Current item sequence state.</param>
/// <returns>The binary span length for the specified state.</returns>
[ExcludeFromCodeCoverage]
#if NET6_0
[RequiresPreviewFeatures]
#endif
static virtual Int32 GetLength(TSelf state) => TSelf.GetSpan(state).Length;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<Using Include="System.Globalization"/>
<Using Include="System.Runtime.CompilerServices"/>
<Using Include="System.Runtime.InteropServices"/>
<Using Include="System.Runtime.Versioning"/>
<Using Include="System.Text"/>

<Using Include="Rxmxnx.PInvoke.Internal"/>
Expand Down
10 changes: 10 additions & 0 deletions src/Intermediate/Rxmxnx.PInvoke.Common.Intermediate/FuncPtr.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,20 @@ public Boolean TryFormat(Span<Char> destination, out Int32 charsWritten, ReadOnl
/// <param name="ptr">A <see cref="IntPtr"/> to explicitly convert.</param>
public static explicit operator FuncPtr<TDelegate>(IntPtr ptr) => new(ptr.ToPointer());
/// <summary>
/// Defines an explicit conversion of a given <see cref="IntPtr"/> to a read-only value pointer.
/// </summary>
/// <param name="ptr">A pointer to explicitly convert.</param>
public static explicit operator FuncPtr<TDelegate>(void* ptr) => new(ptr);
/// <summary>
/// Defines an implicit conversion of a given <see cref="FuncPtr{T}"/> to a pointer.
/// </summary>
/// <param name="valPtr">A <see cref="FuncPtr{T}"/> to implicitly convert.</param>
public static implicit operator IntPtr(FuncPtr<TDelegate> valPtr) => new(valPtr._value);
/// <summary>
/// Defines an implicit conversion of a given <see cref="FuncPtr{T}"/> to a pointer.
/// </summary>
/// <param name="valPtr">A <see cref="FuncPtr{T}"/> to implicitly convert.</param>
public static implicit operator void*(FuncPtr<TDelegate> valPtr) => valPtr._value;

/// <summary>
/// Determines whether two specified instances of <see cref="FuncPtr{T}"/> are equal.
Expand Down
Loading

0 comments on commit 7478b30

Please sign in to comment.