|
// 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.Diagnostics;
using System.Runtime.CompilerServices;
namespace Microsoft.CodeAnalysis.Collections.Internal
{
internal static class SegmentedArrayHelper
{
// This is the threshold where Introspective sort switches to Insertion sort.
// Empirically, 16 seems to speed up most cases without slowing down others, at least for integers.
// Large value types may benefit from a smaller number.
internal const int IntrosortSizeThreshold = 16;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetSegmentSize<T>()
{
return Unsafe.SizeOf<T>() switch
{
// Hard code common values since not all versions of the .NET JIT support reducing this computation to a
// constant value at runtime. Values are validated against the reference implementation in
// CalculateSegmentSize in unit tests.
1 => 65536,
2 => 32768,
4 => 16384,
8 => 8192,
12 => 4096,
16 => 4096,
24 => 2048,
28 => 2048,
32 => 2048,
40 => 2048,
64 => 1024,
#if NETCOREAPP3_0_OR_GREATER
_ => InlineCalculateSegmentSize(Unsafe.SizeOf<T>()),
#else
_ => FallbackSegmentHelper<T>.SegmentSize,
#endif
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetSegmentShift<T>()
{
return Unsafe.SizeOf<T>() switch
{
// Hard code common values since not all versions of the .NET JIT support reducing this computation to a
// constant value at runtime. Values are validated against the reference implementation in
// CalculateSegmentSize in unit tests.
1 => 16,
2 => 15,
4 => 14,
8 => 13,
12 => 12,
16 => 12,
24 => 11,
28 => 11,
32 => 11,
40 => 11,
64 => 10,
#if NETCOREAPP3_0_OR_GREATER
_ => InlineCalculateSegmentShift(Unsafe.SizeOf<T>()),
#else
_ => FallbackSegmentHelper<T>.SegmentShift,
#endif
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetOffsetMask<T>()
{
return Unsafe.SizeOf<T>() switch
{
// Hard code common values since not all versions of the .NET JIT support reducing this computation to a
// constant value at runtime. Values are validated against the reference implementation in
// CalculateSegmentSize in unit tests.
1 => 65535,
2 => 32767,
4 => 16383,
8 => 8191,
12 => 4095,
16 => 4095,
24 => 2047,
28 => 2047,
32 => 2047,
40 => 2047,
64 => 1023,
#if NETCOREAPP3_0_OR_GREATER
_ => InlineCalculateOffsetMask(Unsafe.SizeOf<T>()),
#else
_ => FallbackSegmentHelper<T>.OffsetMask,
#endif
};
}
/// <summary>
/// Calculates the maximum number of elements of size <paramref name="elementSize"/> which can fit into an array
/// which has the following characteristics:
/// <list type="bullet">
/// <item><description>The array can be allocated in the small object heap.</description></item>
/// <item><description>The array length is a power of 2.</description></item>
/// </list>
/// </summary>
/// <param name="elementSize">The size of the elements in the array.</param>
/// <returns>The segment size to use for small object heap segmented arrays.</returns>
private static int CalculateSegmentSize(int elementSize)
{
// Default Large Object Heap size threshold
// https://github.com/dotnet/runtime/blob/c9d69e38d0e54bea5d188593ef6c3b30139f3ab1/src/coreclr/src/gc/gc.h#L111
const int Threshold = 85000;
var segmentSize = 2;
while (ArraySize(elementSize, segmentSize << 1) < Threshold)
{
segmentSize <<= 1;
}
return segmentSize;
static int ArraySize(int elementSize, int segmentSize)
{
// Array object header, plus space for the elements
return (2 * IntPtr.Size + 8) + (elementSize * segmentSize);
}
}
/// <summary>
/// Calculates a shift which can be applied to an absolute index to get the page index within a segmented array.
/// </summary>
/// <param name="segmentSize">The number of elements in each page of the segmented array. Must be a power of 2.</param>
/// <returns>The shift to apply to the absolute index to get the page index within a segmented array.</returns>
private static int CalculateSegmentShift(int segmentSize)
{
var segmentShift = 0;
while (0 != (segmentSize >>= 1))
{
segmentShift++;
}
return segmentShift;
}
/// <summary>
/// Calculates a mask, which can be applied to an absolute index to get the index within a page of a segmented
/// array.
/// </summary>
/// <param name="segmentSize">The number of elements in each page of the segmented array. Must be a power of 2.</param>
/// <returns>The bit mask to obtain the index within a page from an absolute index within a segmented array.</returns>
private static int CalculateOffsetMask(int segmentSize)
{
Debug.Assert(segmentSize == 1 || (segmentSize & (segmentSize - 1)) == 0, "Expected size of 1, or a power of 2");
return segmentSize - 1;
}
// Faster inline implementation for NETCOREAPP to avoid static constructors and non-inlineable
// generics with runtime lookups
#if NETCOREAPP3_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineCalculateSegmentSize(int elementSize)
{
return 1 << InlineCalculateSegmentShift(elementSize);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineCalculateSegmentShift(int elementSize)
{
// Default Large Object Heap size threshold
// https://github.com/dotnet/runtime/blob/c9d69e38d0e54bea5d188593ef6c3b30139f3ab1/src/coreclr/src/gc/gc.h#L111
const uint Threshold = 85000;
return System.Numerics.BitOperations.Log2((uint)((Threshold / elementSize) - (2 * Unsafe.SizeOf<object>() + 8)));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineCalculateOffsetMask(int elementSize)
{
return InlineCalculateSegmentSize(elementSize) - 1;
}
#endif
internal static class TestAccessor
{
public static int CalculateSegmentSize(int elementSize)
=> SegmentedArrayHelper.CalculateSegmentSize(elementSize);
public static int CalculateSegmentShift(int segmentSize)
=> SegmentedArrayHelper.CalculateSegmentShift(segmentSize);
public static int CalculateOffsetMask(int segmentSize)
=> SegmentedArrayHelper.CalculateOffsetMask(segmentSize);
}
#if !NETCOREAPP3_0_OR_GREATER
private static class FallbackSegmentHelper<T>
{
public static readonly int SegmentSize = CalculateSegmentSize(Unsafe.SizeOf<T>());
public static readonly int SegmentShift = CalculateSegmentShift(SegmentSize);
public static readonly int OffsetMask = CalculateOffsetMask(SegmentSize);
}
#endif
}
}
|