MCP
Показаны сообщения с ярлыком производительность. Показать все сообщения
Показаны сообщения с ярлыком производительность. Показать все сообщения

вторник, 9 апреля 2019 г.

Странная реализация HttpClient в .NET Core

Представьте что у вас есть приложение. Не очень маленькое, но и не гигантское, работает помаленьку развивается, и тут вы совершенно случайно замечаете в мониторинге, что один из сервисов жрёт 25% CPU. Хм... вроде многовато, но мало ли что. Может боты набежали на сайт и смотрят все странички, или пользователи ходят. Да и 25% немного, запас огромный (400% максимум). Но на всякий случай сервис перепускается и загрузка падает до 1%. Вы думаете — какой-то глюк, но через некоторое время загрузка процессора растёт опять.

Вот с подобной ситуацией я столкнулся несколько недель назад. Есть какая-то утечка CPU (не памяти, а именно процессора), при этом ни истерики в логах ни каких-то явных знаков, указывающих на проблему. Приложение, хоть и не очень большое, но делает кучу разных вещей, отправляет письма, общается через собственную API-платформу, активно кеширует данные, связывается с другими частями и внешними сервисами, генерирует документы, в общем точек для анализа очень много. А утечка небольшая, несколько процентов в день, да и эти несколько процентов в моменте могут быть настоящей нагрузкой от живых пользователей. Т.е. даже не ясно как поступать с профилированием. Да и на деве это не повторяется.

Когда я в очередной раз сдался, я не выдержал, и подцепился дебаггером к боевому серверу напрямую. И... ничего не понял, всё выглядит нормально. Отцепился — загрузка 0%. Тут у меня уже задёргался глаз. Через некоторое время повторил — да, подключение дебаггера явно лечит проблему, как и перезапуск приложения. Но это ведь не выход и не решение. А вдруг это какая-то проблема у нас, и если в этом приложении данную проблему можно замаскировать, то она может выстрелить где-то ещё. Так что пришлось разбираться дальше.

Неожиданно удалось нагуглить данный тред на GitHub'е. Проблема у Microsoft'овской библиотеки и связана она с HttpClient. Утверждалось, что нельзя его создавать на каждый HTTP запрос, а надо использовать статичный. Правда в документации есть пример использования:

В котором его заворачивают в using. Я попробовал покопать в данном направлении и выяснил первую забавную вещь: использование статичного HttpClient в .NET Core быстрее раз в 5, чем создавать его на каждый запрос. Но тут нюансы с самим временем запроса, но смысл в том, что уже есть причина использовать статичную версию. При этом под "большой" .NET разницы нет никакой.

В общем, выложил новую версию приложения, но проблема осталась. (может я не туда копаю?). Попробовал переключиться с .NET Core 2.1 на 2.0 — проблема ушла. Т.е. проблема есть только под core, под Linux и версия должна быть больше чем 2.0 (более свежие не проверял). Читаем документацию и видим, как разработчики Microsoft, светясь от гордости, рекламируют новую реализацию Http Client Factory, через которую и идут все запросы (не может Microsoft просто написать библиотеку, делающие HTTP запросы, внутри обязана быть магия!). В общем теперь всё managed, в 10 раз лучше и в 50 раз быстрее. Ага, и сделано как раз в 2.1

Прикидываю, где ещё скрылись HttpClient'ы и нахожу библиотечку для отправки Push-уведомлений, которая внутри себя создаёт данный класс. Собственно, поэтому в первый раз и не было найдено это использование. Переделываю использование данной библиотеки и проблема исчезает! Всё работает замечательно.

Тут я начинаю громко материться, но нахожу в документации небольшую приписку:
Т.е. они знают о данной проблеме! Но явно про это написать было стыдно, так что засунули всё в сноску, зато не будет никаких претензий от разработчиков. Сноска есть? Есть! Значит читайте внимательнее.

