Почему TypedReference негласно? Это так быстро и безопасно ... почти волшебно! - PullRequest
122 голосов
/ 22 января 2011

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

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

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


Другое использование, которое я нашел для TypedReference:

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

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)); }
    }
}

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

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
    }
}

(Редактировать: я редактировал тест выше, так как в последней версии поста использовалась отладочная версия кода [я забыл изменитьего выпустить], и не оказывайте давления на ГХ. Эта версия немного более реалистична, а в моей системе она более чем в три раза быстрее, в среднем TypedReference.)

Ответы [ 3 ]

41 голосов
/ 23 января 2011

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

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

Будучи определенным в библиотеке Vararg, становится ясно, что они в первую очередь предназначены для поддержки списков аргументов переменной длины, а не для чего-либо еще.Списки переменных-аргументов мало используются в платформах, которым не нужно взаимодействовать с внешним кодом 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 сам является частью библиотеки Reflection и не входит в профиль ядра CLI.)

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

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

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

Ну, я не Эрик Липперт, поэтому я не могу напрямую говорить о мотивации 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);
}

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

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

3 голосов
/ 26 ноября 2017

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

Печальные подробности: здесь , но к счастью, все это сейчас не имеет значения ...

Этот вопрос теперь не имеет значения для новых ref locals и ref return функций в C # 7

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

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

И, очевидно, кончина TypedReference - или, по крайней мере, почти полное ее обесценивание - означает также выбросить __makeref в мусорную корзину.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...