Хочу на досуге поковыряться в кишках компилятора, а точнее в той ее части, что отвечает за кодогенерацию с целью улучшения качества выхлопа...
Например, можно начать с циклов. Сейчас вот для такого простейшего цикла
private Test(args: array[string]) : void
{
unchecked
{
foreach (i in args)
WriteLine(i);
}
}
генерируется такой IL, на который не рассчитывает джит, в результате проверки за выход границы массива не устраняются. Не говоря уже о мелочах, таких как 2 инициалиазации переменной счетчика и использование более длинной инструкции callvirt instance int32 [mscorlib]System.Array::get_Length(), а не ldlen, что увеличивает размер метода. Для примера тот же самый код, сгенерированный C# имеет размер 25 байт против 38 у Nemerle.
Здравствуйте, rameel, Вы писали:
R>Хочу на досуге поковыряться в кишках компилятора, а точнее в той ее части, что отвечает за кодогенерацию с целью улучшения качества выхлопа...
Хочу сразу предупредить, что многие косяки немерловой генерации нивелируются оптимизациями в джите. Например, практика показала, что бороться с лишними переменными совершенно бесполезно. Джит их сам отлично устраняет (а может они просто ложатся в регистры), так что на скорости кода это не сказывается.
Так что прежде чем что-то править, надо убедиться на тестах, что проблема реально существует.
R>private Test(args: array[string]) : void R> foreach (i in args)
Учти, что foreach — это макрос. Так что "паттерн" для него генерируется не компилятором, а этим макросом. Находится он здесь.
Конкретно код для массивов генерируется здесь. Но там вроде как все цивильно:
def one_dim_case()
{
def index = if(index != null) <[ $(index : name) ]> else <[ index ]>;
def definition = build_definition(<[ cached_collection[$index] ]>);
<[
// accessing to Length property like here
// should trigger CLR to enable "remove of array bounds checking" optimizationfor (mutable $index = 0; $index < cached_collection.Length; ++ $index)
$definition;
]> :: []
}
Так что смотреть надо, скорее, макрос for. Его код находится там же.
Конечно, возможно, что дело в генераторе кода, но с начало нужно в этом убедиться. Для этого надо посмотреть типизированное дерево. Для этого нужно поставить точку прерывания в методе Typer.RunSecondPass. Причем делать это нужно перед вызовом Typer2 и после каждого из шагов (на них производится переписывание кода). К этому моменту макросов уже не будет.
Для упрощения отладки надо выключить генерацию pdb, но не включать Release, иначе отладка будет невозможна.
Для подключения к компилятору проще всего создать проект немерла, описать в нем отлаживаемый код, расположить функцию с кодом первой в модуле, в свойствах проекта на закладке "Build" установить свойство "Run debugger" в true.
R>генерируется такой IL, на который не рассчитывает джит, в результате проверки за выход границы массива не устраняются.
А как ты смотрел ассемблер? Учти, что единственный достоверный способ проверить это — скомпилировать релизную версию и приатачиться к ней отладчиком во время выполнения (вызвать брэйк из кода). Сама программа должна при этом быть запущена без отладчика, иначе джит вырубет оптимизации. Ну, и далее надо смотреть ассемблер.
R>Не говоря уже о мелочах, таких как 2 инициалиазации переменной счетчика и использование более длинной инструкции callvirt instance int32 [mscorlib]System.Array::get_Length(), а не ldlen, что увеличивает размер метода.
Вот, кстати, это может и влиять на производительность. Возможно джит просто не делает оптимизации, так как видит вызов метода, а не эту инструкцию.
Но тут уже надо делать подмену вызова на инструкцию при генерации кода. Он находится в ILEmitter.n.
R>Для примера тот же самый код, сгенерированный C# имеет размер 25 байт против 38 у Nemerle.
Ну, сами по себе байты на скорость не влияют. Немерл генерит лишние переменные (особенно в дебаге), но их выкидывает джит.
R>В общем, код надо реорганизовать таким образом: R>
R>int i = 0;
R>goto start;
R>again:
R> Console.WriteLine(args[i]);
R> i = i + 1;
R>start:
R> if (i < args.Length)
R> goto again;
R>
Ну, для foreach-а, можно конечно и прямо вот такое код сгенерировать. Но по уму нужно делать так, чтобы работал и "for" в который он переписывается.
Кстати, учти, что если в макросе используется макрос, то это макрос из старой версии макро-библиотеки (берущейся из бута). Так что просто поменять код макроса не выйдет. Нужно его бутсрапить (обновлять бут).
Я бы начал не с переписывания кода, а разобрался бы с заменой взова Length на инструкцию. Возможно джит ее и проверяет. Он не так туп и паттерн цикла долежен и так распозновать.
Делать оптимизацию для foreach-а особого смысла нет. Надо править макрос for. Сейчас там вот такой код:
Переписал с использованием goto , IL стал выглядеть почти как надо, но джиту не помогло. Мешает либо виртуальный вызов длины массива, либо callvirt instance int32 [mscorlib]System.Array::get_Length() хоть и инлайнится, но мешает оптимизации. Длина массива на каждой итерации грузится с памяти в регистр и сравнивается со счетчиком, плюс еще какое-то левое сравнение и вызов
Здравствуйте, VladD2, Вы писали:
VD>Ну, сами по себе байты на скорость не влияют. Немерл генерит лишние переменные (особенно в дебаге), но их выкидывает джит.
В общем случае ты не совсем прав: информация из CORINFO_METHOD_INFO (в том числе количество локальных переменных) может использоваться для вычисления рентабильности инлайнинга (для coreclr это DiscretionaryPolicy).
Ну про классику — на размер кода я думаю говорить не нужно, хотя по опять же EstimateCodeSize учитывает много вещей, в том числе и количество локалов. Поэтому безусловно лучше иметь минимально-оптимальный IL. Однако прям сейчас судя по конфигу — эти полиси по дефолту запрещены.
Здравствуйте, VladD2, Вы писали:
VD>Хочу сразу предупредить, что многие косяки немерловой генерации нивелируются оптимизациями в джите. Например, практика показала, что бороться с лишними переменными совершенно бесполезно. Джит их сам отлично устраняет (а может они просто ложатся в регистры), так что на скорости кода это не сказывается.
Это да, но иногда может помешать джиту заинлайнить метод, так как одна из его эвристик смотрит на размер метода, который может оказаться выше некоего порога, и тогда джит пометит метод как [FAILED: too many il bytes] или как non profitable.
VD>Учти, что foreach — это макрос. Так что "паттерн" для него генерируется не компилятором, а этим макросом. Находится он здесь. VD>Конкретно код для массивов генерируется здесь. Но там вроде как все цивильно: VD>Так что смотреть надо, скорее, макрос for. Его код находится там же.
Ага, это я уже нашел и внимательно читаю.
VD>Конечно, возможно, что дело в генераторе кода, но с начало нужно в этом убедиться. Для этого надо посмотреть типизированное дерево <skipped>
ОК, спасибо, сейчас посмотрю.
VD>А как ты смотрел ассемблер? Учти, что единственный достоверный способ проверить это — скомпилировать релизную версию и приатачиться к ней отладчиком во время выполнения (вызвать брэйк из кода). Сама программа должна при этом быть запущена без отладчика, иначе джит вырубет оптимизации. Ну, и далее надо смотреть ассемблер.
Да, так и смотрел.
R>>Для примера тот же самый код, сгенерированный C# имеет размер 25 байт против 38 у Nemerle. VD>Ну, сами по себе байты на скорость не влияют. Немерл генерит лишние переменные (особенно в дебаге), но их выкидывает джит.
Но, к сожалению, могут сказаться на выборе инлайнить или нет этот метод.
VD>Ну, для foreach-а, можно конечно и прямо вот такое код сгенерировать. Но по уму нужно делать так, чтобы работал и "for" в который он переписывается.
Да, надо именно for переписать.
VD>Кстати, учти, что если в макросе используется макрос, то это макрос из старой версии макро-библиотеки (берущейся из бута). Так что просто поменять код макроса не выйдет. Нужно его бутсрапить (обновлять бут).
VD>Я бы начал не с переписывания кода, а разобрался бы с заменой взова Length на инструкцию. Возможно джит ее и проверяет. Он не так туп и паттерн цикла долежен и так распозновать.
Когда я в последний раз смотрел на код джита, конкретно на место в котором определяется for-паттерн, то он там туп как пробка, ибо шаг влево, шаг вправо, и for он в лицо не узнает
Но я попробую сейчас протестировать немерловский IL с заменой Length, посмотрю, что выйдет. Потом уже и сам for можно будет переписать в if-do-while паттерн: Loop inversion, ибо джит не всегда почему-то это делает.
Здравствуйте, fddima, Вы писали:
F> В общем случае ты не совсем прав: информация из CORINFO_METHOD_INFO (в том числе количество локальных переменных) может использоваться для вычисления рентабильности инлайнинга (для coreclr это DiscretionaryPolicy). F> Ну про классику — на размер кода я думаю говорить не нужно, хотя по опять же EstimateCodeSize учитывает много вещей, в том числе и количество локалов. Поэтому безусловно лучше иметь минимально-оптимальный IL. Однако прям сейчас судя по конфигу — эти полиси по дефолту запрещены.
Конечно лучше быть здоровым и богатым, чем бедным и больным. И конечно лишние переменные это не блага.
Но я (и ни я один) делал множество тестов и разницы между Шаропом и Неменлом не видно в микроскоп. Даже в некоторых тестах Немерл вперед вырывается из-за косяков джита на которые нарывается шарповский компилятор и из-за того, что функциональный тип сделан в виде класса с более простой структурой. Но опять таки там разница не велика на сегодня.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Конечно лучше быть здоровым и богатым, чем бедным и больным. И конечно лишние переменные это не блага.
Я лишь показал что они могут попадать в оценки, хотя компилятор их и выкидывает. Более того — я не в курсе, обновляются ли оценки для уже скомпилированных методов. Что касается конкретных действий — то думаю ещё пару релизов net/coreclr можно смело не париться. А там... того глядишь и начнут оценивать иначе, например осилят AOT с profile-based кодогенерацией (мечты-мечты).
VD>Но я (и ни я один) делал множество тестов и разницы между Шаропом и Неменлом не видно в микроскоп. Даже в некоторых тестах Немерл вперед вырывается из-за косяков джита на которые нарывается шарповский компилятор и из-за того, что функциональный тип сделан в виде класса с более простой структурой. Но опять таки там разница не велика на сегодня.
С текущей политикой инлайнинга "лиж бы проперти аксессоры инлайнились" — он (инлайниг), и не может сильно влиять. В остальном же — IL/код вполне вменяемый — соответственно получаем закономерно нормальный результат. Остальное уже к TC, если ему не жалко времени на это.
Здравствуйте, rameel, Вы писали:
R>Но я попробую сейчас протестировать немерловский IL с заменой Length, посмотрю, что выйдет.
Результаты теста:
1. Надо обязательно менять вызов Length на соответствующую инструкцию. Джит устранил проверки выхода за границы.
2. Надо реорганизовать цикл for и исправить последовательность инструкций, чтобы джит вытащил args.Length в регистры
IL_0024: ldlen// вот эти 3 инструкции надо переписать в blt.s или в clt, brtrue.s
IL_0029: clt
IL_002b: brfalse IL_0035
IL_0030: br IL_0016
Внутри реализации goto метки именуются тупо по именам.
Тут похоже Влад при реализации схалтурил. Нужно переделать таким образом, чтобы цвета имён тоже учитывались.
R>Как побороть ошибку FSM\DFSMTransform.n(198,9,229,10): error : Label 'Next' (12583) multiply defined ? Пытался по всякому, не работает.
Интересно почему только с этим кодом проблема. Ибо у тебя должны ошибки по всему коду сыпаться.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали: WH>Почему у тебя goto $next но при этом label Next?
Остатки копипасты,я недостаточное количество раз Ctrl+Z нажал перед публикацией сюда А так я пробовал по разному WH>Внутри реализации goto метки именуются тупо по именам. WH>В качестве временного решения должно сработать WH>
R>>Как побороть ошибку FSM\DFSMTransform.n(198,9,229,10): error : Label 'Next' (12583) multiply defined ? Пытался по всякому, не работает. WH>Интересно почему только с этим кодом проблема. Ибо у тебя должны ошибки по всему коду сыпаться.
В разных случаях по разному, в основном компиляция заканчивается проекте peg-parser
Здравствуйте, rameel, Вы писали:
R>Как побороть ошибку FSM\DFSMTransform.n(198,9,229,10): error : Label 'Next' (12583) multiply defined ? Пытался по всякому, не работает.
Косяк в label-макре. Очень уж редко это дело используется. По сути всего один раз использовали в Нитре. Они там по именам в хэш-таблицу загоняются.
Т.е. вручную сгенерировать уникальные имена для лэйблов.
R>PS. Кстати, а что случилось со статьями? Все листинги в статьях по немерле (остальные статьи не смотрел) слиплись в одну нечитаемую кашу
Я вижу, что первый отступ улетает. А так вроде читабельно
Я тут этот вариант попробовал, и оказалось не все так радужно как на первый взгляд. При компиляции не тривиальных циклов (в Нитре, например) вылазит сообщение о нелокальном goto в выражениях.
Так что это дело надо как следует тестировать.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
def ary = array(1000);
Measure("After", () => {
def a = ary;
mutable s = 0;
unchecked {
for (mutable i = 0; i < 1_000_000; i++)
foreach (v in a)
s += v;
s;
}
});
Кстати, перед тем как отправить пуллреквест, хочу спросить. Я в методе emit_method_call добавил такое условие:
Здравствуйте, VladD2, Вы писали:
VD>Я тут этот вариант попробовал, и оказалось не все так радужно как на первый взгляд. При компиляции не тривиальных циклов (в Нитре, например) вылазит сообщение о нелокальном goto в выражениях.
У меня проект нитры скомпилировался только с такими сообщениями:
Build succeeded.
"F:\Projects\nitra\Common\BootTasks.proj" (BuildBoot target) (1) ->
"F:\Projects\nitra\Boot2\Nitra.Compiler\Nitra.Compiler.nproj" (Build target) (2) ->
"F:\Projects\nitra\Boot2\Nitra.Runtime\Nitra.Runtime.nproj" (default target) (3) ->
(CoreCompile target) ->
Parsing\ParseResult.n(45,131,45,135): warning : N168: a function parameter head was never used (defined in '.ctor(par
seSession : Nitra.ParseSession, sourceSnapshot : Nitra.SourceSnapshot, oldParseResult : Nitra.ParseResult, head : int,
tail : int) : void') [F:\Projects\nitra\Boot2\Nitra.Runtime\Nitra.Runtime.nproj]
Parsing\ParseResult.n(45,131,45,135): warning : hint: replace name with `_' or prefix it like `_bar' to avoid the war
ning [F:\Projects\nitra\Boot2\Nitra.Runtime\Nitra.Runtime.nproj]
2 Warning(s)
0 Error(s)
Или имеется в виду, что сообщения вылезли при компиляции одной из грамматик нитры?
Здравствуйте, VladD2, Вы писали:
VD>Я тут этот вариант попробовал, и оказалось не все так радужно как на первый взгляд. При компиляции не тривиальных циклов (в Нитре, например) вылазит сообщение о нелокальном goto в выражениях.
Хорошо бы понять почему так происходит, но у меня пока не хватает знаний по внутренностям компилятора, поэтому можно пока цикл for оставить как и раньше, так как джит вполне справился, остались только один 2 джампа, вместо одно на итерацию, но это мелочи. С этим можно и потом разобраться.