Почему TypedReference за кулисами? Это так быстро и безопасно ... почти волшебно!

StackOverflow https://stackoverflow.com/questions/4764573

  •  16-10-2019
  •  | 
  •  

Вопрос

Предупреждение: этот вопрос немного еретический ... религиозные программисты, всегда соблюдающие хорошие практики, пожалуйста, не читайте его. :)

Кто -нибудь знает, почему использование TypedReference так обескуражен (неявно, из -за отсутствия документации)?

Я нашел для этого отличного применения, например, при прохождении общих параметров через функции, которые не должны быть общими (при использовании object может быть излишним или медленным, если вам нужен тип значения), поскольку, когда вам нужен непрозрачный указатель, или когда вам нужно быстро получить доступ к элементу массива, чьи характеристики вы найдете во время выполнения (используя Array.InternalGetReference) Поскольку CLR даже не допускает неправильного использования этого типа, почему он обескуражен? Это не кажется небезопасным или что -то в этом роде ...


Другие применения, которые я нашел TypedReference:

«Специализирующиеся» дженерики в C# (это тип-безопасность):

static void foo<T>(ref T value)
{
    //This is the ONLY way to treat value as int, without boxing/unboxing objects
    if (value is int)
    { __refvalue(__makeref(value), int) = 1; }
    else { value = default(T); }
}

Написание кода, который работает с общими указателями (это очень небезопасно, если он используется неправильно, но быстро и безопасно, если правильно используется):

//This bypasses the restriction that you can't have a pointer to T,
//letting you write very high-performance generic code.
//It's dangerous if you don't know what you're doing, but very worth if you do.
static T Read<T>(IntPtr address)
{
    var obj = default(T);
    var tr = __makeref(obj);

    //This is equivalent to shooting yourself in the foot
    //but it's the only high-perf solution in some cases
    //it sets the first field of the TypedReference (which is a pointer)
    //to the address you give it, then it dereferences the value.
    //Better be 10000% sure that your type T is unmanaged/blittable...
    unsafe { *(IntPtr*)(&tr) = address; }

    return __refvalue(tr, T);
}

Написание а метод версия sizeof инструкция, которая может быть иногда полезна:

static class ArrayOfTwoElements<T> { static readonly Value = new T[2]; }

static uint SizeOf<T>()
{
    unsafe 
    {
        TypedReference
            elem1 = __makeref(ArrayOfTwoElements<T>.Value[0] ),
            elem2 = __makeref(ArrayOfTwoElements<T>.Value[1] );
        unsafe
        { return (uint)((byte*)*(IntPtr*)(&elem2) - (byte*)*(IntPtr*)(&elem1)); }
    }
}

Написание метода, который проходит параметр «состояния», который хочет избежать бокса:

static void call(Action<int, TypedReference> action, TypedReference state)
{
    //Note: I could've said "object" instead of "TypedReference",
    //but if I had, then the user would've had to box any value types
    try
    {
        action(0, state);
    }
    finally { /*Do any cleanup needed*/ }
}

Так почему же использование такого «обескуражено» (из -за отсутствия документации)? Какие -либо конкретные соображения безопасности? Это кажется совершенно безопасным и проверенным, если он не смешивается с указателями (которые в любом случае небезопасны или не поддаются проверке) ...


Обновлять:

Пример кода, чтобы показать это, действительно, TypedReference может быть вдвое быстрее (или больше):

using System;
using System.Collections.Generic;
static class Program
{
    static void Set1<T>(T[] a, int i, int v)
    { __refvalue(__makeref(a[i]), int) = v; }

    static void Set2<T>(T[] a, int i, int v)
    { a[i] = (T)(object)v; }

    static void Main(string[] args)
    {
        var root = new List<object>();
        var rand = new Random();
        for (int i = 0; i < 1024; i++)
        { root.Add(new byte[rand.Next(1024 * 64)]); }
        //The above code is to put just a bit of pressure on the GC

        var arr = new int[5];
        int start;
        const int COUNT = 40000000;

        start = Environment.TickCount;
        for (int i = 0; i < COUNT; i++)
        { Set1(arr, 0, i); }
        Console.WriteLine("Using TypedReference:  {0} ticks",
                          Environment.TickCount - start);
        start = Environment.TickCount;
        for (int i = 0; i < COUNT; i++)
        { Set2(arr, 0, i); }
        Console.WriteLine("Using boxing/unboxing: {0} ticks",
                          Environment.TickCount - start);

        //Output Using TypedReference:  156 ticks
        //Output Using boxing/unboxing: 484 ticks
    }
}

(РЕДАКТИРОВАТЬ: Я отредактировал эталон выше, так как в последней версии поста использовалась отладочная версия кода [я забыл изменить его на выпуск] и не оказывал давления на GC. Эта версия немного более реалистична, и В моей системе это более чем в три раза быстрее с TypedReference в среднем.)

Это было полезно?

Решение

Короткий ответ: портативность.

Пока __arglist, __makeref, а также __refvalue находятся Языковые расширения и недокументированы в спецификации языка C#, конструкции, используемые для их реализации под капюшоном (vararg вызововая конвенция, TypedReference тип, arglist, refanytype, mkanyref, а также refanyval инструкции) идеально задокументированы в Спецификация CLI (ECMA-335) в Библиотека Варарга.

Определение в библиотеке Варарга совершенно ясно дает понять, что они в первую очередь предназначены для поддержки списков аргументов переменной длины и ничего другого. Списки с переменным аргументом мало используются в платформах, которым не нужно взаимодействовать с внешним C-кодом, который использует Varargs. По этой причине библиотека Varargs не является частью какого -либо профиля CLI. Законные реализации CLI могут не поддерживать библиотеку Varargs, поскольку она не включена в профиль ядра CLI:

4.1.6 Vararg

А Набор функций Vararg Поддерживает списки аргументов переменной длины и указатели типа времени.

Если опущено: Любая попытка ссылаться на метод с vararg вызов соглашения или подписи кодировки, связанные с методами Vararg (см. Раздел II), должны бросить System.NotImplementedException исключение. Методы с использованием инструкций CIL arglist, refanytype, mkrefany, а также refanyval должен бросить System.NotImplementedException исключение. Точное время исключения не указано. Тип System.TypedReference не нужно определять.

Обновление (ответь на GetValueDirect комментарий):

FieldInfo.GetValueDirect находятся FieldInfo.SetValueDirect находятся нет Часть библиотеки базового класса. Обратите внимание, что есть разница между библиотекой классов .NET Framework и библиотекой базовых классов. BCL - единственная вещь, необходимая для соответствующей реализации CLI/C# и задокументирована в ECMA TR/84. Анкет (Фактически, FieldInfo Сама является частью библиотеки отражения, и это также не включено в профиль ядра CLI).

Как только вы используете метод за пределами BCL, вы отказываетесь от небольшого портативности (и это становится все более важным с появлением реализаций CLI без .NET, таких как Silverlight и Monotouch). Даже если реализация хотела увеличить совместимость с библиотекой классов Microsoft .net Framework, она может просто предоставить GetValueDirect а также SetValueDirect принимая TypedReference не делая TypedReference Специально обрабатывается во время выполнения (в основном, что делает их эквивалентными своим object коллеги без выгоды от производительности).

Если бы они задокументировали это в C#, это имело бы хотя бы пару последствий:

  1. Как любая функция, это май Станьте препятствием для новых функций, тем более что этот на самом деле не вписывается в дизайн C# и требует странных синтаксических расширений и специальной передачи типа во время выполнения.
  2. Все реализации C# должны каким -то образом реализовать эту функцию, и это не обязательно тривиально/возможно для реализаций C#, которые вообще не работают поверх CLI или работают поверх CLI без Varargs.

Другие советы

Что ж, я не Эрик Липперт, поэтому я не могу говорить напрямую о мотивации Microsoft, но если бы я рискнул предположить, я бы сказал, что TypedReference и другие. плохо документировано, потому что, честно говоря, они вам не нужны.

Каждое использование, которое вы упомянули для этих функций, может быть выполнено без них, хотя в некоторых случаях в результате штрафа за производительность. Но C# (и .net в целом) не предназначен для высокопроизводительного языка. (Я предполагаю, что «быстрее Java» была целью производительности.)

Это не значит, что определенные соображения производительности не были предоставлены. Действительно, такие особенности, как указатели, stackalloc, и определенные оптимизированные фондовые функции существуют в значительной степени для повышения производительности в определенных ситуациях.

Дженерики, которые я бы сказал начальный Преимущество безопасности типа, также повышает производительность аналогично TypedReference Избегая бокса и распаковки. На самом деле, мне было интересно, почему вы предпочитаете это:

static void call(Action<int, TypedReference> action, TypedReference state){
    action(0, state);
}

к этому:

static void call<T>(Action<int, T> action, T state){
    action(0, state);
}

Компромисс, как я вижу, заключаются в том, что первое требует меньше джитов (и, следует, меньше памяти), в то время как последнее более знакомо, и, я бы предположил, немного быстрее (избегая указателя).

Я бы позвонил TypedReference и подробности реализации друзей. Вы указали на некоторые аккуратные применения для них, и я думаю, что их стоит изучить, но применяется обычное предостережение по рассмотрению деталей реализации - следующая версия может сломать ваш код.

Я не могу понять, должен ли название этого вопроса саркастичным: он был давно установлен что TypedReference это медленный, раздутый, уродливый двоюродный брат «истинных» управляемых указателей, последний из которых мы получаем C ++/CLI interior_ptr<T>, или даже традиционная ссылка (ref/out) параметры в C#Анкет На самом деле, это довольно сложно сделать TypedReference Даже достичь базовой производительности простого использования целого числа для повторного индекса с исходного массива CLR каждый раз.

Печальные детали здесь, но К счастью, сейчас ничего не имеет значения ...

Этот вопрос теперь представлен новым Ссылка местных жителей а также ref return Особенности в C# 7

Эти новые языковые функции обеспечивают выдающуюся первоклассную поддержку в C# для объявления, обмена и манипулирования истинными CLR Управляемый тип ссылки-types в тщательно предварительному ситуациям.

Ограничения на использование не более строгие, чем ранее требовалось TypedReference (И выступление буквально прыгать от худшего к лучшему), поэтому я не вижу оставшегося возможного использования в C# за TypedReference. Анкет Например, ранее не было никакого способа сохранить TypedReference в GC Куча, так что то же самое относится и к превосходным управляемым указателям, теперь не является вынос.

И, очевидно, гибель TypedReference- ИЛИ, по крайней мере, почти полная снижение - __makeref На Junkheap также.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top