Какой самый быстрый способ преобразования типа «время компиляции»? - PullRequest
3 голосов
/ 21 января 2012

Я знаю, что название немного расплывчато. Но я пытаюсь добиться чего-то вроде этого:

Внутри абстрактного класса:

public abstract bool TryGet<T>(string input, out T output) where T : struct;

Внутри класса с такой подписью:

private class Param<T> : AbstractParam where T : struct

Эта реализация:

public override bool TryGetVal<TOriginal>(string input, out TOriginal output)
{
    T oTemp;
    bool res = _func(input, out oTemp); // _func is the actual function
                                        // that retrieves the value.
    output = (TOriginal)oTemp; // Compile-time error
    return res;
}

И TOriginal всегда будет того же типа, что и T. Это позволит обойти ошибку времени компиляции, но я не хочу делать эту причину снижения производительности:

output = (TOriginal)(object)oTemp;

Если бы это были ссылочные типы, это обеспечило бы решение:

output = oTemp as TOriginal;

Отражение / динамика также решит проблему, но это снижение производительности еще больше:

output = (TOriginal)(dynamic)oTemp;

Я попытался использовать небезопасный код, но безуспешно, но это может быть только я.

Поэтому я надеюсь, что компилятор оптимизирует (TOriginal)(object)oTemp до (TOriginal)oTemp, чего я не знаю. Или что есть небезопасный подход к этому.

Спасите меня лекция о преждевременной оптимизации, я хочу знать это исключительно для исследований, и мне интересно, есть ли способ обойти это ограничение. Я понимаю, что это окажет незначительное влияние на фактическую производительность.

Окончательный вывод:
После разборки ситуации это были результаты:

                return (TOut)(object)_value;
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        eax 
00000004  mov         dword ptr [ebp-4],ecx 
00000007  cmp         dword ptr ds:[003314CCh],0 
0000000e  je          00000015 
00000010  call        61A33AD3 
00000015  mov         eax,dword ptr [ebp-4] 
00000018  mov         eax,dword ptr [eax+4] 
0000001b  mov         esp,ebp 
0000001d  pop         ebp 
0000001e  ret 

                return _value;
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        eax 
00000004  mov         dword ptr [ebp-4],ecx 
00000007  cmp         dword ptr ds:[004814B4h],0 
0000000e  je          00000015 
00000010  call        61993AA3 
00000015  mov         eax,dword ptr [ebp-4] 
00000018  mov         eax,dword ptr [eax+4] 
0000001b  mov         esp,ebp 
0000001d  pop         ebp 
0000001e  ret 

Оказывается, сегодняшний компилятор оптимизирует это, и, следовательно, производительность не снижается.

output = (TOriginal)(object)oTemp;

Это самый оптимизированный способ сделать это:).

Спасибо Эрик Липперт и Бен Фойгт .

Примечание по ссылочным типам:
При удалении ограничения struct и передаче ссылочного типа (в моем случае string) эта оптимизация выполняется NOT .

Результат:

                return (TOut)(object)_value;
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  sub         esp,10h 
00000006  mov         dword ptr [ebp-4],edx 
00000009  mov         dword ptr [ebp-10h],ecx 
0000000c  mov         dword ptr [ebp-8],edx 
0000000f  cmp         dword ptr ds:[003314B4h],0 
00000016  je          0000001D 
00000018  call        61A63A43 
0000001d  mov         eax,dword ptr [ebp-8] 
00000020  mov         eax,dword ptr [eax+0Ch] 
00000023  mov         eax,dword ptr [eax] 
00000025  mov         dword ptr [ebp-0Ch],eax 
00000028  test        dword ptr [ebp-0Ch],1 
0000002f  jne         00000036 
00000031  mov         ecx,dword ptr [ebp-0Ch] 
00000034  jmp         0000003C 
00000036  mov         eax,dword ptr [ebp-0Ch] 
00000039  mov         ecx,dword ptr [eax-1] 
0000003c  mov         eax,dword ptr [ebp-10h] 
0000003f  mov         edx,dword ptr [eax+4] 
00000042  call        617D79D8 
00000047  mov         esp,ebp 
00000049  pop         ebp 
0000004a  ret 

                return _value;
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        eax 
00000004  mov         dword ptr [ebp-4],ecx 
00000007  cmp         dword ptr ds:[003314B4h],0 
0000000e  je          00000015 
00000010  call        61A639E3 
00000015  mov         eax,dword ptr [ebp-4] 
00000018  mov         eax,dword ptr [eax+4] 
0000001b  mov         esp,ebp 
0000001d  pop         ebp 
0000001e  ret 

