Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ArrayPoolBufferWriter for Utf8JsonWriter for marshalling #3579

Open
wants to merge 3 commits into
base: petesong/stj-deserialization-2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#>
using System.Text.Json;
using System.Buffers;
using ThirdParty.RuntimeBackports;
#pragma warning disable CS0612,CS0618
namespace <#=this.Config.Namespace #>.Model.Internal.MarshallTransformations
{
Expand Down Expand Up @@ -93,9 +94,9 @@ namespace <#=this.Config.Namespace #>.Model.Internal.MarshallTransformations
if (this.Operation.RequestHasBodyMembers || shouldMarshallPayload)
{
#>
#if NETCOREAPP3_1_OR_GREATER
ArrayBufferWriter<byte> arrayBufferWriter = new ArrayBufferWriter<byte>();
using Utf8JsonWriter writer = new Utf8JsonWriter(arrayBufferWriter);
#if !NETFRAMEWORK
using ArrayPoolBufferWriter<byte> arrayPoolBufferWriter = new ArrayPoolBufferWriter<byte>();
using Utf8JsonWriter writer = new Utf8JsonWriter(arrayPoolBufferWriter);
#else
using var memoryStream = new MemoryStream();
using Utf8JsonWriter writer = new Utf8JsonWriter(memoryStream);
Expand Down Expand Up @@ -124,8 +125,9 @@ namespace <#=this.Config.Namespace #>.Model.Internal.MarshallTransformations
<#
}
#>
#if NETCOREAPP3_1_OR_GREATER
request.Content = arrayBufferWriter.WrittenMemory.ToArray();
// ToArray() must be called here because aspects of sigv4 signing require a byte array
#if !NETFRAMEWORK
request.Content = arrayPoolBufferWriter.WrittenMemory.ToArray();
#else
request.Content = memoryStream.ToArray();
#endif
Expand Down
100 changes: 100 additions & 0 deletions sdk/src/Core/ThirdParty/RuntimeBackports/ArrayPoolExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Taken from https://raw.githubusercontent.com/CommunityToolkit/dotnet/refs/heads/main/src/CommunityToolkit.HighPerformance/Extensions/ArrayPoolExtensions.cs
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
#nullable enable
namespace ThirdParty.RuntimeBackports
{
/// <summary>
/// Helpers for working with the <see cref="ArrayPool{T}"/> type.
/// </summary>
public static class ArrayPoolExtensions
{
/// <summary>
/// Changes the number of elements of a rented one-dimensional array to the specified new size.
/// </summary>
/// <typeparam name="T">The type of items into the target array to resize.</typeparam>
/// <param name="pool">The target <see cref="ArrayPool{T}"/> instance to use to resize the array.</param>
/// <param name="array">The rented <typeparamref name="T"/> array to resize, or <see langword="null"/> to create a new array.</param>
/// <param name="newSize">The size of the new array.</param>
/// <param name="clearArray">Indicates whether the contents of the array should be cleared before reuse.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="newSize"/> is less than 0.</exception>
/// <remarks>When this method returns, the caller must not use any references to the old array anymore.</remarks>
public static void Resize<T>(this ArrayPool<T> pool, ref T[]? array, int newSize, bool clearArray = false)
{
// If the old array is null, just create a new one with the requested size
if (array is null)
{
array = pool.Rent(newSize);

return;
}

// If the new size is the same as the current size, do nothing
if (array.Length == newSize)
{
return;
}

// Rent a new array with the specified size, and copy as many items from the current array
// as possible to the new array. This mirrors the behavior of the Array.Resize API from
// the BCL: if the new size is greater than the length of the current array, copy all the
// items from the original array into the new one. Otherwise, copy as many items as possible,
// until the new array is completely filled, and ignore the remaining items in the first array.
T[] newArray = pool.Rent(newSize);
int itemsToCopy = Math.Min(array.Length, newSize);

Array.Copy(array, 0, newArray, 0, itemsToCopy);

pool.Return(array, clearArray);

array = newArray;
}

/// <summary>
/// Ensures that when the method returns <paramref name="array"/> is not null and is at least <paramref name="capacity"/> in length.
/// Contents of <paramref name="array"/> are not copied if a new array is rented.
/// </summary>
/// <typeparam name="T">The type of items into the target array given as input.</typeparam>
/// <param name="pool">The target <see cref="ArrayPool{T}"/> instance used to rent and/or return the array.</param>
/// <param name="array">The rented <typeparamref name="T"/> array to ensure capacity for, or <see langword="null"/> to rent a new array.</param>
/// <param name="capacity">The minimum length of <paramref name="array"/> when the method returns.</param>
/// <param name="clearArray">Indicates whether the contents of the array should be cleared if returned to the pool.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="capacity"/> is less than 0.</exception>
/// <remarks>When this method returns, the caller must not use any references to the old array anymore.</remarks>
public static void EnsureCapacity<T>(this ArrayPool<T> pool, ref T[]? array, int capacity, bool clearArray = false)
{
if (capacity < 0)
{
ThrowArgumentOutOfRangeExceptionForNegativeArrayCapacity();
}

if (array is null)
{
array = pool.Rent(capacity);
}
else if (array.Length < capacity)
{
// Ensure rent succeeds before returning the original array to the pool
T[] newArray = pool.Rent(capacity);

pool.Return(array, clearArray);

array = newArray;
}
}

/// <summary>
/// Throws an <see cref="ArgumentOutOfRangeException"/> when the "capacity" parameter is negative.
/// </summary>
private static void ThrowArgumentOutOfRangeExceptionForNegativeArrayCapacity()
{
throw new ArgumentOutOfRangeException("capacity", "The array capacity must be a positive number.");
}
}
}

37 changes: 37 additions & 0 deletions sdk/src/Core/ThirdParty/RuntimeBackports/BitOperations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Taken from https://raw.githubusercontent.com/CommunityToolkit/dotnet/refs/heads/main/src/CommunityToolkit.HighPerformance/Helpers/Internals/BitOperations.cs
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;

namespace ThirdParty.RuntimeBackports
{
internal static class BitOperations
{
/// <summary>
/// Round the given integral value up to a power of 2.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>
/// The smallest power of 2 which is greater than or equal to <paramref name="value"/>.
/// If <paramref name="value"/> is 0 or the result overflows, returns 0.
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe uint RoundUpToPowerOf2(uint value)
{
// Based on https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
--value;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;

return value + 1;
}
}
}
Loading
Loading