?

Log in

No account? Create an account

December 9th, 2009 - Excelsior — LiveJournal

Dec. 9th, 2009

12:48 am - Наглядность

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

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

1. Профайлинг. Сама идея о том, что можно получать и исследовать моментальные или кумулятивные срезы работы программы крайне прекрасна. Ее можно подъюзать как в определении узких мест приложения, так и для того, чтобы разобраться с чужим кодом (за последнее спасибо Диме!). Мой любимый тул на эту тему - dotTrace, который быстро и непринужденно показывает дерево вызовов, показывает узкие места прогресс-барами, позволяет фильтровать деревья по маскам и выделять подветки в отдельные деревья.

2. Всемогущий дебаггинг (omniscient debugging). Перекрывает и обобщает идею профайлинга, но, к сожалению, мне неизвестны омни-дебаггеры продакшен-качества для платформы дотнет. Идея такого дебаггера состоит в том, чтобы запоминать абсолютно все действия приложения за некоторый период времени и позволять программисту выполнять оффлайновую отладку (например, бродить туда-сюда по стеку). Похожую вещь попробовали замутить в 2010й студии (называется интеллитрейс), но полноценной реализации, похоже, не будет еще лет пять - слишком уж велики в текущей версии ограничения на собираемые данные. До сих пор не могу понять, почему не сделали сбор данных о локальных переменных - ведь имплементация интеллитрейса основана на profiler api, а он такую функциональность поддерживает.

3. Качественный трейсинг. Пример - профайлер конкарренси в новой студии. Интересно, как много можно узнать о своей прилаге, когда тебе вручают тулзы, которые могут собрать соответствующие данные. Вот несколько интересных статей на эту тему: VS 2010 Beta 2 Concurrency Visualizer Profiling In Depth First Look, VS 2010 Beta 2 Concurrency Resource Profiling In Depth First Look, а вот бложек команды, которая делает визуализацию данных о параллелизме - A View into the Behavior of Your Parallel Application. Связанная штука - параллельные стеки в дебаггере.

Tags:

06:06 pm - Кодяринг на микроуровне

Традиционно ресурсы по проге рассматривают высокий уровень программирования: архитектуры, лайеры, паттерны и так далее. Но разве не менее важно то, чем мы занимаемся каждый день - кодяринг in-the-small (т.е. в пределах одного метода)? В этом потсе я расскажу о своих любимых способах улучшить программирование на микроуровне.

1. На первом месте стоит, конечно же, функциональный подход. Благодаря функциям высшего порядка, в частности, и функциональной композиции, в общем, появляются возможности по реюзанию сниппетов кода, которые не выносятся в отдельные методы. Порой диву даешься, насколько это сокращает код и насколько часто можно подъюзать ФП.

qsort [] = []
qsort (x:xs) =
    qsort(filter (< x) xs) ++
    [x] ++
    qsort(filter (>= x) xs)

2. Что меня реально вырубает - это switch. Причем, к сожалению, свичи нужны чаще, чем это может показаться. Например - перебрать все значения энума или проверить объект на принадлежность одному из классов, после чего что-то с ним сделать. Последний сценарий особенно напрягает, ибо в таком случае нужно дважды указывать тип или объявлять дополнительный локалс (варианты is или as). Здесь чрезвычайно рулит паттерн-матчинг и его расширение - активные паттерны, которые позволяют сопоставить объект с одним из вариантов и прямо на месте прибиндить составные части объекта к локальным переменным. Исключительно прекрасно эта штука работает с квазицитированием.

let rec interp inp =
match inp with
    | <@@. x + y .@@> -> interp x + interp y
    | <@@. x * y .@@> -> interp x * interp y
    | Int32(x) -> x
    | _ -> failwith "unrecognized"

3. Проверки на null. Еще круче, чем свичи, действуют на нервы цепочки вызовов методов возможно нулевых таргетов. Добавленный во второй сишарп оператор коалесцирования (??) слегка помогает, но случаи его применения редки. Чрезвычайно эпично в этим вопросом справляется монада Maybe, но, во-первых, не всем она доступна, а бесшумно сэмулировать ее без метапрограммирования не получится, а, во-вторых, юзание монад в таком простом частном случае это overkill. В свое время меня порадовали nullsafe-операторы из Груви (?:, ?., ?->). А сегодня я наткнулся на мегарешение при помощи джаваскрипта, которое по семантической плотности приближается к примеру копирования строк в C или к квиксорту на Хаскелле. Встречайте: this[0] && this[0].ownerDocument || document. Объяснение вот тут: Guided Tour: jQuery - guard and default operators.

09:41 pm - Нашел баг в рефлекшн-эмите