Если вам нужен дешевый способ приведения к «без надлежащей проверки типа», оператор as является вашим решением.

Ответы [ 3 ]

6 голосов
/ 21 января 2012

Я собираюсь дать вам ту лекцию, которую вы не хотели, потому что вы явно не понимаете ее.

Есть две причины для «Измерить, измерить, измерить!»(или эквивалентно «Профиль, Профиль, Профиль!») подход к оптимизации:

  1. Прикладывать усилия там, где это оказывает наибольшее влияние.Вот где появляется термин «преждевременная оптимизация».

    Иногда эта причина не применима (если вы хотите узнать теорию / по академическим соображениям).

  2. Чтобы выяснить, какая реализация на самом деле работает быстрее.

Современные процессоры - сложные звери, до такой степени, что даже сравнение двух разных последовательностей машинного кода не может показать, что лучше из-затонкости поведения кэша, зависимости данных конвейера, микрокод и т. д. И вы работаете на два уровня выше этого (код C # -> MSIL -> машинный код).Невозможно сказать, какие оптимизации произойдут без измерения.

Вы сказали:

Это позволит обойти ошибку времени компиляции, но я не хочу этого делатьпричина падения производительности:

output = (TOriginal)(object)oTemp;

Но я не думаю, что на самом деле там есть ЛЮБОЙ удар по производительности.Общие методы JITted для каждого типа значения, этот процесс должен полностью устранить любое воображаемое снижение производительности.Но вы можете продемонстрировать, что на самом деле есть издержки с помощью данных о производительности (реальных измерений профилировщика) или, по крайней мере, разборки машинного кода, сгенерированного JIT.


В этом конкретном случаеВ этом случае неясно, почему у вас есть два разных параметра универсального типа для начала, если они всегда того же типа, что и вы.Просто избавьтесь от TOriginal и используйте T для типа выходного параметра.

4 голосов
/ 21 января 2012

Я нахожу ироничным то, что ответ Бена гласит, что «используйте науку: измерьте ее, чтобы узнать», и «вот мое убеждение в том, что действительно происходит»:ЛЮБАЯ производительность хит там.Общие методы JITted для каждого типа значения, этот процесс должен полностью устранить любое воображаемое снижение производительности.

Исходя из фактической разборки, показанной в обновленном вопросе, оригинальный постер утверждает, что используемый джиттер, по-видимому, выполняет эту оптимизацию по крайней мере некоторое время.Я не проанализировал это утверждение, чтобы убедиться, что оно правильное;Я бы хотел увидеть реальный скомпилированный код, IL и сгенерированную сборку, чтобы понять, что здесь происходит.

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

Если некоторые из них были устранены, тогда я с удовольствием узнаю об этом.

Поэтому вы не можете сделать вывод prima facie , что джиттер выполняет или не выполняет эту оптимизациюустранения бокса.Я видел случаи, когда это не так;у нас есть непроверенное утверждение, что в некоторых случаях это действительно так.

Бен продолжает давать несколько полезных советов:

Но вы можете продемонстрировать, что на самом деле это ценас помощью данных о производительности (реальных измерений профилировщика) или, по крайней мере, разборки машинного кода, сгенерированного JIT.

Действительно, я настоятельно рекомендую вам это сделать и при наличии более одного дрожания.

Давайте начнем сначала и ответим на фактически заданные вопросы.Мы должны начать с упрощения и прояснения описанного случая:

abstract class B
{
    public abstract T M<T>() where T : struct; 
}

private class D<U> : B where U : struct
{
    public override V M<V>() 
    {
        U u = default(U);
        return (V)u; // compile-time error
    } 
}

Оригинальный плакат гласит, что V всегда будет таким же, как U. Есть первая проблема.Зачем?Ничто не мешает пользователю вызвать M<bool> в экземпляре D<double>.Средство проверки типов совершенно правильно, отметив, что преобразование из U в V. может не произойти.

Как отмечает оригинальный автор, можно выполнить окончательный обход средства проверки типов, выполнив упаковку и распаковав код:

        return (V)(object)u; // Runtime error, not compile-time error

Тогда возникает вопрос: «В случае, если это не дает сбой и ужасно умирает во время выполнения, устраняется ли штраф в боксе дрожанием?»

Джиттер джитит метод только один раз и разделяет код для аргументов ссылочного типа, но каждый раз повторно джитит его для различных параметров типа значения.Следовательно, существует возможность отменить штраф, когда конкретные аргументы, предоставленные для U и V, имеют одинаковый тип значения.

Я задавался этим вопросом один раз несколько лет назад и , поэтому я проверил .Я не проверял более свежие сборки джиттера, но в прошлый раз, когда я проверял, штраф за бокс был , а не .Джиттер выделяет память, копирует значение в кучу, а затем снова копирует его обратно.

Очевидно, согласно обновленному вопросу, это уже не так;протестированный джиттер теперь выполняет эту оптимизацию.Как я уже сказал, я не проверял это утверждение сам.

Джиттер разрешен для выполнения этой оптимизации, но в прошлый раз, когда я проверял, на практике это не так, поэтому мы знаем, что тамв дикой природе есть по крайней мере один джиттер, который не делает эту оптимизацию.

Более интересный пример - тот, где аргументы типа ограничены равными:

abstract class E<T>
{
    public abstract U M<U>(T t) where U : T; 
}
class F<V> : E<V> where V : struct
{
    public override W M<W>(V v)
    {
        return v; // Error 
    }
}

Опять же, это недопустимо, , хотя компилятор C # может логически вывести, что W теперь должен быть идентичен V.

Вы можете снова ввести приведение, чтобы решить проблему,но анализатор типа верификатора IL требует, чтобы V было упаковано и распаковано как W.

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

Я сообщил об этом в качестве возможной оптимизации команде джиттера; они сообщили мне, что у них много высших приоритетов, что является вполне разумным ответом. Это малопонятный и маловероятный сценарий, который я бы тоже не расставил по приоритетам.

Если на самом деле эта оптимизация уже выполнена, то я приятно удивлен.

0 голосов
/ 21 января 2012

Вы бы хотели избежать приведения вашего значения T / TOriginal в качестве объекта, что привело бы к возникновению проблемы бокса, когда тип значения (которым являются все структуры) будет инкапсулирован как System.Object в куче.Есть несколько способов обойти проблему кастинга.Проще всего было бы, чтобы ваш абстрактный класс содержал параметр универсального типа уровня класса вместо TryGet метода, например:

    public abstract class AbstractParam<T> where T : struct
    {
        //....
        public abstract bool TryGet(string input, out T output);
    }

Другой параметр приводится к Nullable<TOriginal> и затем вызывает GetValueOrDefault() вот так:

        public override bool TryGet<TOriginal>(string input, out TOriginal output)
        {
            T oTemp;
            bool res = _func(input, out oTemp);
            Nullable<TOriginal> n = oTemp as Nullable<TOriginal>;
            output = n.GetValueOrDefault();
            return res;
        }
...