Runtime: Внедрить многоуровневую JIT

Созданный на 14 апр. 2016  ·  63Комментарии  ·  Источник: dotnet/runtime

Почему .NET JIT не является многоуровневым?

JIT преследует две основные цели: быстрое время запуска и высокая пропускная способность в устойчивом состоянии.

Сначала эти цели кажутся противоречащими друг другу. Но с двухуровневой схемой JIT они оба достижимы:

  1. Весь код начинается с интерпретации. Это приводит к чрезвычайно быстрому времени запуска (быстрее, чем RyuJIT). Пример: метод Main почти всегда холодный, а его отбрасывание — пустая трата времени.
  2. Код, который часто выполняется, обрабатывается с помощью очень качественного генератора кода. Очень немногие методы будут горячими (1%?). Поэтому пропускная способность высококачественной JIT не имеет большого значения. Он может потратить столько же времени, сколько компилятор C тратит на создание очень хорошего кода. Кроме того, он может _предполагать_, что код горячий. Он может встраиваться как сумасшедший и разворачивать циклы. Размер кода не имеет значения.

Достижение этой архитектуры не кажется слишком затратным:

  1. Написание интерпретатора кажется дешевым по сравнению с JIT.
  2. Должен быть создан генератор кода высокого качества. Это может быть VC или проект LLC.
  3. Должна быть возможность перехода от интерпретируемого работающего кода к скомпилированному коду. Это возможно; JVM делает это. Это называется заменой стека (OSR).

Эта идея реализуется командой JIT?

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

категория: пропускная способность
тема: большие ставки
уровень мастерства: эксперт
Стоимость: очень большая

area-CodeGen-coreclr enhancement optimization tenet-performance

Самый полезный комментарий

С последними PR (https://github.com/dotnet/coreclr/pull/17840, https://github.com/dotnet/sdk/pull/2201) у вас также есть возможность указать многоуровневую компиляцию в качестве runtimeconfig. json или свойство проекта msbuild. Использование этой функции потребует от вас самых последних сборок, в то время как переменная среды существует уже некоторое время.

Все 63 Комментарий

@GSPP Tiering — постоянная тема в разговорах о планировании. У меня сложилось впечатление, что это вопрос _когда_, а не _если_, если это утешает. Что касается _почему_, этого еще нет, я думаю, это потому, что исторически предполагаемые потенциальные выгоды не оправдывали дополнительных ресурсов разработки, необходимых для управления повышенной сложностью и риском нескольких режимов codegen. Я действительно должен позволить экспертам говорить об этом, поэтому я добавлю их.

/cc @dotnet/jit-contrib @russellhadley

Почему-то я сомневаюсь, что это все еще актуально в мире crossgen/ngen, Ready to Run и corert.

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

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

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

Я согласен, но для исправления этого не требуются такие вещи, как переводчик. Просто хороший кроссген-компилятор, будь то лучше RyuJIT или LLILC.

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

Это правда, что динамически генерируемый код является одной из мотиваций, но также верно и то, что статический компилятор никогда не будет иметь доступа ко всей информации, доступной во время выполнения. Мало того, даже когда он предполагает (например, на основе информации о профиле), статическому компилятору гораздо труднее сделать это при наличии модального или внешнего контекстно-зависимого поведения.

Веб-приложения не должны нуждаться в обработке в стиле ngen. Он плохо вписывается в конвейер развертывания. Создание большого двоичного файла занимает много времени (даже если почти весь код динамически мертв или холоден).

Кроме того, при отладке и тестировании веб-приложения вы не можете полагаться на то, что ngen даст вам реалистичную производительность.

Кроме того, я 2-й пункт Кэрол об использовании динамической информации. Уровень интерпретации может профилировать код (ответвления, счетчик циклов, динамические цели диспетчеризации). Это идеальное совпадение! Сначала соберите профиль, потом оптимизируйте.

Многоуровневое решение решает все в каждом сценарии навсегда. Приблизительно говоря :) Это действительно может привести нас к обещанию JIT: достичь производительности _превосходящей_ то, что может сделать компилятор C.

Текущая реализация RyuJIT в ее нынешнем виде достаточно хороша для Уровня 1... Вопрос в следующем: имеет ли смысл использовать JIT для экстремальной оптимизации Уровня 2 для горячих путей, которые могут выполняться постфактум? По сути, когда мы обнаруживаем или имеем достаточно информации во время выполнения, чтобы знать, что что-то горячее, или когда нас просят использовать это вместо этого с самого начала.

RyuJIT на сегодняшний день достаточно хорош, чтобы быть уровнем 1. Проблема в том, что интерпретатор будет иметь _намного_ более быстрое время запуска (по моей оценке). Вторая проблема заключается в том, что для перехода на уровень 2 локальное состояние выполнения кода уровня 1 должно быть перенесено в новый код уровня 2 (OSR). Это требует изменений RyuJIT. Я думаю, что добавление интерпретатора было бы более дешевым путем с меньшей задержкой при запуске.

Еще более дешевым вариантом было бы не заменять работающий код кодом уровня 2. Вместо этого дождитесь естественного возврата кода уровня 1. Это может быть проблемой, если код входит в продолжительный горячий цикл. Таким образом, он никогда не достигнет уровня производительности 2.