Получается совершенно странная ситуация: Disposable-класс, который надо использовать синглтоном. При том, что все предыдущие реинкарнации подобных классов были честными Disposable-объектами. Но тут решили сделать по-другому. Зачем? Я пока не понял. Но свинью подложили знатную. Представьте, у вас есть базовая библиотека, написанная под большой .NET, вы её мигрируете на Core, пользуетесь под Windows, всё нормально, а потом, спустя длительное время замечаете, что приложение жрёт много процессора. Вам нужно будет пройти квест, подобный моему, чтобы найти проблему, переписать библиотеку и весь остальной код, обновить всё что нужно и всё из-за странного архитектурного решения разработчиков из Microsoft.

воскресенье, 10 февраля 2019 г.

Деградация Windows Server

Есть у меня простенький компьютер. Был куплен в порыве шопоголизма на распродаже. Я так и не придумал, чем занять данный компьютер, поэтому накатил на него Windows Server 2016 (По факту LTSB сборка 10-ой винды 1607, которая Anniversary Update, но с серверными фичами). Компьютер после запуска (и ожидания 8 минут, пока винда разберётся со своими внутренними делами) выглядел так:

Если не вдаваться в подробности, то запущено 54 процесса, 848 потоков и занято 0.9Гб оперативки. Вполне допустимая ситуация для пустого сервера, хотя всегда хочется меньше.

Но я решил потестировать недавно вышедший Windows Server 2019, эта та же Windows 10, только уже злополучная 1809, October 2018 Update. Я просто обновил сервер, подождал джентльменские 8 минут и результат на экране:

Процессов уже стало 110 (ровно в два раза больше!), количество потоков увеличилось всего на 300 штук, что уже лучше, хендлов стало больше почти в 2 раза и сожрано стало на 400МБ оперативной памяти больше.

Ещё раз, для понимания бреда. За 2 года разработки операционная система на свои личные нужды стала использовать гораздо больше ресурсов. Может от этого она стала быстрее или лучше работать? Как-то незаметно. Вместо оптимизации системы в итоге получаешь просто ещё больше внутреннего потребления. Зачем? А просто так, потому что программисты Microsoft могут тратить ресурсы как захотят. Железо же становится быстрее, памяти больше, никто и не заметит. И вот это бесит неимоверно.

UPD: Оказывается, это сознательное решение Microsoft, они в 1703 разгруппировали сервисы, если в системе больше чем 3.5ГБ памяти. Объяснили как всегда надёжностью и безопасностью. Но памяти это, конечно же, жрёт больше, о чём не скрывают. А то, что "стабильность" теперь зависит от количества оперативной памяти — выглядит это очень странно.

UPD2: Поиск решения проблемы привёл на следующую ссылку, надо в реестре по пути:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control отредактировать (создать?) ключ SvcHostSplitThresholdInKB, изменив там количество памяти для разделения процессов. Итог изменения ключа следующий:

Процессов и памяти резко уменьшилось. До старых значений ещё не доходит, но всё-таки результат стал получше. 

понедельник, 31 октября 2016 г.

История одной грабли

Сегодня хочу рассказать о том как маленькая аппаратная проблема привела к хорошему отвалу сервисов. Ситуация интересна тем, что вроде бы несвязанные вещи могут привести к фатальным результатам, так что надо продумывать всё на несколько шагов вперёд.

Итак, поехали.

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

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

Основные сервера, которые копируют данные на него видят его, и пытаются скопировать, по факту сделали снапшоты от текущей версии и пытаются передать разницу. Разница никак не передаётся. Разница растёт. Растёт эта разница долго и упорно (да, по закону подлости это всё случилось на выходные) и в понедельник в 5 утра на одном из серверов тупо кончается место. Занято всё снапшотами. Место кончилось, все виртуалки ушли на аварийную паузу. И всё сломалось...

Т.е. все данные есть, ничего не потеряно, но ничего не работает. Починилось это банально — выдёргиванием проблемного диска (ну и заменой его). Рейд развалился, сервер забегал, снапшоты долились.

А теперь проблема в целом: грабли с диском на неважном сервере вывели из строя основной. Т.е. по факту, следить надо за такими фантастическими вещами. Такие вот пироги с котятами.

воскресенье, 17 апреля 2016 г.

ZeroMQ vs NetMQ

