#prog #rust #article
OnceMap: Rust Pattern for Running Concurrent Work Exactly Once
OnceMap: Rust Pattern for Running Concurrent Work Exactly Once
When multiple tasks need the same resource, how do you ensure the work happens exactly once? uv’s solution is OnceMap - a lightweight concurrent memoization primitive that powers deduplication across the resolver and installer.Не смотря на то, что код приведён для Rust, подход, судя по всему, можно адаптировать и под другие языки.
🤔3❤2
#prog #rust #моё
У Алексея Кладова есть пост про реализацию интернирования строк (советую прочитать перед моим постом). Как он замечает, простейший способ интернировать строки — через такой тип:
Новые строки добавляются через вставку в
Недостаток такого решения очевиден: каждая строка выделяется в куче дважды. Особенно это печально в связи с тем, что строки часто интернируют именно для того, чтобы сэкономить память, выделяя её только единожды на каждое значение. Алексей решает это тем, что выделяет память по возможности одним куском в одной
К сожалению, у этого дизайна есть несколько недостатков.
* Выдаваемые
* Из-за индексации каждый лукап — потенциальная паника.
* Идентификаторы никак не привязаны к пулу временами жизни: если мы создадим пул, выделим строку, дропнем пул, создадим заново и снова выделим строку, то возвращённые значения будут считаться равными — что технически верно, но не вполне корректно.
* Можно сравнивать идентификаторы, полученные от двух разных пулов, и получить как и ложно-положительные, так и ложно-отрицательные результаты, и компилятор вообще никак от этого не защищает.
* Очень нишевый недостаток:
* Количество потребляемой таки пулом памяти может только расти, явное переиспользование памяти невозможно. Это может быть важно, если нам требуется многократно использовать пул в ограниченной области действия.
От всех этих недостатков можно избавиться, используя два решения.
Первый из них заключается в трюке, используемом в thin_vec: вместо того, чтобы хранить длину аллокации отдельно, выделять дополнительную память в куче и хранить в начале длину строки. Сами интернированные строки будут хранить указатель на начало аллокации и создавать толстый указатель на строку по требованию. Это даёт несколько преимуществ:
* Выдаваемые идентификаторы могут быть переведены в строки без обращения к пулу — его не надо держать под рукой и получение строки не может паниковать.
* К создаваемой строке можно (на самом деле нужно, для корректности) привязать время жизни. Это позволяет избежать ошибок со случайным переиспользованием строк и до какой-то степени защищает от сравнения строк из разных пулов.
* Указатель имеет естественную нишу в виде null и потому получает оптимизацию раскладки
Второе решение заключается в том, чтобы использовать bump-аллокатор — в данном случае bumpalo. Он даёт гарантии стабильности адресов, а внутри использует тот же трюк с удваиваемыми буферами, который нам не надо повторять самостоятельно. Дополнительно он позволяет скопом освобождать память, сохраняя при этом аллокацию последнего буфера, что позволяет уменьшить потребление памяти и обращение к аллокатору и закрыть таким образом последний недостаток решения Кладова.
Что ж, приступим к реализации!
У Алексея Кладова есть пост про реализацию интернирования строк (советую прочитать перед моим постом). Как он замечает, простейший способ интернировать строки — через такой тип:
struct Interner {
map: HashMap<String, u32>,
vec: Vec<String>,
}Новые строки добавляются через вставку в
map, а идентификатор строк создаётся от длины vec на момент добавления. map позволяет быстро проверить, была ли строка записана, а vec позволяет быстро получить строку по её идентификатору.Недостаток такого решения очевиден: каждая строка выделяется в куче дважды. Особенно это печально в связи с тем, что строки часто интернируют именно для того, чтобы сэкономить память, выделяя её только единожды на каждое значение. Алексей решает это тем, что выделяет память по возможности одним куском в одной
String и хранит в мапе &str на эту память с принудительно приведённым к 'static временем жизни. Инвалидацию ссылок он обходит остроумным приёмом: при нехватке ёмкости он выделяет новый буфер, вдвое больше предыдущего, и записывает новые строки туда, а старый буфер переносит в отдельный вектор. Он эксплуатирует тот факт, что адреса выделенной в куче памяти стабильны и не меняются при перемещении String. С таким дизайном определение структуры данных выглядит так:struct Interner {
map: HashMap<&'static str, u32>,
vec: Vec<&'static str>,
// новые строки записывают сюда
buf: String,
// буферы с недостаточной памятью переносят сюда
full: Vec<String>,
}К сожалению, у этого дизайна есть несколько недостатков.
* Выдаваемые
Interner идентификаторы не самодостаточны: всё ещё нужно обращаться к пулу, если нам потребуется содержимое строки. Пул при этом можно перепутать* Из-за индексации каждый лукап — потенциальная паника.
* Идентификаторы никак не привязаны к пулу временами жизни: если мы создадим пул, выделим строку, дропнем пул, создадим заново и снова выделим строку, то возвращённые значения будут считаться равными — что технически верно, но не вполне корректно.
* Можно сравнивать идентификаторы, полученные от двух разных пулов, и получить как и ложно-положительные, так и ложно-отрицательные результаты, и компилятор вообще никак от этого не защищает.
* Очень нишевый недостаток:
u32 не имеет ниши и потому не получает null pointer optimization при оборачивании в Option. Это, в принципе, решаемо оборачиванием в NonNull<u32>, но не очень удобно из-за инкремента при генерации и декремента при индексации.* Количество потребляемой таки пулом памяти может только расти, явное переиспользование памяти невозможно. Это может быть важно, если нам требуется многократно использовать пул в ограниченной области действия.
От всех этих недостатков можно избавиться, используя два решения.
Первый из них заключается в трюке, используемом в thin_vec: вместо того, чтобы хранить длину аллокации отдельно, выделять дополнительную память в куче и хранить в начале длину строки. Сами интернированные строки будут хранить указатель на начало аллокации и создавать толстый указатель на строку по требованию. Это даёт несколько преимуществ:
* Выдаваемые идентификаторы могут быть переведены в строки без обращения к пулу — его не надо держать под рукой и получение строки не может паниковать.
* К создаваемой строке можно (на самом деле нужно, для корректности) привязать время жизни. Это позволяет избежать ошибок со случайным переиспользованием строк и до какой-то степени защищает от сравнения строк из разных пулов.
* Указатель имеет естественную нишу в виде null и потому получает оптимизацию раскладки
Option.Второе решение заключается в том, чтобы использовать bump-аллокатор — в данном случае bumpalo. Он даёт гарантии стабильности адресов, а внутри использует тот же трюк с удваиваемыми буферами, который нам не надо повторять самостоятельно. Дополнительно он позволяет скопом освобождать память, сохраняя при этом аллокацию последнего буфера, что позволяет уменьшить потребление памяти и обращение к аллокатору и закрыть таким образом последний недостаток решения Кладова.
Что ж, приступим к реализации!
👍3👎1
#prog #rust #article
What does it take to ship Rust in safety-critical?
Статья про очень конкретные препятствия к использованию Rust в safety-critical системах.
Это именно препятствия, а не блоки — в этих областях Rust уже используется в проде.
What does it take to ship Rust in safety-critical?
Статья про очень конкретные препятствия к использованию Rust в safety-critical системах.
Это именно препятствия, а не блоки — в этих областях Rust уже используется в проде.
blog.rust-lang.org
What does it take to ship Rust in safety-critical? | Rust Blog
Empowering everyone to build reliable and efficient software.
🤔2
#prog #rust #article
Офигенно!
Rust's standard library on the GPU
Офигенно!
Rust's standard library on the GPU
GPU code can now use Rust's standard library. We share the implementation approach and what this unlocks for GPU programming.
This works because of our customhostcallframework. A hostcall is analogous to a syscall. A hostcall is a structured request from GPU code to the host CPU to perform something it cannot execute itself. You can think of it like a remote procedure call from the GPU to the host to achieve syscall-like functionality.
To end users, Rust's std APIs appear unchanged and act as one would expect.
❤12👍8
Блог*
В этом случае достаточно поменять тип поля irrelevant на UnconditionallyEqual<String> — и можно будет использовать стандартный #[derive(PartialEq)] для достижения требуемого функционала.
#prog #rust
Кое-что я при написании этого поста упустил. Именно, к хэшированию предъявляется логичное требование: если два значения равны, то их хэши также равны.
Кое-что я при написании этого поста упустил. Именно, к хэшированию предъявляется логичное требование: если два значения равны, то их хэши также равны.
UnconditionallyEqual в том виде, как я написал, не реализовывает Hash, поэтому #[derive(Hash)] работать не будет. Тем не менее, заставлять программиста вручную писать реализацию Hash из-за наличия одного такого поля — не лучший UX. Поэтому стоит написать реализацию Hash для UnconditionallyEqual. Из-за изложенного выше требования и того факта, что UnconditionallyEqual не влияет на операцию равенства, корректной реализацией будет noop:impl<T> Hash for UnconditionallyEqual<T> {
fn hash<H: Hasher>(&self, _state: &mut H) {}
}🍌2
#prog #rust
Одна из клёвых вещей, которые есть в #zig — это синтаксис для многострочных строковых литералов:
Большим преимуществом этого синтаксиса является тот факт, что он позволяет с лёгкостью сделать отступ для литерала целиком и при этом не добавлять этот отступ в сам литерал, а также требует минимального экранирования (всё до перевода строки — содержимое строки). Аналогичный код в, скажем, Rust, требует такой неприятной вещи:
и поддержки со стороны компилятора для того, чтобы экранирование перевода строки убирало не только сам перевод строки, но и предшествующие пробельные символы. Обратите внимание на то, что мне также пришлось экранировать кавычки и
Именно по этой причине есть аж два RFC (раз, два), которые добавляют новые виды строковых литералов для подобных целей (включения многострочного кода на другом языке). Тем не менее, в обсуждении второго один человек заметил (и тут дела приобретают #abnormalprogramming оборот), что имитировать многострочные литералы из Zig в Rust в некоторой мере можно уже сейчас. Именно, doc-комментарии уже на этапе лексирования заменяются на
Одна из клёвых вещей, которые есть в #zig — это синтаксис для многострочных строковых литералов:
const hello_world_in_c =
\\#include <stdio.h>
\\
\\int main(int argc, char **argv) {
\\ printf("hello world\n");
\\ return 0;
\\}
;
Большим преимуществом этого синтаксиса является тот факт, что он позволяет с лёгкостью сделать отступ для литерала целиком и при этом не добавлять этот отступ в сам литерал, а также требует минимального экранирования (всё до перевода строки — содержимое строки). Аналогичный код в, скажем, Rust, требует такой неприятной вещи:
let hello_world_in_c = "\
#include <stdio.h>\
\n\
\nint main(int argc, char **argv) {\
\n printf(\"hello world\\n\");\
\n return 0;\
\n}";
и поддержки со стороны компилятора для того, чтобы экранирование перевода строки убирало не только сам перевод строки, но и предшествующие пробельные символы. Обратите внимание на то, что мне также пришлось экранировать кавычки и
\n в коде. Сырые строковые литералы позволяют убрать экранирование, но не лишние отступы:let hello_world_in_c = r#"#include <stdio.h>
int main(int argc, char **argv) {
printf("hello world\n");
return 0;
}"#;
Именно по этой причине есть аж два RFC (раз, два), которые добавляют новые виды строковых литералов для подобных целей (включения многострочного кода на другом языке). Тем не менее, в обсуждении второго один человек заметил (и тут дела приобретают #abnormalprogramming оборот), что имитировать многострочные литералы из Zig в Rust в некоторой мере можно уже сейчас. Именно, doc-комментарии уже на этапе лексирования заменяются на
#[doc]-атрибуты, а потому можно макросом извлечь эти литералы из атрибутов и сконкатенировать:macro_rules! text {
(#[doc=$first_line:literal] $(#[doc=$lines:literal])*) => {
concat!($first_line, $("\n", $lines),*)
};
}
let hello_world_in_c = text! {
///#include <stdio.h>
///
///int main(int argc, char **argv) {
/// printf("hello world\n");
/// return 0;
///}
};GitHub
Propose code string literals by Diggsey · Pull Request #3450 · rust-lang/rfcs
Add a new syntax for multi-line string literals designed to contain code and play nicely with rustfmt.
Rendered
Rendered
🥴20❤2🤔2
#prog #rust #gamedev #amazingopensource (но пока сырое)
Revy is a proof-of-concept time-travel debugger for the Bevy game engine, built using Rerun.
Revy works by snapshotting diffs of the Bevy database every frame that are then logged into the Rerun database.
This allows you to inspect and visualize the state of the engine at any point in time, either in real-time or after the fact.
These recordings can then be shared to be replayed or e.g. attached to bug reports.
👍7🤔3❤1
#prog #rust #rustreleasenotes
Вышла версия Rust 1.93.0! Как обычно, тут только избранное, остальное только в детальных заметках о релизе.
(да, я выкладываю анонс с опозданием больше недели, и что ты мне сделаешь?)
В этот раз большинство изменений связаны со стандартной библиотекой.
▪️Немного печальное изменение: даже с установлением
▪️Компилятор теперь выдаёт предупреждение на #[repr(C)]-перечисления, у которых значения дискриминантов не умещаются в сишный int. Смешно, но до C23 у сишных
▪️Компилятор теперь выдаёт предупреждение на попытку вызвать метод на const-значении, который эксплуатирует внутреннюю изменяемость. Так как константы инлайнятся по месту использования, эти изменения не будут видны. Обычно это является следствием ошибки — использования
▪️Метод
▪️Стабилизированы API:
🔸assume_init_drop, assume_init_ref и assume_init_mut на
🔸
🔸
🔸методы для непроверяемых (и потому потенциально дающих UB) битовых сдвигов в обе стороны на примитивных числах, а на знаковых — ещё и unchecked_neg
🔸методы для перевода ссылок на слайсы в (опциональные, разумеется) ссылки на массивы и сырых указателей на слайсы в сырые указатели на массивы, включая мутабельные варианты
🔸pop_front_if и pop_back_if на
🔸std::fmt::from_fn для ad-hoc форматирования через переданную функцию. Позволяет избежать создания одноразовых типов, нужных только для форматирования
▪️Cargo теперь прокидывает значение конфигурации debug_assertions в билд-скрипты.
▪️Команда
▪️
Вышла версия Rust 1.93.0! Как обычно, тут только избранное, остальное только в детальных заметках о релизе.
(да, я выкладываю анонс с опозданием больше недели, и что ты мне сделаешь?)
В этот раз большинство изменений связаны со стандартной библиотекой.
▪️Немного печальное изменение: даже с установлением
#[global_allocator] std всё ещё может вызывать System аллокатор. По крайней мере, на core это не распространяется.▪️Компилятор теперь выдаёт предупреждение на #[repr(C)]-перечисления, у которых значения дискриминантов не умещаются в сишный int. Смешно, но до C23 у сишных
enum нельзя было указать численный тип и нельзя было использовать значения больше int для дискриминантов.▪️Компилятор теперь выдаёт предупреждение на попытку вызвать метод на const-значении, который эксплуатирует внутреннюю изменяемость. Так как константы инлайнятся по месту использования, эти изменения не будут видны. Обычно это является следствием ошибки — использования
const вместо static (и я, кстати, такую ошибку уже делал).▪️Метод
append на BTreeSet и BTreeMap теперь сохраняет ключ из исходной коллекции, если ключи считаются равными через ==. До этого перезаписывались и ключ, и значение.▪️Стабилизированы API:
🔸assume_init_drop, assume_init_ref и assume_init_mut на
MaybeUninit<T>🔸
write_{copy, clone}_of_slice на [MaybeUninit<T>]🔸
into_raw_parts на String и на Vec. Странно, что только сейчас, потому что обратные from_raw_parts методы были стабилизированы ещё в версии 1.0.🔸методы для непроверяемых (и потому потенциально дающих UB) битовых сдвигов в обе стороны на примитивных числах, а на знаковых — ещё и unchecked_neg
🔸методы для перевода ссылок на слайсы в (опциональные, разумеется) ссылки на массивы и сырых указателей на слайсы в сырые указатели на массивы, включая мутабельные варианты
🔸pop_front_if и pop_back_if на
VecDeque🔸std::fmt::from_fn для ad-hoc форматирования через переданную функцию. Позволяет избежать создания одноразовых типов, нужных только для форматирования
▪️Cargo теперь прокидывает значение конфигурации debug_assertions в билд-скрипты.
▪️Команда
cargo tree теперь позволяет использовать для форматирования записей в дереве длинные имена для элементов шаблонов. Например, следующие два вызова эквивалентны:cargo tree --format='{p} {f}'cargo tree --format='{package} {features}'▪️
cargo clean теперь может удалять артефакты компиляции из всего workspace-а целиком.👍8😁1