Я думаю, что это было бы неплохо и могло бы использоваться в качестве стратегии v1. Доступны смягчающие идеи, такие как атрибут, помечающий метод как горячий (он должен существовать в любом случае, даже при текущей стратегии JIT).

@GSPP Это правда, но это не значит, что вы не узнаете об этом при следующем запуске. Если Jitted-код и инструментарий становятся постоянными, то при втором выполнении вы все равно получите код уровня 2 (за счет некоторого времени запуска) --- на этот раз мне лично все равно, поскольку я пишу в основном серверный код.

Написание интерпретатора кажется дешевым по сравнению с JIT.

Может, вместо того, чтобы писать новый интерпретатор, было бы целесообразно запустить RyuJIT с отключенными оптимизациями? Достаточно ли это улучшит время запуска?

Должен быть создан генератор кода высокого качества. Это может быть ВК

Вы говорите о C2, бэкенде Visual C++? Это не кроссплатформенный и не с открытым исходным кодом. Я сомневаюсь, что исправление обоих произойдет в ближайшее время.

Хорошая идея с отключением оптимизаций. Однако проблема с OSR остается. Не уверен, насколько сложно сгенерировать код, который позволяет среде выполнения получать состояние архитектуры IL (локальные переменные и стек) во время выполнения в безопасной точке, копировать его в код второго уровня и возобновлять выполнение уровня 2 в середине функции. JVM делает это, но кто знает, сколько времени ушло на это.

Да, я говорил о С2. Кажется, я помню, что по крайней мере один из JIT для настольных компьютеров основан на коде C2. Вероятно, не работает для CoreCLR, но, возможно, для рабочего стола. Я уверен, что Microsoft заинтересована в выравнивании кодовых баз, так что это, вероятно, действительно отсутствует. LLVM кажется отличным выбором. Я считаю, что несколько языков в настоящее время заинтересованы в том, чтобы заставить LLVM работать с GC и с управляемыми средами выполнения в целом.

LLVM кажется отличным выбором. Я считаю, что несколько языков в настоящее время заинтересованы в том, чтобы заставить LLVM работать с GC и с управляемыми средами выполнения в целом.

Интересная статья на эту тему: Apple недавно переместила последний уровень своего JavaScript JIT из LLVM: https://webkit.org/blog/5852/introduction-the-b3-jit-compiler/ . Скорее всего, мы столкнемся с проблемами, похожими на те, с которыми столкнулись они: медленное время компиляции и незнание LLVM исходного языка.

В 10 раз медленнее, чем RyuJIT, было бы вполне приемлемо для 2-го уровня.

Я не думаю, что незнание исходного языка (что вызывает серьезную озабоченность) присуще архитектуре LLVM. Я считаю, что многие команды заняты переводом LLVM в состояние, в котором знание исходного языка может быть более легко использовано. _Все_ языки высокого уровня, отличные от C, имеют эту проблему при компиляции на LLVM.

Проект WebKIT FTL/B3 находится в более сложном положении для достижения успеха, чем .NET, потому что они должны преуспеть при выполнении кода, который _всего_ потребляет несколько сотен миллисекунд времени, а затем завершается. Такова природа рабочих нагрузок JavaScript, управляющих веб-страницами. .NET не в этом месте.

@GSPP Я уверен, что вы, наверное, знаете о LLILC . Если нет, взгляните.

Мы некоторое время работали над поддержкой LLVM для концепций CLR и вложили средства в улучшения как EH, так и GC. В обоих случаях еще многое предстоит сделать. Кроме того, некоторые из них требуют неизвестного объема работы, чтобы оптимизация работала должным образом в присутствии GC.

LLILC, похоже, застопорился. Это?
18 апреля 2016 г., 19:32, «Энди Эйерс» [email protected] написал:

@GSPP https://github.com/GSPP Я уверен, что вы, наверное, знаете о LLILC
https://github.com/dotnet/llilc. Если нет, взгляните.

Мы некоторое время работали над поддержкой LLVM для концепций CLR и
инвестировал в улучшения как EH, так и GC. Еще совсем немного, чтобы сделать на
оба. Помимо этого, некоторые работы по оптимизации неизвестны.
правильно работает в присутствии GC.


Вы получаете это, потому что вы прокомментировали.
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/dotnet/coreclr/issues/4331#issuecomment -211630483

@drbo - LLILC на данный момент отошла на второй план - команда MS сосредоточилась на том, чтобы получить больше целей, поднятых в RyuJIT, а также на устранении проблем, возникающих по мере того, как CoreCLR приближается к выпуску, и это заняло почти все наше время. В моем списке TODO (в свободное время) я должен написать пост об извлеченных уроках, основанный на том, как далеко мы (в настоящее время) продвинулись с LLILC, но я еще не добрался до этого.
Что касается уровней, эта тема вызвала много дискуссий на протяжении многих лет. Я думаю, что, учитывая некоторые новые рабочие нагрузки, а также новое добавление готовых к запуску образов с возможностью управления версиями, мы по-новому взглянем на то, как и где распределять уровни.

@russellhadley , у тебя было свободное время, чтобы написать пост?

Я предполагаю, что должно быть что-то о не продвигаемых слотах стека и gcroots, нарушающих оптимизацию и медленном времени джиттинга... Мне лучше взглянуть на код проекта.

Мне также интересно, возможно ли и выгодно ли напрямую перейти в SelectionDAG и выполнить часть бэкэнда LLVM. Хоть какой-нибудь глазок и копирование... если например продвижение gcroot в регистры поддерживается в LLILC

Мне любопытно узнать о статусе LLILC, в том числе о текущих узких местах и ​​о том, как он справляется с RyuJIT. LLVM, будучи полноценным "промышленным" компилятором, должен иметь большое количество оптимизаций, доступных для OSS. В списке рассылки было несколько разговоров о более эффективной и быстрой сериализации/десериализации формата биткода; Мне интересно, является ли это полезной вещью для LLILC.

Были ли еще мысли по этому поводу? @russellhadley Выпущен CoreCLR, а RyuJIT портирован (как минимум) на x86 — что дальше?

См. dotnet/coreclr#10478 для начала работы над этим.

Также dotnet/coreclr#12193

@noahfalk , не могли бы вы предоставить способ указать среде выполнения принудительно выполнить компиляцию уровня 2 сразу из самого управляемого кода? Многоуровневая компиляция — очень хорошая идея для большинства случаев использования, но я работаю над проектом, где время запуска не имеет значения, но необходимы пропускная способность и стабильная задержка.

Вне моей головы это может быть либо:

  • новый параметр в файле конфигурации, переключатель типа <gcServer enabled="true" /> , чтобы заставить JIT всегда пропускать уровень 1
  • или что-то вроде RuntimeHelpers.PrepareMethod , который будет вызываться кодом для всех методов, являющихся частью горячего пути (мы используем это для предварительного JIT нашего кода при запуске). Это имеет то преимущество, что дает большую степень свободы разработчику, который должен знать, что такое горячий путь. Дополнительная перегрузка этого метода была бы просто прекрасной.

Конечно, немногие проекты выиграют от этого, но меня немного беспокоит то, что JIT пропускает оптимизации по умолчанию, и я не могу сказать, что вместо этого я бы предпочел, чтобы он сильно оптимизировал мой код.

Я знаю, что вы написали следующее в дизайн-документе:

Добавьте новый этап конвейера сборки, доступный из API управляемого кода, для выполнения самоизменяющегося кода.

Звучит очень интересно 😁 но я не совсем уверен, что это соответствует тому, о чем я здесь спрашиваю.


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

Это должно произойти, когда метод вызывается достаточное количество раз, или если цикл
выполняет достаточное количество итераций (замена на этапе). Это должно произойти
асинхронно в фоновом потоке.

29 июня 2017 г., 19:01, «Лукас Тржесневски» [email protected]
написал:

@noahfalk https://github.com/noahfalk , не могли бы вы указать способ
чтобы сказать среде выполнения, чтобы принудительно выполнить компиляцию уровня 2 сразу из
сам управляемый код? Многоуровневая компиляция — очень хорошая идея для большинства
варианты использования, но я работаю над проектом, где время запуска не имеет значения
но пропускная способность и стабильная задержка необходимы.

Вне моей головы это может быть либо:

  • новая настройка в конфигурационном файле, переключатель типа enabled="true" /> чтобы заставить JIT всегда пропускать уровень 1
  • или что-то вроде RuntimeHelpers.PrepareMethod, что было бы
    вызывается кодом на всех методах, которые являются частями горячего пути (мы
    используя это для предварительной JIT нашего кода при запуске). Это имеет преимущество
    предоставление большей степени свободы разработчику, который должен знать, что
    горячий путь есть. Дополнительная перегрузка этого метода была бы просто прекрасной.

Конечно, немногие проекты выиграют от этого, но меня немного беспокоит
JIT пропускает оптимизацию по умолчанию, и я не могу это сказать
Вместо этого я бы предпочел, чтобы он сильно оптимизировал мой код.

Я знаю, что вы написали следующее в дизайн-документе:

Добавить новый этап конвейера сборки, доступный из API управляемого кода для выполнения.
самомодифицирующийся код.

Звучит очень интересно 😁 но я не совсем уверен, что это охватывает

Я спрашиваю здесь.

Также связанный с этим вопрос: когда сработает второй проход JIT? Когда
метод будет вызываться в n -й раз? Произойдет ли JIT
поток, в котором должен был выполняться метод? Если это так, это повлечет за собой
задержка перед вызовом метода. Если вы реализуете более агрессивный
оптимизации эта задержка будет больше, чем текущее время JIT, которое
может стать проблемой.


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/dotnet/coreclr/issues/4331#issuecomment-312130920 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AGGWB2WbZ2qVBjRIQWS86MStTSa1ODfoks5sJCzOgaJpZM4IHWs8
.

@ltrzesniewski - Спасибо за отзыв! Конечно, я надеюсь, что многоуровневая компиляция полезна для подавляющего большинства проектов, но компромиссы могут быть не идеальными для каждого проекта. Я размышлял, что мы бы оставили переменную среды на месте, чтобы отключить многоуровневое джиттинг, и в этом случае вы сохраните поведение во время выполнения, которое у вас есть сейчас, с более высоким качеством (но медленнее для генерации) джиттинга впереди. Является ли установка переменной среды чем-то разумным для вашего приложения? Возможны и другие варианты, я просто склоняюсь к переменной окружения, потому что это один из самых простых вариантов конфигурации, которые мы можем использовать.

Также связанный с этим вопрос: когда сработает второй проход JIT?

Это политика, которая, скорее всего, со временем изменится. Текущая реализация прототипа использует упрощенную политику: «Метод вызывался >= 30 раз»
https://github.com/dotnet/coreclr/blob/master/src/vm/tieredcompilation.cpp#L89
https://github.com/dotnet/coreclr/blob/master/src/vm/tieredcompilation.cpp#L122

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

@DemiMarie - Сейчас у нас нет ничего, что отслеживало бы итерации цикла как часть политики, но это интересная перспектива на будущее.

Были ли мысли о профилировании, спекулятивной оптимизации и
деоптимизация? JVM делает все это.

29 июня 2017 г., 20:58, «Ноа Фальк» [email protected] написал:

@ltrzesniewski https://github.com/ltrzesniewski - Спасибо за
Обратная связь! Конечно, я надеюсь, что многоуровневая компиляция будет полезна для
большинства проектов, но компромиссы могут быть не идеальными для каждого проекта.
Я размышлял, что мы оставим переменную окружения на месте, чтобы
отключите многоуровневое джиттинг, и в этом случае вы сохраните поведение во время выполнения, которое вы
теперь с более высоким качеством (но медленнее генерируются), прыгая вперед. Является
установить переменную среды что-то разумное для вашего приложения?
Возможны и другие варианты, я просто тяготею к экологии
переменная, потому что это один из самых простых вариантов конфигурации, которые мы можем использовать.

Также связанный с этим вопрос: когда сработает второй проход JIT?

Это политика, которая, скорее всего, со временем изменится. Электрический ток
реализация прототипа использует упрощенную политику: «Был ли метод
звонили >= 30 раз"
https://github.com/dotnet/coreclr/blob/master/src/vm/
многоуровневая компиляция.cpp#L89
https://github.com/dotnet/coreclr/blob/master/src/vm/
многоуровневая компиляция.cpp#L122

Удобно, что эта очень простая политика предлагает хорошее улучшение производительности на
моя машина, даже если это всего лишь предположение. Для того, чтобы создать лучшую политику
нам нужно получить отзывы об использовании в реальном мире, и получение этих отзывов
потребуется, чтобы основная механика была достаточно надежной в различных
сценарии. Итак, мой план состоит в том, чтобы сначала улучшить надежность/совместимость, а затем
больше исследований для настройки политики.

@DemiMarie https://github.com/demimarie — у нас нет ничего такого
теперь отслеживает итерации цикла как часть политики, но это интересно
перспектива на будущее.


Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/dotnet/coreclr/issues/4331#issuecomment-312146470 ,
или заглушить тему
https://github.com/notifications/unsubscribe-auth/AGGWB5m2qCnOKJsaXFCFigI3J6Ql8PMQks5sJEgZgaJpZM4IHWs8
.

@noahfalk Переменная окружения определенно не является решением, которое позволило бы вам управлять этим приложением за приложением. Для серверных/служебных приложений вам обычно все равно, сколько времени требуется для запуска приложения (я знаю, что мы не за счет производительности). При разработке механизма базы данных, я могу сказать вам из первых рук, нам нужно, чтобы он работал как можно быстрее с самого начала и даже на обычных путях или тестах, выполненных потенциальными новыми клиентами.

С другой стороны, учитывая, что в типичных средах время безотказной работы может измеряться неделями, нас не волнует, займет ли оно хотя бы 30 секунд; что нас действительно волнует, так это то, что принуждение пользователя к общему переключению (все или ничего) или даже то, что пользователь должен заботиться об этом (например, установленное по умолчанию из файлов конфигурации), — это 10 шагов назад.

Не поймите меня неправильно, я с нетерпением жду многоуровневого JIT, потому что он открывает путь к высокой производительности, требующей столько времени, сколько вам нужно для оптимизации на уровне JIT. Я даже сам предложил это давным-давно в неофициальных беседах с некоторыми инженерами JIT, и вы уже заметили это. Но способ настроить поведение для всего приложения (не всей системы) является (по крайней мере, для нас) важным показателем качества для этой конкретной функции.

EDIT: некоторые проблемы со стилем.

@redknightlois - Спасибо за продолжение

Переменная окружения определенно не является решением, которое позволит вам управлять этим приложением за приложением.

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

set COMPLUS_EXPERIMENTAL_TieredCompilation=1
MyApp.exe
set COMPLUS_EXPERIMENTAL_TieredCompilation=0

что нас действительно заботит, так это то, что [мы не] заставляем пользователя ... заботиться об этом

Я так понимаю, вам нужен параметр конфигурации, который может указывать разработчик приложения, а не человек, запускающий приложение? Одна из возможностей с env var — сделать так, чтобы пользователь запускал тривиальную оболочку (например, пакетный скрипт), которая запускает приложение coreclr, хотя я признаю, что это кажется немного неэлегантным. Я открыт для альтернатив и не настроен на env var. Просто чтобы задать ожидания, это не та область, в которой я буду активно работать над дизайном в самом ближайшем будущем, но я согласен с тем, что иметь соответствующую конфигурацию важно.

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

Были ли мысли о профилировании, спекулятивной оптимизации и
деоптимизация?

@DemiMarie - Они, безусловно, всплывали в разговорах, и я думаю, что многие люди взволнованы тем, что многоуровневая компиляция открывает эти возможности. Говоря только о себе, я стараюсь сосредоточиться на предоставлении базовых возможностей многоуровневой компиляции, прежде чем ставить перед собой более высокие цели. Другие люди в нашем сообществе, вероятно, уже опережают меня в других приложениях.

@noahfalk Да, неэлегантность также означает, что обычный процесс его запуска может (и, скорее всего, будет) стать подверженным ошибкам, и в этом, по сути, проблема (единственный способ быть полностью уверенным, что никто не испортит, - это делать это в масштабах всей системы). Альтернативой, которая, как мы знаем, работает, является то, что таким же образом вы можете настроить, если вы собираетесь использовать сборщик мусора сервера с записью в app.config , вы можете сделать то же самое с многоуровневой компиляцией (по крайней мере, до тех пор, пока многоуровневая система может постоянно превосходить производительность в устойчивом состоянии). Будучи JIT, вы также можете сделать это для каждой сборки, используя assembly.config , и вы получите степень возможностей, которых в настоящее время не существует, если другие ручки также могут быть выбраны таким же образом.

Переменные среды часто задаются для каждого пользователя или системы, что потенциально негативно влияет на все такие процессы в нескольких версиях среды выполнения. Конфигурационный файл для каждого приложения кажется гораздо лучшим решением (даже если для каждого пользователя/системы также доступен) — что-то вроде значений конфигурации рабочего стола, которые можно установить в app.config, но также использовать env vars или реестр .

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

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

Итак, позвольте мне указать на некоторые возможные осложнения....

Вполне возможно, что JIT второго уровня загрузит себя поверх наблюдений за поведением кода, созданного JIT первого уровня. Таким образом, обход jit первого уровня и прямой запрос jit второго уровня может вообще не сработать или не сработать так же хорошо, как просто позволить многоуровневому распределению идти своим чередом. Возможно, вариант «обхода уровней», как бы он ни был реализован, в конечном итоге дал бы код, подобный коду, который jit создает по умолчанию сегодня, а не код, который мог бы создать jit второго уровня.

JIT второго уровня может быть настроен таким образом, что его выполнение на большом наборе методов вызовет относительно медленное время jit (поскольку мы ожидаем, что относительно небольшое количество методов в конечном итоге будет обработано JIT второго уровня, и мы ожидаем, что jit второго уровня проведет более тщательную оптимизацию). Мы еще не знаем правильных компромиссов.

Что, как говорится...

Я думаю, что атрибут метода «агрессивной оптимизации» имеет смысл — он требует, чтобы jit вел себя примерно так же, как jit второго уровня мог бы вести себя для определенных методов, и, возможно, пропуская эти методы во время предварительного создания кода (поскольку код с предварительным редактированием работает медленнее, чем код с джиттингом, особенно для R2R). Но применение этого понятия ко всей сборке или ко всем сборкам в приложении не кажется столь привлекательным.

Если вы возьмете то, что происходит в нативных компиляторах, в качестве подходящей аналогии, компромисс между производительностью и временем компиляции/размером кода может стать довольно плохим при более высоких уровнях оптимизации, например, компиляция в 10 раз дольше для совокупного повышения производительности на 1-2%. Ключом к решению головоломки является знание того, какие методы имеют значение, и единственный способ сделать это — либо программистам, либо системе, которая сама догадается об этом.

@AndyAyersMS Думаю, вы попали в точку. JIT-обработка атрибута «агрессивной оптимизации», вероятно, решит большинство проблем, связанных с отсутствием достаточной информации для JIT-компиляции для создания более качественного кода при изоляции без того, чтобы у JIT-компилятора первого уровня было время предоставить эту обратную связь.

Атрибут @redknightlois не будет работать, если мы хотим больше уровней: - T3 JIT, T4 JIT, ... Я не уверен, что двух уровней недостаточно, но мы должны хотя бы рассмотреть эту возможность.

Было бы здорово иметь возможность использовать что-то похожее на MPGO , чтобы начать работать с джиттинговым кодом второго уровня. Быстрая перемотка первого уровня вместо полного его обхода.

@AndyAyersMS , упростил ли интеграцию LLVM в CLR тот факт, что Azul реализовал управляемый JIT для JVM с использованием LLVM? По-видимому, в процессе изменения были отправлены вверх по течению в LLVM.

К вашему сведению, я создал ряд рабочих элементов для определенной работы, которую нам нужно выполнить, чтобы начать многоуровневую работу с нуля (#12609, dotnet/coreclr#12610, dotnet/coreclr#12611, dotnet/coreclr#12612, dotnet/coreclr №12617). Если ваш интерес напрямую связан с одним из них, не стесняйтесь добавлять к ним свои комментарии. Я предполагаю, что обсуждение любых других тем останется здесь, или любой может создать проблему для конкретной подтемы, если есть достаточный интерес, чтобы выделить ее отдельно.

@MendelMonteiro Предоставление данных обратной связи в стиле MPGO при джиттинге, безусловно, является вариантом (в настоящее время мы можем считывать эти данные только при предварительном джитинге). Существуют различные ограничения на то, что можно инструментировать, поэтому не все методы можно обрабатывать таким образом, есть и другие ограничения, на которые нам нужно обратить внимание (например, нет данных обратной связи для встроенных), инструментальные и обучающие прогоны, необходимые для создания данные MPGO являются барьером для многих пользователей, а данные MPGO могут совпадать или не совпадать с теми, что были бы у нас при загрузке с первого уровня, но идея, безусловно, заслуживает внимания.

Что касается верхнего уровня, основанного на LLVM, очевидно, что мы в некоторой степени изучили это с LLILC, и в то время мы часто общались с людьми из Azul, поэтому мы знакомы со многими вещами, которые они делали в LLVM, чтобы сделать его более подходящим для компиляции языков с точным сборщиком мусора.

Были (и, вероятно, все еще есть) существенные различия в поддержке LLVM, необходимой для CLR, по сравнению с тем, что необходимо для Java, как в GC, так и в EH, а также в ограничениях, которые необходимо наложить на оптимизатор. Приведу только один пример: CLR GC в настоящее время не допускает управляемых указателей, указывающих на конец объектов. Java обрабатывает это с помощью базового/производного парного механизма отчетов. Нам нужно либо внедрить поддержку такого рода парных отчетов в CLR, либо ограничить проходы оптимизатора LLVM, чтобы никогда не создавать такие указатели. Вдобавок ко всему, JIT LLILC был медленным, и мы не были уверены, какое качество кода он может обеспечить.

Таким образом, выяснение того, как LLILC может вписаться в потенциальный многоуровневый подход, которого еще не существовало, казалось (и все еще кажется) преждевременным. Идея на данный момент состоит в том, чтобы добавить многоуровневость во фреймворк и использовать RyuJit для jit второго уровня. По мере того, как мы узнаем больше, мы можем обнаружить, что действительно есть место для высокоуровневых шуток, или, по крайней мере, лучше понять, что еще нам нужно сделать, прежде чем такие вещи обретут смысл.

@AndyAyersMS Возможно, вы также можете внести необходимые изменения в LLVM, а не обойти его ограничения.

Работает ли многоядерный JIT и его оптимизация профиля с coreclr?

@benaadams - Да, многоядерный JIT работает. Я не помню, в каких (если есть) сценариях, где он включен по умолчанию, но вы можете включить его через конфигурацию: https://github.com/dotnet/coreclr/blob/master/src/inc/clrconfigvalues.h# L548

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

Я имею в виду следующее: если функция вызывается много раз, параметры как:

  • увеличить количество встроенных инструкций
  • использовать более «продвинутый» распределитель регистров (LLVM-подобный колоратор с возвратом или полный колоризатор)
  • сделать больше проходов оптимизации, может быть, какой-то специализированный с местным знанием. Например: разрешить замену полного выделения объекта на выделение стека, если объект объявлен в методе и не назначен в теле более крупной встроенной функции.
  • используйте PIC для большинства пораженных объектов, где CHA невозможен. Даже StringBuilder, например, скорее всего, не будет переопределен, код может быть помечен как каждый раз, когда он был поражен StringBuilder, все методы, вызываемые внутри, могут быть безопасно девиртуализированы, а перед доступом SB устанавливается защита типа.

Было бы также очень приятно, но, может быть, это моя мечта наяву, чтобы CompilerServices предлагала «расширенный компилятор», который был бы доступен через код или метаданные, поэтому такие места, как игры или торговые платформы, могли бы выиграть, запустив компиляция заранее, какие классы и методы должны быть "более глубоко скомпилированы". Это не NGen, но если многоуровневый компилятор не обязательно возможен (желателен), по крайней мере, чтобы можно было использовать более тяжелый оптимизированный код для критических частей, которым нужна эта дополнительная производительность. Конечно, если платформа не предлагает серьезных оптимизаций (скажем, Mono), вызовы API будут в основном NO-OP.

Благодаря тяжелой работе @noahfalk , @kouvel и других у нас есть прочная основа для многоуровневого распределения.

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

Где-то описано текущее поведение? Я нашел только это, но это больше о деталях реализации, а не о многоуровневом уровне.

Я полагаю, что скоро у нас будет своего рода итоговый отчет с некоторыми данными, которые мы собрали.

Многоуровневость можно включить в версии 2.1, установив COMPlus_TieredCompilation=1 . Если вы попробуете это, пожалуйста, сообщите, что вы найдете....

С последними PR (https://github.com/dotnet/coreclr/pull/17840, https://github.com/dotnet/sdk/pull/2201) у вас также есть возможность указать многоуровневую компиляцию в качестве runtimeconfig. json или свойство проекта msbuild. Использование этой функции потребует от вас самых последних сборок, в то время как переменная среды существует уже некоторое время.

Как мы уже обсуждали с @jkotas, многоуровневая JIT может сократить время запуска. Работает ли это, когда мы используем нативные изображения?
Мы провели измерения для нескольких приложений на телефоне с Tizen, и вот результаты:

Системные библиотеки DLL|Библиотеки приложений|Многоуровневые|время, с
-----------|--------|------|--------
R2R |R2R |нет |2,68
R2R | R2R | да | 2,61 (-3%)
R2R |нет |нет |4.40
R2R |нет |да |3,63 (-17%)

Мы также проверим режим FNV, но, похоже, он работает хорошо, когда нет изображений.

копия @gbalykov @nkaretnikov2

К вашему сведению, многоуровневая компиляция теперь используется по умолчанию для .NET Core: https://github.com/dotnet/coreclr/pull/19525 .

@alpencolt , улучшения времени запуска могут быть меньше при использовании компиляции AOT, такой как R2R. Улучшение времени запуска в настоящее время происходит за счет более быстрой компоновки с меньшим количеством оптимизаций, а при использовании компиляции AOT будет меньше JIT. Некоторые методы не создаются заранее, например некоторые универсальные методы, заглушки IL и другие динамические методы. Некоторые дженерики могут извлечь выгоду из многоуровневого запуска во время запуска даже при использовании компиляции AOT.

Я собираюсь закрыть эту проблему, так как с фиксацией @kouvel я думаю, что достиг вопроса в заголовке: D Люди могут продолжать обсуждение и / или открывать новые проблемы по более конкретным темам, таким как запрошенные улучшения, вопросы или конкретные исследования. Если кто-то думает, что он закрыт преждевременно, конечно, дайте нам знать.

@kouvel Извините, что комментирую закрытую проблему. Интересно, при использовании AOT-компиляции, такой как crossgen, приложение по-прежнему выиграет от компиляции второго уровня для путей кода горячих точек?

@daxian-dbw да, очень; во время выполнения Jit может выполнять кросс-ассемблерное встраивание (между dll); устранение переходов на основе констант времени выполнения ( readonly static ); и т.д

@benaadams А хорошо разработанный компилятор AOT не может?

Я нашел некоторую информацию об этом на https://blogs.msdn.microsoft.com/dotnet/2018/08/02/tiered-compilation-preview-in-net-core-2-1/ :

предварительно скомпилированные образы имеют ограничения версий и инструкций ЦП, которые запрещают некоторые типы оптимизации. Для любых методов в этих образах, которые часто вызываются многоуровневой компиляцией, JIT запрашивает создание оптимизированного кода в фоновом потоке, который заменит предварительно скомпилированную версию.

Да, это пример «не очень хорошо спроектированного AOT». 😛

предварительно скомпилированные образы имеют ограничения версий и инструкций ЦП, которые запрещают некоторые типы оптимизации.

Одним из примеров являются методы, использующие встроенные аппаратные средства. Компилятор AOT (кроссген) просто предполагает SSE2 в качестве цели codegen для x86/x64, поэтому все методы, использующие встроенное аппаратное обеспечение, будут отклонены crossgen и скомпилированы JIT, который знает базовую информацию об оборудовании.

А хорошо спроектированный компилятор AOT не может?

Компилятору AOT требуется оптимизация времени компоновки (для встраивания кросс-сборки) и оптимизация на основе профиля (для констант времени выполнения). Между тем, компилятору AOT требуется «основная» информация об оборудовании (например, -mavx2 в gcc/clang) во время сборки кода SIMD.

Одним из примеров являются методы, использующие встроенные аппаратные средства. Компилятор AOT (кроссген) просто предполагает SSE2 в качестве цели codegen для x86/x64, поэтому все методы, использующие встроенное аппаратное обеспечение, будут отклонены crossgen и скомпилированы JIT, который знает базовую информацию об оборудовании.

Чего ждать? Я не совсем понимаю здесь. Почему компилятор AOT отвергает встроенные функции?

А хорошо спроектированный компилятор AOT не может?

Компилятору AOT требуется оптимизация времени компоновки (для встраивания кросс-сборки) и оптимизация на основе профиля (для констант времени выполнения). Между тем, компилятору AOT требуется «основная» информация об оборудовании (например, -mavx2 в gcc/clang) во время сборки кода SIMD.

Да, как я уже сказал, «хорошо спроектированный AOT-компилятор». 😁

@masonwheeler другой сценарий; crossgen — это AoT, который работает с Jit и позволяет обслуживать/исправлять dll без необходимости полной перекомпиляции и распространения приложения. Он предлагает лучшую генерацию кода, чем уровень 0, с более быстрым запуском, чем уровень 1; но не является платформо-нейтральным.

Tier0, crossgen и Tier1 работают вместе как сплоченная модель в coreclr

Для встроенной кросс-ассемблирования (не Jit) потребуется компиляция статически связанного исполняемого файла с одним файлом, а также полная перекомпиляция и повторное распространение приложения для исправления любой используемой библиотеки, а также таргетинг на конкретную платформу (какая версия SSE, Avx и т. д. для использования; самая низкая общая или производственная версия для всех?).

corert будет использовать AoT в этом стиле приложений.

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

например, любой код, использующий такой метод, как (где Jit уровня 1 удалит все if s)

readonly static _numProcs = Environment.ProcessorCount;

public void DoThing()
{
    if (_numProcs == 1) 
    {
       // Single proc path
    }
    else if (_numProcs == 2) 
    {
       // Two proc path
    }
    else
    {
       // Multi proc path
    }
}

@бенаадамс

Для встроенной кросс-ассемблирования (не Jit) потребуется компиляция статически связанного исполняемого файла с одним файлом, а также полная перекомпиляция и повторное распространение приложения для исправления любой используемой библиотеки, а также таргетинг на конкретную платформу (какая версия SSE, Avx и т. д. для использования; самая низкая общая или производственная версия для всех?).

Это не должно требовать полного повторного распространения приложения. Посмотрите на систему компиляции Android ART: вы распространяете приложение как управляемый код (Java в их случае, но применяются те же принципы), а компилятор, который живет в локальной системе, AOT компилирует управляемый код в сверхоптимизированный собственный исполняемый файл.

Если вы измените какую-нибудь маленькую библиотеку, весь управляемый код останется на месте, и вам не придется распространять все заново, только то, что с патчем, а затем можно повторно запустить AOT для создания нового исполняемого файла. (Очевидно, что здесь аналогия с Android не работает из-за модели распространения APK-приложений Android, но это не относится к разработке для настольных компьютеров/серверов.)

и компилятор, который живет в локальной системе, AOT компилирует управляемый код...

Это предыдущая модель NGen, в которой использовалась полная структура; хотя не думаю, что он также создал единую сборку, встраивая код фреймворка в код приложения? Разница между двумя подходами была подчеркнута при запуске Bing.com на .NET Core 2.1! Сообщение блога

Готовые к запуску изображения

Управляемые приложения часто могут иметь низкую производительность при запуске, поскольку методы сначала должны быть JIT-компилированы в машинный код. В .NET Framework есть технология предварительной компиляции NGEN. Однако NGEN требует, чтобы этап предварительной компиляции выполнялся на машине, на которой будет выполняться код. Для Bing это означало бы NGENing на тысячах машин. Это в сочетании с агрессивным циклом развертывания приведет к значительному снижению пропускной способности, поскольку приложение предварительно компилируется на машинах веб-обслуживания. Кроме того, для запуска NGEN требуются административные привилегии, которые часто недоступны или тщательно проверяются в настройках центра обработки данных. В .NET Core инструмент crossgen позволяет предварительно скомпилировать код на этапе перед развертыванием, например в лаборатории сборки, а образы, развернутые в рабочей среде, готовы к запуску!

@masonwheeler AOT сталкивается с встречными ветрами в полной мере .Net из-за динамического характера процесса .Net. Например, тела методов в .Net могут быть изменены с помощью профилировщика в любое время, классы могут быть загружены или созданы с помощью отражения, а новый код может быть создан средой выполнения по мере необходимости для таких вещей, как взаимодействие, поэтому информация межпроцедурного анализа в лучшем случае отражает переходное состояние запущенного процесса. Любой межпроцедурный анализ или оптимизация (включая встраивание) в .Net должны быть невозможными во время выполнения.

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

Если вы можете встроить механизмы для преодоления или ограничения динамической природы процессов .Net, тогда чистый AOT может работать очень хорошо — например, .Net Native учитывает влияние отражения и взаимодействия и запрещает загрузку сборки, создание отражения и ( Полагаю) профиль прикрепить. Но это не просто.

Ведутся некоторые работы, которые позволят нам расширить возможности crossgen до нескольких сборок, чтобы мы могли AOT скомпилировать все основные фреймворки (или все сборки asp.net) в виде пакета. Но это жизнеспособно только потому, что у нас есть jit как запасной вариант для повторного кодегена, когда что-то изменится.

@AndyAyersMS Я никогда не верил, что решение .NET AOT должно быть решением «только для AOT» именно по тем причинам, которые вы здесь описываете. Наличие JIT для создания нового кода по мере необходимости очень важно. Но ситуаций, в которых это необходимо, очень мало, и поэтому я думаю, что здесь можно с пользой применить правило Андерса Хейлсберга для систем типов:

Статические, где это возможно, динамические, когда это необходимо.

Из System.Linq.Expressions
общедоступная компиляция TDelegate (bool PreferInterpretation);

Продолжает ли работать многоуровневая компиляция, если для параметра preferenceInterpretation установлено значение true?

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

iCodeWebApps picture iCodeWebApps  ·  3Комментарии

GitAntoinee picture GitAntoinee  ·  3Комментарии

omajid picture omajid  ·  3Комментарии

Timovzl picture Timovzl  ·  3Комментарии

matty-hall picture matty-hall  ·  3Комментарии