Последнее время не спеша изучаю ZeroMQ, а так как пишу в основном на .NET, то решил сравнить производительность двух реализаций, "официальной" clrzmq4 (в nuget'е пакет называется ZeroMQ, и все примеры с сайта на нём), и альтернативный вариант NetMQ, написанный полностью на .NET и совместимый с ZeroMQ.

В лоб бенчмарков не нашёл, только фразы вида примерно одинаковая скорость, так что решил всё-таки быстренько глянуть на производительность, и в случае принципиальной разницы исследовать дальше.

Для самого простого варианта я взял Publisher/Subscribe модель и начал закидывать сообщения по-кругу, и смотреть на результат. Результат оказался слегка неожиданным, но красноречивым. Не буду делать графики, ибо и без них всё понятно. Просто некоторые пункты:

  • clrzmq4/NetMQ одинаково быстры (около миллиона сообщений в секунду) при работе с массивами байт
  • Два паблишера на одного сабскрайбера (странный вариант, ну да ладно), гораздо быстрее работают у NetMQ 
  • При попытке работы со строками clrzmq4 начинает резко сливать производительность (в 3 раза). Судя по-всему, не очень удачная работа с маршаллингом
  • С clrzmq4 надо быть очень аккуратным, пропущенный Dispose даст прирост в скорости, но может вызвать неприятные ошибки
В общем, тестировать производительность дальше неинтересно, не думаю, что там есть какая-то волшебная разница на разных паттернах, с учётом того, что у NetMQ гораздо приятнее API, рекомендую рассматривать в качестве реализации API сразу же его и не париться с выбором.
NetMQ — быстрее и удобнее.

четверг, 7 апреля 2016 г.

Производительность SQL при поиске по ключу в зависимости от типа

Последнее время в проектах для SQL-таблиц в качестве идентификатора мы используем гуиды. Ибо уникально, не нужны идентити, и можно идшники генерить на клиенте (причём сразу для всего дерева объектов), а потом вставлять в базу (при этом, хоть асинхронно).

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

Тестировать можно долго и по-разному, но для простоты, я ограничился следующими условиями:

  • Таблицы с Id и Value (чтобы совсем грустные не были
  • Id типов int, bigint и guid (т.е. 4, 8 и 16 байт, меньшие типы несерьёзно тестировать)
  • Id - кластерный индекс и первичный ключ
  • Заполнение по Identity и по рандому (чтобы проверить, влияет ли более разреженный индекс на результаты). Для гуидов идентити эмулировалось вручную, генерацией "правильных" данных 
  • Заранее вставил 1000000 записей в каждую таблицу
  • Селекчу много раз определённую запись (глупо, но SQL ведь особых чудес ловкости отваги проявить тут не сможет, впрочем, попробовал разные записи, результат похож)
В качестве подходящей незагруженной железяки (чтобы не влияли на результаты) оказался сервер с SSD, достаточным количеством памяти, но с SQL 2008. Не очень свежо, но не думаю, что что-то менялось на подобных запросах. Впрочем, позднее перетестирую, и если результаты изменятся — обновлю пост.

В общем, замерил результаты и.... они оказались практически идентичными. Конечно, разница есть в пределах 3%, но это несущественно. Впрочем, смотрите картинку сами:
Вам может показаться, что разница существенная и в пользу инта, давайте немного поправим масштаб картинки:


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

Но дальше я решил разнообразить тесты и сделать точно такие же таблицы, но без ключей (не повторять самостоятельно, опасно для жизни!). Результаты оказались слегка обескураживающими
Инт тут не просто в пролёте, он в почти в 3 раза медленнее гуида! А бигинт самый быстрый (что, в принципе не так уж и странно, но не в случае такого провала с интом).
Вначале я думал, что табличке с интами не повезло и она нарвалась на длинный фулскан, пробовал селектить разные записи — результат идентичный. В чём дело, я не понимаю. Возможно проблемы SQL сервера, а возможно так и задумано, тут я не специалист. В общем, надо доисследовать, но ситуация очень неординарная. Бигинты круче интов в несколько раз...

Ну и напоследок размер таблиц. Понятно, что это всё сильно depends и от данных и от наполненности индекса, но общее представление можно поиметь:


Тут всё просто и логично, снос кластерного индекса экономит 4-5 мегабайт на миллионе записей, бигинт больше инта где-то на 4 мегабайта, гуид больше бигинта на 8-9 мегабайт в зависимости от наличия/отсутствия индекса.

Update: Обновил SQL Server с 2008 до 2014. Результаты: Поиск по индексу стал где-то на 40% быстрее (смысл обновляться оказывается имеется!), Поиск по таблицам без первичного ключа: с интами ничего не изменилось, гуиды стали быстрее на 30%, т.. практически сравнялись с бигнитами. Т.е. проблемы те же самые на месте, но гуиды стали ещё более привлекательнее. Картинка с данными:

PS: OLTP-таблицы дают офигенный прирост скорости для таблиц без индекса, но OLTP для таблиц с индексом даёт те же результаты в моём случае. В общем, OLTP, это предмет отдельного поста, просто, раз обновился, решил глянуть.

суббота, 24 мая 2014 г.

Деградация размера MFT от времени

Небольшая заметка посвящена тому, что недавно я обнаружил тот факт, что MFT в Windows не умеет уменьшаться.

MFT — это базовый служебный файл файловой системы NTFS, в котором хранится информация обо всех остальных файлах. Всё это весьма неплохо работает, но есть различные грабли. Сегодня расскажу про один косяк, связанный с тем, что MFT может только расти.

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

Хорошая новость состоит в том, что если вы опять закините этот миллион файлов, MFT переиспользует своё место и расти не будет. А если не планируете, и ваш перфекционизм мешает спать, то единственный вариант, который я вижу — бекап существующих файлов, форматирование диск и восстановление.

Если вы найдёте способ лучше, буду премного благодарен.

воскресенье, 23 февраля 2014 г.

Мысли про возможности инструментов

Тут неделю играюсь с новым китайским кубиком Рубика. Изначально, я его брал, чтобы на работе и дома было по кубику, но, когда приехал новый, хоть и очень дешёвый китайский, я понял одну вещь — я многого не понимал.

Если вкратце, то он вращается настолько легче и плавнее тех, что были у меня в наличии, что удовольствие от сборки переходит на совершенно другой уровень. На старом те действия, которые можно сделать на новом, просто технически невозможны. А попытки их повторить выглядят смешно и жалко. Чтобы понятнее было: это повороты граней одним пальцем, срезание углов кубиком, точность вращения.

К чему я это пишу: в жизни тоже часто бывает так, что люди, имея дешёвый инструмент, считают что дорогой "просто лучше и удобнее", а в реальности, им можно делать совершенно другие вещи, или делать существующие по-другому.

Т.к. я программист, то пример из этой области: студия без Решарпера, кажется удобной. После его использования, его отсутствие приводит к ломке. Хотя сам по себе инструмент не меняет ничего кардинально. Скрипты на нормальном языке программирования (или хотя бы на перле), приводят к тому, что можно сделать логику, которую раньше просто никто и не думал реализовывать. Знание многих языков, приводит к тому, что можно соединить несколько разных технологий и быстро написать то, что надо. Наличие большого количества оперативной памяти может привести к тому, что можно держать весь объём данных в памяти, тем самым в сотни раз ускорив приложение.

К чему я всё это веду — старайтесь использовать лучшие инструменты и технологии, это поможет вам понять, как действительно нужно и что можно сделать с инструментами. Не надо писать код в блокноте, это круто для ЧСВ, но неудобно в жизни.

PS: Кстати, что удивительно, лучшие кубики Рубика делает не фирма Rubik's, а самые настоящие китайцы, фирма DaYan. Т.е. не в том плане, что в китае делается, а в том, что именно сам китайский бренд котируется лучше всего. Так, в копилку бесполезных фактов.

пятница, 31 января 2014 г.

Про кубик Рубика

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

Мысль раз — как головоломка, кубик Рубика весьма плохо выполняет свою роль. Я имею ввиду то, что практически невозможно собрать его без алгоритмов. При этом, чтобы их придумать, нужно или очень много терпения и анализа, или же очень крутым IQ.
Т.е. по факту, большинство людей без подсказок не смогут его решить. А смысл в головоломке, которую нельзя решить от меня ускользает.
При этом, если разобранный кубик раздражает чувства перфекциониста, можно выучить 7 простых алгоритмов (можно ограничиться четырьмя, но не стоит), и собирать его. Удовольствия никакого.

Но, мысль два, навеяна спидкуберами. Я нашёл-таки, некий извращённый способ получать удовольствие при сборке. Заключается он в том, что при сборе первых двух слоёв, всё-таки можно обойтись без алгоритмов, и жить на чистой логике. И хотя алгоритмы практически вырисовываются после нескольких десятков сбора, сам процесс поиска наиболее оптимального решения как раз и доставляет удовольствие.
Оставшийся ряд, тут уж ничего не поделаешь, приходится собирать, используя готовые решения. Впрочем, тут тоже можно получать удовольствие, выбирая из пары сотен алгоритмов, наиболее интересные и забавные, компенсируя невыученные случаи изобретальностью. Всё лучше, чем бездумно учить огромную прорву поворотов. Хотя, если у вас склад ума, который позволяет легко запомнить некий алгоритм (на словах, или руками), то вам, возможно, такой подход будет более интересен, и вы приблизитесь к настоящему спидкубингу.

Кстати, про тех людей, которые как раз собирают кубик за 10 секунд. Как я понимаю, у них это получается с помощью использования нескольких факторов: быстро и оптимально крутить кубик, знать огромное количество алгоритмов, правильно выбирать нужный в конкретной ситуации и выбирать следующий в тот момент, пока выполняется текущий. Т.е. терпение, труд, и небольшая склонность к решению этой задачи приводят к таким выдающимся результатам.

Итак, чем же может быть полезен кубик Рубика. Во-первых, он развивает мелкую моторику рук. Во-вторых, может слегка прокачать пространственное мышление. Возможно, также слегка помогает оценивать обстановку и быстро принимать не самое плохое решение. Не уверен.

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

суббота, 17 августа 2013 г.

Методы и производительность динамического создания конструкторов

В .NET достаточно развитые средства рефлексии, и как результат этим активно пользуются. При этом вариантов и способов использования достаточно много. Эти способы отличаются в зависимости от того что надо: по типу действий (создать объект, вызвать его метод, получить/изменить значение поля), по информации об объекте (мы заранее знаем сигнатуру или не знаем), по типу доступа (публичные/приватные методы), по наличию параметров.

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

Сегодня я рассмотрю достаточно простой способ: вызов конкструктора без параметров и с известными параметрами. Это достаточно часто встречающаяся задача, например, она может использоваться для создания плагинов и модулей. При этом мы знаем, что объект имеет определённый тип (отнаследован от интерфейса, например), так что в дальнейшем мы можем вызывать методы напрямую. Но тем не менее, мы не можем напрямую создать объект, т.к. у нас есть только информация о типе (Type), но изначально мы просто не можем написать new MyObject() — данные придут позднее.

Соответственно есть несколько путей решений:

Activator.CreateInstance

Самый простой и удобный метод создания объекта:
Activator.CreateInstance(myObjectType)

Также можно передать параметры для конструктора. Всё просто и очевидно.

ConstructorInfo.Invoke

"Классический рефлекшен". 
var constructorInfo = myObjectType.GetConstructor(new Type[0]);
constructorInfo.Invoke(new object[0]);

Т.е. мы получаем объект ConstructorInfo и через рефлексию создаём объект. В данном примере нет параметров, но их легко можно передать массивом объектов

Compiled Expression

Ещё один сравнительно новый вариант создания объекта, который заключается в построении Expression'а и дальнейшей компиляции его в делегат (анонимную функцию). Способ создания без параметров выглядит так:
var d = Expression.Lambda<Func<object>>(Expression.New(constructorInfo)).Compile();

В результате у нас будет делегат, возвращающий object (можно специфицировать точнее), который мы можем вызывать как обычный метод.

Если конструктор имеет параметры и нам точно известны, какие они должны быть, мы можем использовать подобный код:
var p1 = Expression.Parameter(typeof(int));
var p2 = Expression.Parameter(typeof(string));
var newExpression = Expression.New(constructorInfo, p1, p2);
var d = Expression.Lambda<Func<int, string, object>>(newExpression, true, p1, p2).Compile();

В данном случае, мы точно знаем, что у нас есть конкструктор с двумя параметрами типа int и string, поэтому мы генерируем конкретный код, вызывающий конкструктор с нужными параметрами. В отличие от предыдущих методов, данный слегка читерский, ибо тут мы точно указываем параметры, а мы их можем и не знать, соответственно у этого метода должна быть фора.

Compiled Expression with Any Parameters

Чтобы избавиться от этой форы и рассмотреть случаи, когда мы заранее не можем знать параметры, надо сгенерировать что-то общее. Логично будет рассмотреть случай, когда у нас есть делегат, принимающий массив параметров типа object, при этом данный делегат внутри сам проводит преобразования для корректного вызова. Код будет выглядеть примерно так:

var argsExpr = Expression.Parameter(typeof(object[]));
var constructorParams = constructorInfo.GetParameters()
.Select((p, i) => Expression.Convert(Expression.ArrayAccess(argsExpr, Expression.Constant(i)), p.ParameterType));
var newExpr = Expression.New(constructorInfo, constructorParams);
var d = Expression.Lambda<Func<object[], object>>(newExpr, true, argsExpr).Compile();

Т.е. мы передаём массив, в котором находятся аргументы, потом из этого массива достаём параметры для конструктора, конвертируем к нужному типу и вызываем конструктор.

Dynamic Method

Самый сложный метод, который заключается в использовании DynamicMethod и ручной генерации MSIL'а. Поскольку решение в отличие от остальных не совсем тривиальное, детальное описание реализации оставлю для следующей статьи (будет ссылка, когда статья будет написана). Мы его также как и Expression можем сделать специфичным, или обобщённым. У обобщённого основная идея та же, что и в предыдущем методе: мы генерируем делегат, принимающий массив object'ов, внутри приводим аргументы к нужному типу и вызываем конструктор. Только делаем всё это нативно, генерируя MSIL и компилируя его.

Результаты производительности

Мерять тут можно много чего. Но я остановился на следующих условиях:
  • Только публичные конкструкторы (приватные значительно дольше, а необходимость в их использовании весьма специфичная)
  • Время подготовки нужного объекта не замеряется. Подразумевается, что если уж есть цель оптимизации, то этим временем можно пренебречь. Хотя оно может и вносить ощутимые задержки на первый вызов. Если для вас это будет существенно, наверное стоит провести замеры на ваших условиях и железе.
  • Объекты очень простые но не совершенно пустые (сложность этих объектов может сильно повлиять на расстановку сил, нивелировав или максимально усилив различия между методами
  • Проверяется Debug и Release режим, чтобы выяснить ускорение, получаемое кодом от оптимизации
  • Конкретные цифры я не покажу, ибо это сильно зависит от железа. Только отношение
  • Также для сравнения добавлен прямой вызов конструктора 

Вызов конструктора без параметров

reflection constructor performance

По графику можно сделать простой вывод: если скорость не очень критична, используйте Activator, а если объектов нужно создавать очень много, то Expression. И да, Release режим в некоторых случаях рулит. Рефлекшен через ConstructorInfo самый медленный. Если у вас есть желание, можете использовать DynamicMethod, он самый быстрый.

Вызов конструктора с параметрами

Я взял конструктор с двумя параметрами int и string, чтобы было заметно влияние боксинга. Результаты получились следующие:
reflection constructor performance

Те же графики, но без активатора, чтобы был лучше виден масштаб:
reflection constructor performance


В данном случае Activator показал дичайшие тормоза, что очень удивительно, относительно предыдущего варианта. Обобщённый экспрешшен в 2 раза медленнее специализированного (хоть на графике это плохо видно) и в 4-6 раз медленнее прямого вызова (для сравнения скорости). Т.е. вывод тут такой — используйте скомпилированные экспрешшены, по-возможности с явными типами, если нет, то с обобщёнными, потери не очень сильные. При этом обобщённый DynamicMethod быстрее обобщённого Expresssion'а, но медленнее типизированного. Т.е. если хочется максимальной скорости, придётся относительно много писать, чтобы сделать максимально типизированный и производительный DynamicMethod.