Есть ли способ привести общее значение перечисления к значению UInt64 без выделения? - PullRequest
0 голосов
/ 26 декабря 2018

Я хочу иметь быстрый общий способ проверки, является ли допустимым значение перечисления, помеченное FlagsAttribute.Для этого я создал класс с именем EnumInfo<T>, который вычисляет шаблон флагов в его статическом конструкторе путем побитового ИЛИ всех различных значений перечисления.Затем метод IsValidFlagsValue просто проверяет предоставленное значение, выполняя побитовое И сравнивая с шаблоном флагов.

Рассмотрим следующий код C #

// This code requires C# 7.3
public static class EnumInfo<T> where T : Enum, IConvertible
{
    // This field is calculated in the static constructor
    public static readonly ulong FlagsPattern; 

    public static bool IsValidFlagsValue(this T enumValue)
    {
        // The actual problem is here: ToUInt64 allocates (because of internal boxing?)
        var convertedUInt64Value = enumValue.ToUInt64(null);
        return (FlagsPattern & convertedUInt64Value) == convertedUInt64Value;
    }
}

Моя настоящая проблема заключается в следующем : для повторного использования этого кода я преобразовываю значения перечисления в тип ulong.Однако enumValue.ToUInt64(null) выделяет внутренне 24 байта, как видно из следующего теста (выполняется с помощью BenchmarkDotNet ):

public class ToUInt64Benchmark
{
    public IConvertible FlagsValue = 
        BindingFlags.Public | BindingFlags.Instance;

    [Benchmark]
    public ulong EnumToUInt64() => FlagsValue.ToUInt64(null);
}

BenchmarkDotNet results

Есть ли какой-либо способ избежать этого распределения? Я пытался использовать небезопасный код, но не могу получить адрес общего значения (то есть &enumValue не работает).Может быть, есть другой способ сделать это, о котором я не думал?

Спасибо за вашу помощь заранее.

Ответы [ 2 ]

0 голосов
/ 26 декабря 2018

Как указано pinkfloydx33 в комментариях к этим вопросам, в пакете System.Runtime.CompilerServices.Unsafe NuGet есть класс Unsafe.С его помощью вы можете бросить на ulong небезопасным способом, который не выделяет.Если мы изменим эталонный класс из моего вопроса следующим образом:

public class ToUInt64Benchmark
{
    public BindingFlags FlagsValue = BindingFlags.Public | BindingFlags.Instance;

    public IConvertible FlagsValueAsConvertible;

    public ToUInt64Benchmark() => FlagsValueAsConvertible = FlagsValue;

    [Benchmark(Baseline = true)]
    public ulong EnumToUInt64() => FlagsValueAsConvertible.ToUInt64(null);

    [Benchmark]
    public ulong UnsafeEnumToUInt64() => ConvertUnsafe(FlagsValue);

    private static unsafe ulong ConvertUnsafe<T>(T enumValue) where T : struct, Enum
    {
        var pointer = (ulong*)Unsafe.AsPointer(ref enumValue);
        return *pointer;
    }
}

... это приведет к следующим результатам:

Benchmark Results with unsafe code

Это намного быстрее (только 10% времени выполнения ToUInt64 в .NET Core 2.2 на моем Surface Pro 4) и, самое главное, он не выделяется.

Пожалуйста, обязательнодобавьте тег AllowUnsafeBlocks в файл csproj, чтобы разрешить компиляцию небезопасного кода.Вы не можете запустить этот код на частично доверенных платформах (например, Silverlight).Вот структура моего файла csproj.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>netcoreapp2.2;net472</TargetFrameworks>
    <DebugType>portable</DebugType>
    <PlatformTarget>x64</PlatformTarget>
    <LangVersion>7.3</LangVersion>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.11.3" />
    <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
  </ItemGroup>
</Project>

Обновление 2018-12-26 17:00 UTC

Как pinkfloydx33 правильно указано в комментариях (один разснова), код можно упростить с помощью Unsafe.As:

public class ToUInt64Benchmark
{
    // Other members omitted for brevity's sake
    [Benchmark]
    public ulong UnsafeEnumToUInt64() => ConvertUnsafe(FlagsValue);

    private static ulong ConvertUnsafe<T>(T enumValue) where T : struct, Enum => 
        Unsafe.As<T, ulong>(ref enumValue);
}

Это не влияет на производительность, но для него не требуется тег AllowUnsafeBlocks в файле csproj.

0 голосов
/ 26 декабря 2018

Нельзя избежать бокса и использовать интерфейсы одновременно.Бокс происходит потому, что вы вызываете метод интерфейса на вашем enumValue.

Если вы отчаянно пытаетесь избежать дополнительного выделения - забудьте об интерфейсах.Высокая производительность - не близкий друг с идеальным дизайном OO.

...