#prog #rust #article
Improve performance of you Rust functions by const currying
TL;DR: иногда для конкретных значений аргументов код может быть оптимизирован лучше, чем в общем случае. Вместо того, чтобы выделять руками оптимизированную версию, можно сделать вариант функции, которая принимает аргумент (или несколько) как const generic и выставить в публичный интерфейс функцию, которая бранчится по этому аргументу и для значений, способствующих оптимизации, вызывать функцию с const generic.
Автор также предлагает процедурный макрос, который позволяет автоматизировать написание нужного для этого бойлерплейта
Improve performance of you Rust functions by const currying
TL;DR: иногда для конкретных значений аргументов код может быть оптимизирован лучше, чем в общем случае. Вместо того, чтобы выделять руками оптимизированную версию, можно сделать вариант функции, которая принимает аргумент (или несколько) как const generic и выставить в публичный интерфейс функцию, которая бранчится по этому аргументу и для значений, способствующих оптимизации, вызывать функцию с const generic.
Автор также предлагает процедурный макрос, который позволяет автоматизировать написание нужного для этого бойлерплейта
Cocl2
Improve performance of you Rust functions by const currying
Currying is a functional programming technique that allows you to partially apply a function’s arguments and return a new function that takes the remaining arguments. This is widely used in functional programming languages like Haskell, as a fundamental tool…
🔥9
#prog #rust
Модель компиляции Rust отличается от сишной. Модули могут иметь циклические зависимости, но единицей компиляции являются крейты. Крейты сами по себе не имеют имён и идентифицируются только при указывании зависимостей других крейтов. Вкупе с отсутствием глобального пространства имён это даёт возможность иметь в дереве зависимостей один и тот же крейт нескольких версий.
С одной стороны, это хорошо, потому что позволяет удовлетворять требования к версиям зависимостей без того, чтобы выбирать только одну версию каждой библиотеки. С другой стороны, это плохо, потому что код библиотек обычно не очень сильно меняется от версии к версии (по крайней мере, при смене минорной версии), и потому при компиляции итогового проекта компилятор в итоге компилирует примерно один и тот же код с небольшими отличиями, увеличивая время компиляции.
При разработке большого проекта избежать дублирования зависимостей сложно. Это не является неизбежностью, поскольку cargo старается по возможности выбирать одну версию библиотеки, которая удовлетворяет всем ограничениям на версию, но всё же это порой случается — особенно, если в зависимостях где-то ограничение на версию сверху. Иногда на это могут быть обоснованные причины — например, когда используются две разные мажорные версии библиотеки, у которых из общего в основном только название — но чаще это просто увеличивает технический долг.
Убирать дубликаты зависимостей не всегда так же просто, как просто вызвать
Один из способов контролировать технический долг — это зафиксировать его состояние и убедиться, что он не растёт. Конкретно в данном случае это решение довольно простое: вынести список дубликатов зависимостей в отдельный файл, который включается в репозиторий, и вставить в CI проверку, которая будет сравнивать этот список с реальным списком дубликатов.
Именно это я недавно сделал у себя на работе. Весь нужный для этого код уместился в чуть менее, чем в 200 строчек кода на Rust — и это включая пустые строки и комментарии. Если бы я меньше заботился о диагностике и некоторых допфичах (об этом ниже), то вышло бы ещё меньше. В связи с этим я хочу поделиться некоторыми практическими советами на тот случай, если вы захотите сделать нечто подобное у себя.
Самый насущный из практических вопросов: куда положить нужный код? Возможно, наиболее идеологически правильным было бы отнести это в отдельную стадию внешнего линтинга, но это значит, что нужный код нужно или иметь в качестве уже собранного бинарника и каким-то образом подтягивать его, или собирать во время сборки. Оба подхода усложняют CI, особенно первый. Всех этих хлопот можно избежать, если включиться в шаг, который уже подразумевает компиляцию кода и его запуск. Именно, нужный код можно поместить в тесты, и он будет автоматически запускаться во время
Второй по важности момент — как именно получать список дублирующихся зависимостей. Для этого можно использовать cargo, и если вы разрабатываете проект на Rust, почти наверняка вы уже его используете, поэтому это не приносит новых зависимостей для сборки. Нам понадобится cargo tree (команда доступна с версии cargo 1.44) с ключом
Модель компиляции Rust отличается от сишной. Модули могут иметь циклические зависимости, но единицей компиляции являются крейты. Крейты сами по себе не имеют имён и идентифицируются только при указывании зависимостей других крейтов. Вкупе с отсутствием глобального пространства имён это даёт возможность иметь в дереве зависимостей один и тот же крейт нескольких версий.
С одной стороны, это хорошо, потому что позволяет удовлетворять требования к версиям зависимостей без того, чтобы выбирать только одну версию каждой библиотеки. С другой стороны, это плохо, потому что код библиотек обычно не очень сильно меняется от версии к версии (по крайней мере, при смене минорной версии), и потому при компиляции итогового проекта компилятор в итоге компилирует примерно один и тот же код с небольшими отличиями, увеличивая время компиляции.
При разработке большого проекта избежать дублирования зависимостей сложно. Это не является неизбежностью, поскольку cargo старается по возможности выбирать одну версию библиотеки, которая удовлетворяет всем ограничениям на версию, но всё же это порой случается — особенно, если в зависимостях где-то ограничение на версию сверху. Иногда на это могут быть обоснованные причины — например, когда используются две разные мажорные версии библиотеки, у которых из общего в основном только название — но чаще это просто увеличивает технический долг.
Убирать дубликаты зависимостей не всегда так же просто, как просто вызвать
cargo update
(говорю по своему опыту), но и оставлять их в долгосрочной перспективе не стоит. Не всегда можно сразу одним разом убрать дубликаты, но даже если и можно, то это часто может стать блокером для внесения других изменений в кодовую базу. С другой стороны, если за этим не следить, новые изменения могут добавить новые дубликаты зависимостей, только увеличивая время компиляции и технический долг.Один из способов контролировать технический долг — это зафиксировать его состояние и убедиться, что он не растёт. Конкретно в данном случае это решение довольно простое: вынести список дубликатов зависимостей в отдельный файл, который включается в репозиторий, и вставить в CI проверку, которая будет сравнивать этот список с реальным списком дубликатов.
Именно это я недавно сделал у себя на работе. Весь нужный для этого код уместился в чуть менее, чем в 200 строчек кода на Rust — и это включая пустые строки и комментарии. Если бы я меньше заботился о диагностике и некоторых допфичах (об этом ниже), то вышло бы ещё меньше. В связи с этим я хочу поделиться некоторыми практическими советами на тот случай, если вы захотите сделать нечто подобное у себя.
Самый насущный из практических вопросов: куда положить нужный код? Возможно, наиболее идеологически правильным было бы отнести это в отдельную стадию внешнего линтинга, но это значит, что нужный код нужно или иметь в качестве уже собранного бинарника и каким-то образом подтягивать его, или собирать во время сборки. Оба подхода усложняют CI, особенно первый. Всех этих хлопот можно избежать, если включиться в шаг, который уже подразумевает компиляцию кода и его запуск. Именно, нужный код можно поместить в тесты, и он будет автоматически запускаться во время
cargo test
. В этом случае код для проверки будет запускаться автоматически для каждого нового изменения (вы же запускаете тесты в CI, верно?) и завалит тесты в случае изменения списка дубликатов, достигая нужной нам цели.Второй по важности момент — как именно получать список дублирующихся зависимостей. Для этого можно использовать cargo, и если вы разрабатываете проект на Rust, почти наверняка вы уже его используете, поэтому это не приносит новых зависимостей для сборки. Нам понадобится cargo tree (команда доступна с версии cargo 1.44) с ключом
-d
(--duplicates
). Задача кажется простой, но тут есть пара тонкостей, о которых стоит упомянуть. ⬇️👍4🔥2❤1
По умолчанию
А препроцессировать его придётся. Ввиду того, что вывод
Кстати, насчёт зависимостей: у
Ещё один из дефолтов
Теперь немного о самом процессе проверки. Вывод
Именно это значение нам и надо сохранить. Можно также считать разными зависимости несовместимых версий, но это несколько менее удобно в обработке (код не проверял):
Надо отметить, что в этом случае в списке будут не только сами зависимости, но и их версии, что может создать неудобства при их обновлении.
Насчёт обработки списков: сигнализировать об ошибке стоит не только в том случае, если есть дубликат вне зафиксированного списка, но и в том случае, если зависимость вне фиксированного списка отсутствует в выдаче
cargo tree -d
показывает не только дубликаты зависимостей, но и зависимые от них крейты. Это удобно для человека, но не особо пригодно для наших целей, когда мы хотим получить только дубликаты и ничего более. Для того, чтобы оставить только нужные нам результаты, можно воспользоваться флагом --depth=0
(доступен с версии cargo 1.54). В этом случае обработка вывода cargo tree
будет сведена к минимуму.А препроцессировать его придётся. Ввиду того, что вывод
cargo tree
для пользователя, а не для обработки машиной (и потому чисто технически это всё может сломаться с обновлением версии cargo), в нём есть пустые строки (которые отделяют деревья зависимостей друг от друга) и строки, предваряющие разных секции зависимостей, а именно, [build-dependencies]
и [dev-dependencies]
. Эти строки при обработке вывода нужно убирать.Кстати, насчёт зависимостей: у
cargo tree
есть флаг -e
/--edges
, который указывает, какие именно зависимости показывать. По умолчанию это прямые зависимости, build-зависимости (которые нужны для сборки, но не нужны непосредственно для компиляции исполняемого кода — скажем, генератор кода поверх .proto-файлов) и dev-зависимости (не нужные для сборки, но полезные для разработки — обычно это зависимости для тестов и бенчмарков). Лично меня куда меньше волнует дублирование, вызванное dev-зависимостями, во многом из-за того, что они не компилируются при каждом cargo build
/cargo check
. Если вы разделяете моё мнение на этот счёт, используйте флаг -e=normal,build
.Ещё один из дефолтов
cargo tree
— консистентный с остальными командами — по умолчанию код генерируется только для текущей целевой платформы и с фичами проекта по умолчанию. Из-за этого можно упустить дубликаты, которые появляются при компиляции под другие платформы и с другими фичами. Конкретно в моём случае это не является проблемой, поскольку в силу специфики используемых технологий компонент, над которым я работаю, поддерживает только один target triple, а все наши features нужны исключительно для отладки, но, скорее всего, имеет смысл использовать флаги --all-targets
и --all-features
. Только имейте в виду, что по очевидным причинам это может сильно замедлить скорость проверки.Теперь немного о самом процессе проверки. Вывод
cargo tree
выводит имя зависимости вместе с версией. Нам нужно только имя зависимости для исключения дубликатов. К счастью, так как имя пакета не может включать в себя пробел, выделение имени — это простоlet name = line.split_once(' ').unwrap().0;
Именно это значение нам и надо сохранить. Можно также считать разными зависимости несовместимых версий, но это несколько менее удобно в обработке (код не проверял):
let (name, version) = line.split_once(' ').unwrap();
let version = {
let (major, rest) = version.split_once('.').unwrap();
if major == "v0" {
let minor = rest.split_once('.').unwrap().0;
format!("v0.{minor}")
} else {
major.to_owned()
}
};
// (name, version) используется в качестве ключа
Надо отметить, что в этом случае в списке будут не только сами зависимости, но и их версии, что может создать неудобства при их обновлении.
Насчёт обработки списков: сигнализировать об ошибке стоит не только в том случае, если есть дубликат вне зафиксированного списка, но и в том случае, если зависимость вне фиксированного списка отсутствует в выдаче
cargo tree
. Это позволяет поддерживать список в актуальном, не врущем состоянии. ⬇️🔥5❤2
Насчёт формата списка дубликатов: так как вы всё равно парсите его самостоятельно, имеет смысл добавить туда поддержку комментариев. Это позволяет писать, почему дубликаты ещё не убраны, и при необходимости записывать ссылки на блокеры. Также это позволяет сделать список самодокументируемым: можно записать в начале, для чего этот список нужен, какой у него формат, как запустить проверку дубликатов и как при необходимости вносить исправления (можно даже немного угореть по сопровождаемости и требовать комментарий с объяснением у каждой зависимости). Лично я у себя записываю список в наиболее, наверное, простом виде — по имени на строку — поэтому я сделал комментариями строки, которые начинаются с
И ещё небольшой момент. В основном репозитории Rust в корне есть инструмент
К чему я это всё? Имеет смысл добавить в данный код функциональность для безусловного перезаписывания списка. Разумеется, она должна быть немного более сложной, чем просто запись поверх, потому что иначе она будет перетирать комментарии. Лично я для своего формата поступил следующим образом:
Функция получает на вход набор новых дубликатов и набор более-не-дубликатов. Сначала я считываю список целиком и разбиваю его на строки. Затем я прохожу по нему и для строк с именами зависимостей добавляю номер строки в набор удаляемых строк, если имя зависимости присутствует в наборе более-не-дубликатов. После этого я снова открываю файл с
Вы, вероятно, скажете, что отслеживать номера излишне, поскольку можно просто проверять строки во время вывода с набором удалённых дубликатов. Так и есть. Но дело в том, что я делаю немного больше и удаляю не только строки с дубликатами, но и непосредственно предшествующие им строки с комментариями. Это решение я обосновываю тем, что если перед именем зависимости есть комментарий, то он наверняка объясняет, почему эта зависимость присутствует в списке дубликатов, а потому при удалении зависимости этот комментарий более неактуален и может ввести в заблуждение.
И последний момент: нужно как-то прокидывать информацию о том, что тест должен обновить список. Использование аргументов командной строки не выглядит хорошей (и удобной) идеей, поэтому мой код обновляет список, если выставлена определённая переменная окружения. Имя этой переменной, разумеется, есть в комментариях в списке дубликатов.
#
. И ещё небольшой момент. В основном репозитории Rust в корне есть инструмент
x.py
, который позволяет коротко выполнять важные в контексте разработки задачи — в частности, запускать тесты. В репозитории Rust есть куча UI тестов, которые проверяют, что компилятор выдаёт на проблемы с кодом диагностики в ожидаемом формате. Создавать файлы с ожидаемым форматом вручную неудобно — особенно с учётом того, что формат для ожидаемого вывода абстрагируется от конкретных номеров строк — поэтому у x.py test
есть опция --bless
, которая перезаписывает ожидаемый вывод указанного теста реальным выводом компилятора. Эта опция также пригождается, когда вносится изменение в код для вывода диагностик — это позволяет избежать внесения изменений в ожидаемый вывод вручную.К чему я это всё? Имеет смысл добавить в данный код функциональность для безусловного перезаписывания списка. Разумеется, она должна быть немного более сложной, чем просто запись поверх, потому что иначе она будет перетирать комментарии. Лично я для своего формата поступил следующим образом:
Функция получает на вход набор новых дубликатов и набор более-не-дубликатов. Сначала я считываю список целиком и разбиваю его на строки. Затем я прохожу по нему и для строк с именами зависимостей добавляю номер строки в набор удаляемых строк, если имя зависимости присутствует в наборе более-не-дубликатов. После этого я снова открываю файл с
.truncate(true)
, записываю туда построчно старое содержимое, пропуская строки с номерами из числа удаляемых, и докидываю туда построчно список новых дубликатов.Вы, вероятно, скажете, что отслеживать номера излишне, поскольку можно просто проверять строки во время вывода с набором удалённых дубликатов. Так и есть. Но дело в том, что я делаю немного больше и удаляю не только строки с дубликатами, но и непосредственно предшествующие им строки с комментариями. Это решение я обосновываю тем, что если перед именем зависимости есть комментарий, то он наверняка объясняет, почему эта зависимость присутствует в списке дубликатов, а потому при удалении зависимости этот комментарий более неактуален и может ввести в заблуждение.
И последний момент: нужно как-то прокидывать информацию о том, что тест должен обновить список. Использование аргументов командной строки не выглядит хорошей (и удобной) идеей, поэтому мой код обновляет список, если выставлена определённая переменная окружения. Имя этой переменной, разумеется, есть в комментариях в списке дубликатов.
👍4❤3🔥2
😒🤚 Избавляться от кругов под глазами консилером
😏👉 Избавляться от кругов под глазами хорошим сном
😏👉 Избавляться от кругов под глазами хорошим сном
👏16🥰4🤔2
Купил себе майку (местного производства), а она настолько длинная, что мне почти годится как мини-платье.
И это типа размер S.
И это типа размер S.
🌚8👎1
Блог*
😒🤚 Избавляться от кругов под глазами консилером 😏👉 Избавляться от кругов под глазами хорошим сном
Подписчик прислал ссылку по теме: https://t.iss.one/donttouchmyface/870
Telegram
don't touch my face
Темные круги под глазами - это такая штука, с которой многие пытаются бороться, покупают какие-то кремы в огромных количествах, но в итоге, ничего, кроме разочарования, не испытывают.
Если тёмные круги у вас всю жизнь, то это гены, и справиться с ними не…
Если тёмные круги у вас всю жизнь, то это гены, и справиться с ними не…
Forwarded from Таксики и лытдыбр σποραδικος
В женской консультации почему-то принимает стоматолог. Vagina dentata?