Как стать автором
Обновить

Комментарии 4

Вспомнил ещё, что JIT-x64 иногда умеет оптимизировать хвостовую рекурсию, что может приводить к неожиданно успешному выполнению очень глубоких рекурсивных вызовов, непредсказуемому влиянию на производительность и т.п. весёлым вещам.

(Интересно, кстати, что при этом будет в стектрейсе — будет ли рантайм наворачивать туда фреймы от хвостового вызова или нет.)

Кстати, кто знает, как в RjuJIT с tail recursion? Он умеет .tailcall или, может быть, сам умеет оптимизировать какие-то простые случаи?
Очень интересная тема. Постараюсь в ближайшее время заняться исследованием. Я свято верю, что RyuJIT должен оптимизировать хвостовую рекурсию. Если не умеет, то я буду грустить. По поводу стектрейса тоже интересный вопрос: если .NET умеет делать правильный StackTrace, то я плохо представляю как он это делает. Кстати говоря, в Java по этой причине от оптимизации отказались (StackTrace потеряется, это плохо, пусть лучше у нас всё будет работать медленно). У меня есть подозрение, что дело обстоит так: в Debug оптимизация не применяется и StackTrace считается правильно, а в Release ситуация скорее всего должна быть аналогична ситуации с инлайнингом функций — там же тоже по сути стек вызовов портится.
Только что проверил вот на такой простой программе:

	class Program
	{
		static int Recursive(int x)
		{
			if (x == int.MaxValue)
			{
				throw new Exception();
			}

			return Recursive(x + 1);
		}

		static void Main(string[] args)
		{
			int x = Recursive(0);
			Console.WriteLine(x);
		}
	}

В AnyCPU под 64-битной осью оптимизация не срабатывает, в дебаге — тоже. Если компилять под x64-only и в релиз — тогда начинает работать. Стек действительно портится — показывает, что упал в Main -> Recursive без отслеживания хвостовых вызовов. Ну что ж, нормально — лучше уж так, чем вообще без оптимизации.

Вообще говоря, мне не кажется, что TCO такая уж важная вещь в .NET именно по причине того, что она не гарантируется. Вот если она будет гарантирована стандартом в каких-то случаях, или же можно будет развесить какие-то атрибуты, которые бы проверяли её наличие и падали, если оптимизировать не получилось (типа @tailrec в Scala) — это было бы нормально. Моя мотивация проста — TCO это крайне важная особенность поведения кода, которая может сильно влиять на его быстродействие и характеристики потребления памяти, ну а писать рабочий код, который полагается на какие-то негарантированные оптимизации компилятора и ломается, если у компилятора или JIT'тера что-то там «не получилось» — это уж слишком, по-моему.
В AnyCPU под 64-битной осью оптимизация не срабатывает,

А галочка «Prefer 32 bit» не стоит

по причине того, что она не гарантируется

Ну а как её можно гарантировать? Там же пару байт поменяешь, так TCO станет ухудшать код или вовсе станет невозможна. Подвергать оптимизации метод нужно только тогда, когда он отвечает ряду специфических условий, за определение которых и должен отвечать JIT. А вот насколько хорошо он это делает — другой вопрос. В общем, я займусь на днях исследованием.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий