TG Telegram Group Link
Channel: PLComp
Back to Bottom
https://arxiv.org/pdf/1810.07951.pdf
Don't Unroll Adjoint: Differentiating SSA-form Programs
Michael J Innes, 2019

https://proceedings.neurips.cc/paper/2020/file/9332c513ef44b682e9347822c2e457ac-Paper.pdf
Instead of Rewriting Foreign Code for Machine Learning, Automatically Synthesize Fast Gradients
William S. Moses and Valentin Churavy, NeurIPS 2020

Обе статьи посвящены (обратному aka reverse-mode) автоматическому (или алгоритмическому) дифференцированию функций, представленных в форме Single Static Assignment aka SSA. И тем не менее, они описывают существенно различные подходы.

Первая статья даёт краткое введение в обратное дифференцирование и распространённый подход на основе Wengert Lists. Чтобы перейти к SSA форме, к Wengert Lists необходимо добавить метки, условные и безусловные переходы и фи-узлы (φ nodes). Соответственно, статья вводит правила дифференцирования этих управляющих конструкций (control flow constructs). Дополнительно вводятся правила дифференцирования для чтения и записи в ячейки памяти, поскольку основной прицел статьи — императивные языки (и Julia в особенности). Забавно, что на практике (на текущий момент) основанная на описанном подходе библиотека Zygote не поддерживает деструктивную модификацию массивов, несмотря на её (библиотеки) широкое использование, в особенности во фреймворке для машинного обучения Flux. 😊

Несмотря на использование SSA-формы, первая статья подразумевает сравнительно высокоуровневое представление, близкое к исходному языку, до проведения оптимизаций. Вторая же статья рассматривает внедрение автоматического дифференцирования непосредственно в фреймворк LLVM в виде одного из проходов компиляции, выполняемого над низкоуровневым SSA-представлением, не зависящим от исходного языка и прошедшего ряд оптимизаций. Поэтому основное внимание она уделяет низкоуровневым аспектам: теневой памяти (shadow memory), кешам, обработке указателей, в том числе — вызовам функций по указателю, и переиспользованию информации с других проходов, таких как type-based alias analysis.

Стремление проводить автоматическое дифференцирование настолько низкоуровневого представления продиктовано двумя соображениями. Во-первых, немедленная применимость к большому количеству промышленных языков — C, C++, Rust, Julia — без каких-либо изменений в самом языке. Во-вторых, оптимизация исходного кода может сильно упростить и ускорить порождаемый код расчёта градиента функции, в некоторых случаях — понизить сложность с квадратичной до линейной после применения loop-invariant code motion к исходному коду.

Для подтверждения ускорения, авторы провели замеры производительности и сравнения с традиционными подходами на задачах ADBench от Microsoft и нескольких сторонних реализациях численного решателя дифференциальных уравнений. Результаты и графики приведены в статье. 😊

В любом случае, обе работы полагаются на "классические компиляторные техники", такие как dataflow analysis, alias analysis, abstract interpretation, и оптимизации. И потому представляют собой интереснейшее расширение "поля деятельности компиляторщиков" в сравнительно новую, но стремительно набирающую популярность, область.
Первая часть цикла докладов об автоматизации программирования в СССР доступна на youtube.

Цель — напомнить о хорошо забытых идеях и алгоритмах, не вдаваясь в подробности биографий их авторов и детали устройства вычислительных машин.

Цикл состоит из четырех частей:

1. Программирующие программы (50-е годы).
2. Трансляторы (60-70-е годы).
3. Метавычисления.
4. Синтез программ.

Кстати, идея доклада возникла случайно. Для планируемой к изданию энциклопедии «Истории отечественного программирования» нужны были статьи. Ко мне обратились поздно и я написал обзорную заметку, текстом которой был не очень доволен. Увы, проект энциклопедии так, похоже, и не состоялся. Но ссылки интернет хранит: https://rcc.msu.ru/ru/enciklopediya-istoriya-otechestvennogo-programmirovaniya

К счастью, материал не пропал, а был серьезно развит и переработан для выступления на C++ Russia (спасибо Тимуру Сафину за приглашение выступить!).

https://youtube.com/watch?v=0bTdplAlGYg

P.S. На днях на C++ Russia состоится несколько компиляторных докладов с уклоном в LLVM. Я для себя выделил этот доклад: https://cppconf.ru/talks/595fef990d9342c1b34179bff9c057ab/
Эта небольшая заметка посвящена нескольким новостям по нашей тематике.

Во-первых, хочу напомнить, что в июне прошла конференция PLDI 2022 и многие из представленных там работ имеют компиляторную тематику. Вот программа конференции: https://pldi22.sigplan.org/program/program-pldi-2022/

Во-вторых, давайте поговорим о весьма достойных книгах, которые официально только готовятся к печати и выйдут в 2022-2023 гг.

Нас ждет третье издание классики Engineering a Compiler. Второе издание вышло в 2012 году и мне, увы, пока не удалось установить, в чем же особенности нового издания. Судя по всему, новая версия знаменитого учебника выйдет в этом сентябре: https://www.amazon.com/Engineering-Compiler-Keith-D-Cooper/dp/0128154128

В феврале 2023 года выйдет Essentials of Compilation в издательстве MIT: https://mitpress.mit.edu/books/essentials-compilation Материал в разных редакциях давно уже публиковался на github: https://github.com/IUCompilerCourse/Essentials-of-Compilation

Еще одна довольно известная онлайн-книга скоро будет напечатана. Речь об SSA Book. Официально она теперь называется SSA-based Compiler Design: https://link.springer.com/book/9783030805142 Ждем ее ближе к февралю 2023 года.

Наконец, упомяну о настоящем "долгострое": новой версии классики Programming Languages: An Interpreter-Based Approach" от Samuel Kamin. Новый автор и известный компьютерный ученый, Norman Ramsey, работал над своей версией еще, кажется, с 2005 года. На протяжении многих лет черновые варианты учебника адресно рассылались отдельным профессорам в разных университетах. И, вот, наконец, в этом октябре книга под официальным названием Programming Languages Build, Prove, and Compare будет напечатана: https://www.cambridge.org/ru/academic/subjects/computer-science/programming-languages-and-applied-logic/programming-languages-build-prove-and-compare
Недавно завершилась конференция C++ Russia 2023 и пришло время подвести некоторые ее итоги. Разумеется, в контексте нашего канала, благо на конференции появилась отдельная секция по компиляторным докладам.

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

Loop unrolling в деталях Ивана Афансьева. Доклад, как это понятно из названия, посвящен теме раскрутки циклов в компиляторе. Это введение в предмет, более интересное, пожалуй, начинающим. Правда, есть один любопытный нюанс. В завершающей части доклада Иван переходит от игрушечных задач к задаче из своей практической деятельности. Это, совершенно неожиданно, оказывается NP-полная задача о вершинном покрытии графа. Боюсь, что далеко не все начинающие поняли тонкости задачи в процессе доклада. Откуда взялась эта задача? Этот вопрос я и задал Ивану. Тут-то и выяснилось самое интересное. Оказывается, это задача относится к проекту генератора кода для спецпроцессора. Иван решает ее в лоб для малого набора данных (раскручивает циклы, использует векторизацию), а если набор данных велик -- использует ансамбль из эвристических алгоритмов. Достаточно впечатляюще! Здесь я пожалел, что Иван не сделал на эту тему отдельного доклада уже для опытных компиляторщиков.

Автоматизация программирования в СССР. Трансляторы (60–70-е годы). Вторая часть моего доклада. В процессе подготовки от ряда тем пришлось отказаться, поскольку на их изложение времени уже не хватало. В этой части я постарался еще более явно связать древние времена с самыми современными компиляторными подходами и в этом смысле, боюсь, доклад не совсем будет понятен начинающим. Впрочем, возможно я и ошибаюсь, так что предлагаю самостоятельно посмотреть и оценить. За возможность дать ссылку на yt благодарю Тимура Сафина и JUG Ru Group.

https://www.youtube.com/watch?v=Q2ErYDuVAWo
Прерываю затянувшуюся тишину на нашем канале! Надеюсь, остальные участники PLComp тоже, со временем, активизируются.

Недавно я выступил с двумя компиляторными докладами. Рассказал о том, что мне самому было бы интересно услышать.

Доклад Graph-Based Intermediate Representations: An Overview and Perspectives. Введение и обзор графовых IR. Такие IR диктуют оригинальный взгляд на построение компиляторов в целом. Впрочем, до сих пор эти IR являются, скорее, экзотикой, поэтому доклад будет более интересен специалистам.

Видеозаписи у меня нет, но имеются слайды: https://github.com/true-grue/graph-irs

Доклад В Python есть готовый фронтенд для вашего компилятора. Компиляторные технологии для начинающих в Питоне. О том, как сочетание модуля ast и сопоставления с образцом дает возможность по-новому взглянуть на задачу построения DSL. Кстати, доклад должен быть интересен не только питонистам, ведь примеры (каждый из которых содержит <100 строк кода) включают в себя DSL-компилятор Datalog и компилятор в Wasm.

Видеозапись: https://www.youtube.com/watch?v=h-TzDPL2nDE

Материалы к докладу: https://github.com/true-grue/python-dsls
Видеозапись моего недавнего выступления. Хотя тема и узкоспециальная, все же, предлагаю досмотреть. Мне кажется, во второй-третьей части доклад становится интереснее и для тех компиляторщиков, кому заявленная тематика не близка. Отдельно благодарю представителей компании YADRO за отличные вопросы!

https://www.youtube.com/watch?v=Q6-h6R_e-04
Коллеги, друзья и единомышленники!

Трудно поверить, но нашему каналу PLComp (Programming Languages & Compilers) уже исполнилось 4 года. За это время мы подготовили почти сотню обзорных заметок, освещающих историю развития компиляторов, структуру современных и перспективных компиляторов, а также смежные темы. Мы искренне надеемся, что наши читатели смогли найти в этих материалах что-то интересное и полезное для себя.

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

1. Какие из опубликованных материалов вам запомнились больше всего?

2. Считаете ли вы, что нам стоит продолжить работу над каналом?

3. Какой формат подачи информации кажется вам наиболее подходящим и удобным?

4. Хотели бы вы принять участие в будущих публикациях на PLComp? (лучше ответить личным сообщением)

Мы будем признательны за любые комментарии, идеи и предложения.
📚 Breno Campos, Ferreira Guimarães
"Lazy Evaluation for the Lazy: Automatically Transforming Call-by-Value into Call-by-Need"
Proceedings of the 32nd ACM SIGPLAN International Conference on Compiler Construction. February 2023: 239–249


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

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

Цель авторов — научить компиляторы находить вычисления, которые предпочтительно сделать ленивыми, не используя аннотирование lazy вручную и даже поддержку ленивости со стороны языка. Для этого они реализовали внутри LLVM специальную profile-guided оптимизацию, делающую вычисление некоторых аргументов функций ленивым. Оптимизация происходит на уровне LLVM IR в SSA форме.

Статья описывает два алгоритма:
1) модификацию Value Slicing, которая строит замыкание для вычисления произвольного значения в SSA IR.
2) Lazification по информации от профилировщика заменяет call-by-value на call-by-need.

Выбор вычислений для lazification и необходимое профилирование программы описаны в более ранней статье:
Chang, Stephen. "Laziness by need." Programming Languages and Systems: 22nd European Symposium on Programming, ESOP 2013

Авторы добились роста производительности программ на C и C++ от 1% до ~10% за счет увеличения размера на ~10%; тесты включают в себя SPEC CPU 2017.

#optimization #profiling #lazy #llvm
Новости PLComp №1, часть 1

📚Статья Flan: An expressive and efficient datalog compiler for program analysis от Supun Abeysinghe, Xhebraj Anxhelo и Tiark Rompf (Purdue)

Реализация компилятора Datalog, который на тестах опережает по производительности известный компилятор Soufflé. Реализация основана на eDSL, что позволяет расширять Datalog с помощью механизмов хост-языка Scala. Для eDSL написан простой интерпретатор, код которого, с помощью Scala LMS (первая проекция Футамуры), транслируется в производительный код на C.

📚Заметки Compiling With Constraints (Mar 18, 2024) от Филипа Цукера (Philip Zucker)

Заметки по очень перспективному подходу к использованию программирования в ограничениях для декларативного описания фаз выбора команд, планирования и распределения регистров. Во многом основано на изучении проекта Unison. Минимум теории, максимум практики в виде примеров кода на Python и MiniZinc.

📚Видео доклада Архитектурно-зависимые оптимизации компилятора LCC для микропроцессоров архитектуры Эльбрус (2024 г.), автор М.И. Нейман-Заде

Доклад посвящён работе оптимизирующего компилятора lcc для процессоров Эльбрус с широким командным словом (VLIW). Компилятор lcc сам по себе структурно схож с традиционными компиляторами, однако особенности целевой платформы накладывают серьезные отпечатки на существующие низкоуровневые оптимизации. В докладе раскрываются аппаратные особенности работы процессоров Эльбрус, их отличия от "традиционных" процессоров, после чего описываются некоторые аппаратно-зависимые оптимизации, характерные для Эльбрусов.

Среди оптимизаций рассказывается про:
* Слияние кода (if-conversion).
* Конвейеризацию циклов (software pipelining с аппаратной поддержкой).
* Динамический разрыв зависимостей.
* Предварительную подкачку данных (улучшенный аналог prefetch).

📚Курс Declarative Program Analysis and Optimization (Spring 2024) от Макса Уилси (Max Willsey, UC Berkley) (если ссылка не работает, попробуйте отключить VPN)

Учебный курс с очень интригующим названием, поскольку декларативный подход к реализации фаз компилятора все еще остается уделом почти исключительно академистов. В курсе речь идет о переписывании термов, стратегическом переписывании, синтезе и верификации систем переписывания, использовании Datalog, о E-графах и насыщении равенствами. Словом, это ультрасовременный университетский курс по специальным вопросам компиляции.

📚Статья Fixing a Bug in PyPy's Incremental GC (2024-03-26)

Починка каждого бага, связанного с GC, это сама по себе уже интересная история,
потому что такие баги обычно нетривиальны. К тому же баг, про который рассказывает автор, приводит к memory corruption и это еще больше усложняет отладку. Поиск стабильного воспроизведения, пошаговая интерактивная отладка в gdb/rr,
написание скриптов для GDB API для упрощения отладки GC и т.д.

(продолжение следует)

#news
Новости PLComp №1, часть 2

📚Статья Troubleshooting an Intermittent Failure in CI Tests on ARM64 (October 4, 2023)

Тесты для Kong Gateway время от времени падали с синтаксической ошибкой, причем происходило это только на ARM64 и только с включенным компилятором в OpenResty (использует LuaJIT). В ходе исследования бага авторы выяснили причину — некорректная кодогенерация для ARM64, и предложили исправление в основной репозиторий LuaJIT.

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

📚Видео доклада How Badly Do We Want Correct Compilers? (NDC TechTown 2023)

Доклад известного исследователя Джона Регира (John Regehr). Докладчик, насколько можно заметить, ни разу не произносит слово "CompCert" и сомневается в успешности проектов формально верифицированных компиляторов для популярных ЯП, что, конечно, интригует. Рассматриваются инструменты, в разработке которых принял участие автор доклада: YARPGen, Souper и другие проекты.

📚Заметки Compiling ML models to C for fun (September 19, 2023) и Vectorizing ML models for fun (February 18, 2024), автор Макс Бернштейн (Max Bernstein)

Учебное пособие в двух частях для самых маленьких по разработке DL-компиляторов с примерами на Python. Автор начинает с реализации более производительного варианта библиотеки micrograd и создает компилятор, выдающий представление на C. Во второй части делается переход от скаляров к векторам и осуществляется автовекторизация с помощью выявления скалярных произведений в коде.

📚Видео доклада A Full Employment Theorem for PL Researchers: Domain-Specific Languages от Нейта Фостера (Nate Foster, Cornell) (23 Oct 2023)

Доклад призван воодушевить исследователей в области языков программирования на работу с современными DSL, в первую очередь с теми, которые используются для программирования специализированного "железа". Рассматривается Тьюринг-неполный язык P4 для разбора сетевых пакетов и ставится задача установления эквивалентности двух двоичных парсеров. Для решения задачи используется подход с использованием конечных автоматов, бисимуляции, SMT-решателя и Coq.

📚Видео доклада ægraphs: Acyclic E-graphs for Efficient Optimization in a Production Compiler, автор Chris Fallin (8 июля 2023 г.)

Перенацеливаемый генератор кода Cranelift является, судя по всему, первым промышленным проектом, где широко используются E-графы для решения задачи выбора порядка фаз при компиляции. В Cranelift, как показано в докладе, используется оригинальный упрощенный вариант E-графов -- ациклические E-графы (aegraphs). В предложенную схему на основе aegraphs включены и такие "тяжеловесные" оптимизации, как GVN и LICM.

Над выпуском работали: Пётр Советов, Сергей Бронников, Алексей Маркин

#news
📚 Пост AI-Powered Fuzzing: Breaking the Bug Hunting Barrier

В 2016 году Google запустила бесплатный сервис для фаззинга открытого ПО OSS-Fuzz. Это произошло после обнародования нескольких критических уязвимостей типа Heartbleed, которые, как обнаружилось, можно было предотвратить, если бы разработчики использовали фаззинг.

Чтобы использовать OSS-Fuzz, разработчик реализует фаззеры для своего проекта и конфигурирует взаимодействие с сервисом. Сервис запускает фаззеры, собирает результаты, приватно сообщает разработчикам о найденных багах, а затем следит за обновлениями чтобы удостовериться, что разработчики смогли исправить баг. Открытые проекты обслуживаются бесплатно, а для закрытого кода можно запустить сервис на своей машине.

В среднем, код проектов, использующих OSS-Fuzz, покрывается не более, чем на 30%, потому что писать фаззеры приходится вручную. Это и трудно, и долго, а разработчики не всегда могут выделить на эту задачу ресурсы. Чтобы помочь разработчикам увеличить покрытие кода фаззерами, в августе 2023 инженеры Google реализовали протокол интеграции OSS-Fuzz с LLM, дабы языковые модели генерировали фаззеры без участия человека.

OSS-Fuzz идентифицирует непокрытые части проекта и формирует запрос к LLM. Запрос включает информацию о непокрытой части проекта. Код фаззера может автоматически уточняться у языковой модели в случае ошибок компиляции или если сгенерированный фаззер не увеличил покрытие кода.

Примеры запросов к LLM и system prompts.

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

- 1300+ бенчмарков из 300 проектов ПО.
- В 160 проектах LLM cумела сгенерировать корректные фаззеры и увеличить покрытие на 10-30%.

К сожалению, неясно, какую LLM использовали для этого эксперимента — заявлена поддержка Vertex AI code-bison и code-bison-32k, Gemini Pro и OpenAI GPT-3.5-turbo и GPT-4.

#llm #fuzzing
📚 Отчёт Wentao Gao, Van-Thuan Pham, Dongge Liu, Oliver Chang, Toby Murray, and Benjamin I.P. Rubinstein. 2023. Beyond the Coverage Plateau: A Comprehensive Study of Fuzz Blockers (Registered Report). In Proceedings of the 2nd International Fuzzing Workshop (FUZZING ’23), July 17, 2023


Отчёт анализирует некоторые причины проблемы плато, с которой сталкивается Coverage-Guided Greybox Fuzzing (CGF).

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

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

1. Выбирает вход из накапливаемого множества входов (seed corpus).
2. Мутирует вход, пытаясь достигнуть непокрытых участков кода.
3. Запускает программу на мутированном входе. Если покрытие увеличилось, добавляет вход в seed corpus; при неожиданном поведении программы, например, ошибке времени выполнения, описывает этот случай в отчёте.

Алгоритмы CGF различаются правилами мутации, начальным заполнением seed corpus и другими деталями.

Эксперименты показывают, что современные фаззеры достаточно быстро выходят на плато, при котором покрытие кода перестает расти, сколько бы дополнительного времени ни получил фаззер.
Чтобы понять причины эффекта плато, авторы изучили результаты фаззинга трех популярных библиотек: libPNG, iGraph, OpenSSL с помощью утилиты FuzzIntrospector для cервиса OSS-Fuzz.
(Пример отчёта)

Фаззинг библиотек требует написания программ-оберток (drivers), которые запускают библиотечные функции. Этим занимаются, как правило, сами разработчики библиотек. По мнению авторов, чрезмерно упрощенные обертки являются важной причиной эффекта плато.

Анализ авторов показал, что проблемные ситуации часто возникали независимо от входов: в libPNG все 12 экспериментов показали плато на любых входах, а в iGraph — 9 из 22. Покрытие переставало увеличиваться по следующим причинам:

1. Аргументы функций в обертке зафиксированы. Например, библиотека iGraph поддерживает работу как с ориентированными, так и с неориентированными графами, но существующие обертки вызывали некоторые функции с такими фиксированными аргументами, что под фаззинг попадали только ориентированные графы.
2. Функции вообще не вызываются оберткой:
2.1. Вызываются не все перегрузки.
2.2. Ошибки бы проявились если бы функция была вызвана больше одного раза (например, init-функции).
2.3. Пропущены вызовы, которые могли бы настроить библиотеку на разные конфигурации.
2.4. Пропущены вызовы, добавляющие функциональность (например, установка обработчиков событий).
3. Функции вызываются только в "правильном", ожидаемом порядке, а ошибки проявляются лишь если перемешать вызовы.
4. Наличие объективно недостижимого кода.

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

Анализ фаззинга OpenSSL, видимо, будет включен в следующую работу.

В качестве решений проблем с обертками авторы предлагают рассмотреть автоматическую генерацию оберток и structure-aware fuzzing, имеющий полную информацию о коде программы.

#fuzzing
📚 Статья Rocha, Rodrigo CO, et al. "Hybf: A hybrid branch fusion strategy for code size reduction." Proceedings of the 32nd ACM SIGPLAN International Conference on Compiler Construction. 2023.

Авторы реализовали для LLVM IR два новых алгоритма слияния ветвей. 70% бенчмарков показали уменьшение размера кода до 4% поверх уже внедренных оптимизаций с пренебрежимо малым эффектом на производительность кода.

Распространенные алгоритмы слияния ветвей ограничены в применении:

- Первоначальная версия алгоритма слияния ветвей [1] работает только для похожих ветвей, содержащих по одному базовому блоку, т.е. для ромбов в графе потока управления.
- Другой алгоритм — Control Flow Melding (Divergence Aware Region Melder, DARM), поддерживает только структурно простые ветви: вложенные if-then, if-then-else или natural loops.

Авторы предлагают два новых алгоритма для слияния ветвей и алгоритм HyBF, выбирающий лучшую из двух трансформаций для конкретного подграфа потока управления. Они работают с т.н. регионами графа потока управления – это подграфы с одной точкой входа.

Первый алгоритм, предложенный авторами — Control Flow Melding (CFM-CS), это улучшенная версия алгоритма DARM.
CFM-CS работает только с SESE-регионами (single-entry-single-exit, регионы с одной точкой выхода).

Первоначально, DARM предназначен для борьбы с control flow divergence. Это ситуация в моделях вычислений Single Instruction Multiple Threads, когда несколько потоков выполняют один и тот же код, но выбирают разные пути из-за условного ветвления [2]. Если ветви выполняются неодинаковое время, потоки, уже выполнившие более короткую ветвь, ожидают еще не завершившиеся потоки, занятые длинными ветвями, как при барьерной синхронизации.
DARM обычно ведёт к уменьшению размера кода, но не нацелен на это, и не способен работать со сложно структурированными подграфами потока управления.

Улучшенная версия DARM — CFM-CS — работает следующим образом:

1. Определить SESE-регион, где возможно слияние (блок с двумя исходящими ветвями, сходящимися в одном блоке).
2. Собрать подрегионы каждой ветви в дерево (авторы используют структуру данных region tree в LLVM).
3. Изо всех возможных пар подрегионов, принадлежащих двум ветвям, нужно выбрать пару изоморфных подрегионов, причем с наиболее "похожими" инструкциями внутри. Для этого регионы должны быть выровнены, как в проблеме "выравнивания последовательностей"[3]. Авторы используют специальную эвристику в качестве меры сходства, позволяющей выбрать пары подрегионов наиболее выгодные для слияния.
4. Чтобы объединить изоморфные подрегионы из двух ветвей необходимо выровнять уже инструкции внутри соответствующих блоков. Это разделит инструкции блоков на:
- не имеющие соответствия в блоке изоморфного подрегиона. Эти инструкции перемещаются в новые, условно выполняемые базовые блоки.
- совпадающие с инструкциями в блоке изоморфного подрегиона. Это одинаковые инструкции с разными операндами: они будут помещены в объединенный базовый блок, но их операнды будут вычисляться с использованием псевдоинструкций SELECT. Условием таких SELECT'ов будет, разумеется, условие из входного блока большого SESE-региона.
Исходный код алгоритма CFM-CS для LLVM IR.

Алгоритм CFM-CS эффективен, когда ветви имеют похожую структуру, но если одна из ветвей содержит всего один блок, такой блок можно обернуть в код, имитирующий структуру другой ветви, а затем запустить на полученном регионе CFM-CS. Эта техника называется Region Replication, и авторы также предлагают улучшение существующего алгоритма Region Replication используемого в DARM.

Второй предложенный алгоритм SEME-fusion обобщает алгоритм слияния функций HyFM, предложенный авторами в предыдущей статье [4], на любой SEME-регион (Single-Entry-Multiple-Exit). Он сравнивает блоки двух областей SEME на основе расстояний между их fingerprint'ами, вычисляя linear pairwise alignment [4]. После выравнивания и слияния блоков происходит технический процесс корректировки ϕ-функций в выходных блоках.
Исходный код алгоритма SEME-fusion для LLVM IR.
Таким образом, CFM-CS лучше работает для ветвей с похожей структурой, а SEME-fusion предпочтителен в ситуациях SEME-регионов или для сильно различающихся по структуре ветвей.
Так как эти два подхода ортогональны, авторы реализовали специальный проход по модулям в LLVM, применяющий к регионам с ветвлением обе трансформации и выбирающий лучшую.

[1] B. Coutinho, D. Sampaio, F. M. Q. Pereira, and W. Meira Jr. 2011. Divergence Analysis and Optimizations. In 2011 International Conference on Parallel Architectures and Compilation Techniques. 320–329.
[2] Saumya, Charitha, Kirshanthan Sundararajah, and Milind Kulkarni. "DARM: Control-Flow Melding for SIMT Thread Divergence Reduction--Extended Version." arXiv preprint arXiv:2107.05681 (2021).
[3] https://en.wikipedia.org/wiki/Sequence_alignment
[4] Rocha, Rodrigo CO, et al. "HyFM: Function merging for free." Proceedings of the 22nd ACM SIGPLAN/SIGBED International Conference on Languages, Compilers, and Tools for Embedded Systems. 2021.


#llvm #function_merging #optimization #branch_merging #code_size_reduction
Implementation Strategies for Mutable Value Semantics
Dimitri Racordon, Denys Shabalin, Daniel Zheng, Dave Abrahams, Brennan Saeta
https://kyouko-taiga.github.io/assets/papers/jot2022-mvs.pdf

Одно из неоспоримых достоинств языка Rust — он наглядно показал, что безопасная работа с указателями и ссылками сложна, и требует следовать довольно замысловатым правилам, уберегающим от неочевидных потенциальных проблем. Даже опыт более простых языков, повсеместно использующих ссылки и GC — например, Java — показывает, что непреднамеренный алиасинг (aliasing) может создавать дыры в системе безопасности, помимо других проблем.

Эти два фактора — сложность системы типов и владения в Rust, и сложность безопасного использования ссылок без контроля алиасинга — привели к появлению ряда языков, например, https://www.hylo-lang.org/ или https://vale.dev/, отказавшихся от первоклассных ссылок вообще. Отказ от первоклассных ссылок означает, что в языке просто нет механизмов, позволяющих явным образом взять ссылку на объект в памяти, передать её в функцию или того хуже — записать в поле другого объекта.

Отказавшись от ссылок и ссылочной семантики, мы приходим к семантике значений. Концептуально, каждое присваивание копирует значение целиком из одного места в другое, а каждый объект владеет своими частями (whole-part relation). Благодаря этому все данные в программе образуют лес, корни деревьев которого располагаются в переменных. И поскольку в такой структуре не может быть алиасинга, можно безбоязненно сделать значения изменяемыми, откуда и возникает Mutable Value Semantics — семантика изменяемых значений. Возможность изменения "по месту" (in-place) частей объектов и отличает MVS от семантики неизменных (immutable) значений в функциональном программировании, которая тривиально реализуется поверх ссылок на общие части в персистентных структурах данных.

Естественно, что наивная реализация такой семантики крайне неэффективна из-за постоянного копирования развесистых структур данных. Самое очевидное узкое место — работа с массивами: при передаче массивов в функции их пришлось бы копировать, а единственный способ вернуть массив из функции — снова создать копию. Для обхода этой проблемы и реализации работы с массивами и другими структурами "по месту" (in-place) предлагается использовать ключевое слово inout для параметров функций, передаваемых "по ссылке". Это и есть "ссылки второго класса", которые возникают и используются неявно, без возможности сохранения в переменных или полях объектов.

Тем не менее, поскольку по ссылке могут передаваться только аргументы функций, а всё остальное должно копироваться — это всё ещё непозволительно много копирований. Особенно "лишних" копирований значений, которые никогда и не меняются, что значит для них алиасинг не наблюдаем и не создаёт проблем (аналогично ФП, упоминавшемуся выше). Чтобы избавиться от этих копий предлагается использовать механизмы Copy-on-Write и подсчёт ссылок при реализации языка. Для уменьшения инкрементов и декрементов счётчика предлагается обратиться к таким работам как Perceus для Koka и Lean 4.

Однако возникает вопрос: коль скоро мы вводим неявные ссылки в наш язык и его реализацию, не сломают ли они семантику изменяемых значений? Чтобы на него ответить, авторы формализуют core language, его систему типов и операционную семантику, после чего доказывают корректность оптимизаций и сохранение семантики изменяемых значений теми правилами, которым подчиняется работа inout аргументов.

Но авторы не ограничились теоретическим рассмотрением вопроса, а пошли дальше, и реализовали прототип предлагаемой семантики с оптимизациями в виде упрощённого языка программирования на основе Swift, используя LLVM в качестве бэкенда. Это позволило оценить производительность прототипа в сравнении с другими языками, использующими LLVM (C++, Swift, Scala Native) на ряде синтетических бенчмарков и общеизвестных примеров (Mandelbrot, NBody, NQueens, etc.).
Бенчмарки показали жизнеспособность подхода MVS — производительность оказалась на уровне между Scala Native и Swift. Отставание от Swift авторы объясняют тем, что в прототипе не использовались более продвинутые оптимизации, в частности, уменьшающие количество обращений к счётчику ссылок. Таким образом подход имеет перспективы значительного дальнейшего улучшения за счёт комбинирования с другими известными оптимизациями (например, упоминавшийся Perceus).

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

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

#mutable_value_semantics #swift #CoW
Возможно вы знакомы с алгоритмами сборки мусора, которые используются в языках программирования. Американский программист Кен Фокс (Ken Fox) написал программу с использованием разных алгоритмов сборки мусора и вариант программы без освобождения памяти, собрал данные для каждого варианта запуска программы и сгенерировал анимированные изображения для них. В репозитории представлены визуализации четырех основных алгоритмов сборки мусора:

- Mark-sweep
- Mark-compact
- Copying
- Reference counting

Каждому из этих алгоритмов посвящена отдельная глава в основополагающем учебнике The Garbage Collection Handbook. Рекомендуем обратиться к этому учебнику для полного погружения в тему.

Каждое анимированное изображение - это пространство памяти, выделенное процессу. Чёрным цветом изображена неиспользуемая память, по мере использования память начинает подсвечиваться жёлтым (операции записи) и зелёным (операции чтения) цветами. Цвет фрагментов памяти, которые используются дольше, начинает тускнеть, чтобы визуализировать развитие процесса во времени. По ходу выполнения программа перестаёт использовать отдельные участки памяти. Они и считаются "мусором".

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

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

Третья анимация демонстрирует работу алгоритма "mark-and-sweep". Все объекты делятся на достижимые и недостижимые. Определённое множество объектов считается достижимым изначально - так называемые корневые объекты. Объект, на который есть ссылка из достижимого объекта, тоже считается достижимым. При запуске сборщик мусора выполняет следующие шаги:

- Фаза Mark: отмечает все достижимые объекты
- Фаза Sweep: проходит рекурсивно по объектам в куче, удаляет недостижимые объекты и возвращает их в пул свободной памяти.

Визуализации остальных алгоритмов приведены в репозитории. Помимо описанных визуализаций алгоритмов сборки мусора есть и другие, например интерактивная визуализация алгоритма tricolor mark-sweep, используемого в реализации языка Go.
This media is not supported in your browser
VIEW IN TELEGRAM
Программа без освобождения памяти (NO_GC).
This media is not supported in your browser
VIEW IN TELEGRAM
Программа со счётчиком ссылок.
This media is not supported in your browser
VIEW IN TELEGRAM
Программа с использованием сборщика мусора с алгоритмом Mark and Sweep.
HTML Embed Code:
2024/05/17 21:42:46
Back to Top