Далеко не факт, что даже на минутку хардкода потянет, но мне было интересно изучать этот вопрос.
Про new() ограничение тут все, наверняка знают, во что это выливается. Но мне было интересно, как можно эту проблему обойти, чтобы получить на выходе обобщенную фабрику, сравнимую по скорости с вызовом делегата, создающего экземпляр конкретного типа. Оказалось, что можно, хоть и через кодогенерацию.
В общем, все это вылилось в очередной long read:
Dissecting the new() constraint in C#: a perfect example of a leaky abstraction
Здравствуйте, hardcase, Вы писали:
H>Вот если бы ты рассказал, почему активатор в примитивном случае конструктора без параметров так медленно работает...
By design.
Код вынужден выполнять кучу проверок, которые в "нормальном" режиме выполняются JIT-ом или компилятором + верификатором IL.
Здравствуйте, Sinix, Вы писали:
Код вынужден выполнять кучу проверок, которые в "нормальном" режиме выполняются JIT-ом или компилятором + верификатором IL.
Это настолько очевидная отмазка, что даже не смешно. Они при джите адреса методов на лету меняю. А тут прямо рокет-сайнс начался.
Раз даже автор пидумал костыль, то уж авторы джита точно могли сделать все по человечески.
Здравствуйте, Sinix, Вы писали:
S>Код вынужден выполнять кучу проверок, которые в "нормальном" режиме выполняются JIT-ом или компилятором + верификатором IL.
Делаем банальное кэширование и ускоряем повторный вызов в хренилион раз.
Упрощенный код:
static readonly Dictionary<Type, ConstructorInfo> _ctorsMap = new Dictionary<Type, ConstructorInfo>();
public static object CreateInstance(Type type, bool nonPublic)
{
if (type == null)
throw new ArgumentNullException(nameof(type));
ConstructorInfo constructor;
if (!_ctorsMap.TryGetValue(type, out constructor))
{
type = type.UnderlyingSystemType;
CreateInstanceCheckType(type);
// This short-circuit depends on the fact that the toolchain prohibits valuetypes with nullary constructors. Unfortunately, we can't check for the presence of nullary
// constructors without risking a MissingMetadataException, and we can't regress the prior N behavior that allowed CreateInstance on valuetypes to work regardless of metadata.
if (type.IsValueType)
return RuntimeAugments.NewObject(type.TypeHandle);
BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance;
if (nonPublic)
bindingFlags |= BindingFlags.NonPublic;
constructor = type.GetConstructor(bindingFlags, null, CallingConventions.Any, Array.Empty<Type>(), null);
_ctorsMap.Add(type, constructor);
}
if (constructor == null)
throw new MissingMethodException(SR.Arg_NoDefCTor);
object result = constructor.Invoke(Array.Empty<object>());
return result;
}
Здравствуйте, VladD2, Вы писали:
VD>Это настолько очевидная отмазка
Я даж возражать не буду. Нравятся утверждения по принципу "мне очевидно, что все неправы" — смысл спорить?
Здравствуйте, Sinix, Вы писали:
VD>>Это настолько очевидная отмазка
S>Я даж возражать не буду. Нравятся утверждения по принципу "мне очевидно, что все неправы" — смысл спорить?
Мне очевидно, что конкретно это отмазка. Данная ссылка доказывает, что можно было сделать сильно быстрее. Просто в МС всем по фиг.
Здравствуйте, VladD2, Вы писали:
S>>Я даж возражать не буду. Нравятся утверждения по принципу "мне очевидно, что все неправы" — смысл спорить?
VD>Мне очевидно, что конкретно это отмазка. Данная ссылка доказывает, что можно было сделать сильно быстрее. Просто в МС всем по фиг.
Автор поста, то есть я, придумал workaround, но он находится не на уровне джита, а выше, и заключается в кодогенерации. И да, мне не ясно, почему именно его не исползовали при выпуске 2-й версии фреймворка, ведь все доступные средства были
Но вот на уровне джита, ИМХО, сделать все сложнее. Приведенный тобой пример с кэшем работает, но не универсален. Подобное кэширование приведет (точнее, уже приводило) к проблемам, когда апликуха использует активатор или рефлексию на загруженных типах. В результате, такой кэш — это мемори лик.
Вот, как раз, небольшая история с кэшированием рефлксии в 1-й версии фремворка:
Так вот, в первой версии .NET Framework его авторы решили, что было бы хорошо иметь кэш для таких дорогостоящих операций, как получения информации о типе (Type.GetMethod, GetMethods, GetProperty, GetProperties etc). И пошли по следующему пути: если пользователь получает информацию об одном из членов, то есть не нулевая вероятность, что он захочет получить информацию о других членах. Поэтому при первом обращении к одному из этих методов, давайте закэшируем все метаданные! Идея здравая? Ну, почти.
Подход работает для одного сценария: отражение используется активным образом и тип исследуется целиком и полностью. (Да, и я не сказал, что кэшируется информация не только о текущем типе, но и о базовых членах).
Если же приложение активно использует отражение, но при этом использует лишь информацию о паре членов, то получается беда: юзверь страдает от падения производительности и от лишнего потребления памяти. А если приложение работает с плагинами, то вообще тапочки могут приплыть к дивану на невиданной скорости и обрушить приложение с OOM, ведь инвалидации у кэша нет. А добавляет масла еще и то, что кэш реализован был в неуправляемом коде, что здорово усложняло разбрирательство с проблеми с памятью.
В .NET Framework 2.0 это починили и сделали кэш ленивым: в нем сохранялась лишь запрошенная информация, и он переехал из неуправляемого кода в управляемый. Также была добавлена «политика инвалидация» путем использования слабых ссылок. В добавок появился тип – RuntimeMethodHandle, которые теперь позволяет реализовать кэш самостоятельно.
Если вдруг, вы задаетесь вопросом, что вам делать с этой информацией, то, боюсь, что ответить на это я не смогу. Хотя, тут приходит на ум довольно известная фраза, что любой кэш без политики инвалиации является всего лишь своеобразной (sophisticated) утечкой памяти.
Вот и получается, что я, ХЗ, как можно было научить джит дружить с активатором по серьезному.
З.Ы. На самом деле, джит с рефлекшеном подружен теснее, чем кажется (см.
Drilling into .NET Runtime microbenchmarks: ‘typeof’ optimizations.):например, typeof(FooBar).TypeHandle == anotherHandle будет соптимизирован JIT-ом таким образом, что полная информация о типе получена не будет.
З.Ы.Ы. Я не сильно большой знаток внутренностей рефлекшна, но, насколько я помню, DLR был затюнет с десятком уровней кэшей донельзя, но это не сделало его особенно быстрым. И я тут полностью ХЗ, возможно оптимизячили не там где нужно, и нужно было именно джит тюнить, но как — хз
Здравствуйте, hardcase, Вы писали:
H>Здравствуйте, SergeyT., Вы писали:
ST>>Вот и получается, что я, ХЗ, как можно было научить джит дружить с активатором по серьезному.
H>При фактическом вызове CreateIntance аргумент T известен, сохранить в информации о типе указатель на конструктор по-умолчанию должно быть совсем не трудно, и уж тем более его позвать как память выделится.
Я думаю, что текущая проблема не настолько болезненна, чтобы хардкодить ее в джите и в таблицах с информацией о типе