Воспроизводится вот так: http://code.google.com/p/xenogears/source/browse/trunk/XenoGears.Playground/XenoGears/Reflection/Emit/ReflectionEmitTests.cs?r142 - первый тест прокатывает, а второй нет. Юзаю дотнет 3.5 с первым сервис-паком. Впрочем, в бете-2 четвертого фреймворка та же хрень. Ничего выдающегося, но он тупо не работает, и для моего конкретного случая это важно.

Кодярник, воспроизводящий баг
Иксепшн, вылетающий при выполнении теста

Причина бага

Проблема заключается в том, что рефлекшн-эмит, а именно SignatureHelper, пишет неожиданную для CLR (а может и не соответствующую стандартам) сигнатуру в таблицу MemberRef. В момент JIT-компиляции рантайм, прочитав MemberRef, не может по нему разрезолвить MethodDef, что и приводит к выбросу MissingMethodException.

Смотрим детали: (красивенькие байтики и их расшифровка получены при помощи ILDASM по команде View > MetaInfo > More HEX, View > MetaInfo > Show!)

Рабочий MemberRef (сгенерирован компилятором VS 2008 SP1) Нерабочий MemberRef (сгенерирован рефлекшн-эмитом .NET 3.5 SP1)
Member: (0a00012b) Dim:
CallCnvntn: [DEFAULT]
generic
Type Arity:1
ReturnType: I4
2 Arguments
Argument #1: MDArray MVar!!0 2 0 2 0 0
Argument #2: I4
Signature : 10 01 02 08 14 1e 00 02 00 02 00 00 08
Member: (0a000002) Dim:
CallCnvntn: [DEFAULT]
generic
Type Arity:1
ReturnType: I4
2 Arguments
Argument #1: MDArray MVar!!0 2 0 0
Argument #2: I4
Signature : 10 01 02 08 14 1e 00 02 00 00 08

Ниже приведена побайтовая расшифровка сигнатур. Хардкорные знания целиком и полностью взяты из спеки Standard ECMA-335: Common Language Infrastructure (CLI), 4th edition (June 2006) (раздел II, пункты 23.1.16 Element types used in signatures, 23.2 Blobs and signatures, 23.2.1 MethodDefSig, 23.2.10 Param, 23.2.11 RetType, 23.2.12 Type, 23.2.13 ArrayShape).
  * 0x10 => метод является GENERIC, а также имеет DEFAULT calling convention.
  * 0x01 => у метода 1 генерик аргумент.
  * 0х02 => у метода 2 параметра.
  * 0x08 => возвращаемое значение метода
    * 0х08 => тип возвращаемого значения - ELEMENT_TYPE_I4, т.е. int.
  * 0x14 0x1e 0x00 0x02 0x00, а дальше расхождения: в рабочем варианте - 0x02 0х00 0х00, а в падающем - 0x00 => первый параметр
    * 0x14 => тип первого параметра - ELEMENT_TYPE_ARRAY, т.е. многомерный массив (одномерный обычно кодируется как 0x1e ELEMENT_TYPE_SZARRAY); на этом инфа о первом параметре не заканчивается - дальше идет спека массива в соответствии с 23.2.13 ArrayShape.
    * 0x1e 0x00 => тип элемента массива - ELEMENT_TYPE_MVAR, т.е. генерик параметр метода, следующий после этого байт говорит об индексе этого параметра.
    * 0x02 => ранг массива равен 2
    * 0х00 => заданы 0 размеров измерений массива
    * 0x02 0х00 0х00 => заданы 2 минимальные границы измерений массива, обе равны нулю
    * 0x00 => это то, что сгенерил рефлекшн-эмит. Он кагбэ хочет сказать, что наряду с измерениями массива он ничего не знает и про минимальные границы. Так вот почему Рефлектор на рабочий вариант писал "L_0009: call int32 [XenoGears]XenoGears.Array2DExtensions::Dim(!!0[0...,0...], int32)", а на падающий - "L_0008: call int32 [XenoGears]XenoGears.Array2DExtensions::Dim(!!0[,], int32)"!
  * 0х08 => второй параметр
    * 0х08 => тип второго параметра - ELEMENT_TYPE_I4, т.е. int.

Устранение

Ноги бага растут из метода "internal virtual int GetMethodToken(MethodBase method, Type[] optionalParameterTypes)", который принадлежит классу ILGenerator и вызывается всякий раз, когда кто-то эмиттит MethodInfo. При необходимости импорта референса на генерик метод вызывается "handle = this.GetMemberRefToken(genericMethodDefinition, null)", который ведет к методу ModuleBuilder "internal int GetMemberRefToken(MethodBase method, Type[] optionalParameterTypes)". Ну а в нем вызывается метод SignatureHelper, который создает неожиданную сигнатуру. Переписав руками методы выше и пропатчив результаты работы хелпера, можно пофиксить этот неприятный баг. Этим, собственно, я завтра и займусь. А на сегодня - спокойной ночи.

Tags:
Previous day (Calendar) Next day