1.85K subscribers
3.38K photos
134 videos
15 files
3.64K links
Блог со звёздочкой.

Много репостов, немножко программирования.

Небольшое прикольное комьюнити: @decltype_chat_ptr_t
Автор: @insert_reference_here
Download Telegram
#meme про... Бомжей
открытка @hayork
😁15
Forwarded from Дневник ChatGPT
Иногда кажется, что взрослая жизнь – это детский сад с добавкой кофеина. Мы все тут взрослые, но хотим вечернее печенье и спать с плюшевым мишкой
😢71👍1
Forwarded from shitposting 3.0 [+ dragons]
🤝22😁6🌚3
Табы или пробелы?
Anonymous Poll
53%
47%
🤣38😁1
Блог*
#prog Подписчики, я обескуражен. По идее, calloc быстрее malloc + memset за счёт того, что операционная система может считерить и вместо реальной аллокации памяти смапить всю выделенную виртуальную память на одну и ту же заранее выделенную страницу с нулями.…
Так, всё стало ещё страннее.

Я тут обратил внимание, что вообще-то выделял память с выравниванием 1 🤦‍♂️
Начал выделять с выравниванием на страницу и результаты стали совсем странными.

Во-первых, они стали более шумными (особенно для размеров до 200 страниц включительно).

Во-вторых, на playground calloc теперь гораздо ближе по времени к malloc + memset. Иногда время может отличаться в два раза, причём в обе стороны (!).

В-третьих, на моём ноутбуке calloc иногда либо столько же времени работает, либо быстрее в полтора или два раза.

Я в ещё большем недоумении.
🤔5👍1
Forwarded from rusta::mann
При устройстве на работу:
- Условия мне нравятся, и индустрия у вас интересная, но скажите, какой у вас технологический стек?
- Мы используем Rust и Tokio на бэкенде

@ Rust и Tokio на бэкенде
👏12🥴6😁2😱1
Forwarded from shitposting 3.0 [+ dragons]
15😁10🥴4
Эм...
👍15🌚1
Блог*
💅
💅
🥴5🎉3🤮3🤣1🫡1
Погладил лысого
🥰7🌚2
Forwarded from Сол / Hacking Valhalla (Sōl Astrī)
Какая же жиза.
🔥7🤡1
Блог*
💅
🔥20🤮13🤡5👎4👍3😁1
#prog #rust #моё

(этот текст является логическим продолжением моей статьи о compile time Fizzbuzz, так что если вы её ещё не читали — настоятельно рекомендую начать с неё)

Кое-что при реализации всего этого дела я упустил: конечным продуктом всех этих type-level-конструкций является гетерогенный список из enum, и код для вывода итоговой строки подгружает эти enum, бранчится по им и для каждого вызывает println!("{}", s.as_str()). В оптимизированном коде бранчи, разумеется, уходят — так как они известны статически — но даже так на каждый Str вызывается _print, которая в конечном счёте вызывает io::Write::write_fmt на LineWriter. Это, мягко говоря, неоптимально.

Как же можно было сделать лучше? Таки сделать ответ одной строкой, которая ляжет литералом в сегмент данных, и вызвать write_all на Stdout. Это всё ещё пойдёт через построчную буферизацию, но всё же не будет вызывать машинерию std::fmt, которая очень плохо вычищается оптимизатором. Можно было бы сделать ещё быстрее путём вызова системных функций вызова напрямую, но этот подход требует небезопасного и вдобавок платформо-зависимого кода, так что я решил не идти по этому пути.

Самое обидное, что получение ответа в виде одного слайса не просто возможно, а было возможно ещё на момент публикации статьи на актуальной тогда версии Rust 1.55.0! Но, впрочем, достаточно с введением, перейдём к делу.

Итак, получение байтиков. Как нам это сделать? Нужно взять массив байтов нужного размера и последовательно записать в него нужные значения. Размер искомого массива подсчитать, в принципе, возможно — мы знаем, сколько байт нужно на каждое число, сколько байт нужно на каждое слово — нужно только не забыть прибавить по месту на перевод строки на каждое значение в списке.

Подумаем немного о том, как именно мы будем заполнять этот массив. Заполнение его с начала кажется логичным, но по факту сопряжено с трудностями. Создание нужного значения как ассоциированной константы не представляется возможным, поскольку поток управления будет идти от голов Cons-ов к хвостам, но при индуктивных определениях констант поток данных идёт, наоборот, от конечного Nil вверх к обрамляющим Cons. Прибавим к этому тот факт, что прямое заполнение наверняка потребовало бы методов на трейтах (но это не точно), которые пока не поддерживаются в const fn, а также тот факт, что наиболее очевидный способ получения цифр числа выдаёт их в "обратном" порядке — от младших разрядов к старшим — и вам станет понятно, почему я решил заполнять итоговый массив от конца к началу.

Теперь немного подумаем об интерфейсе для этих операций. Наиболее удобно было бы отдавать в функции мутабельные ссылки на слайсы, но мутабельные ссылки в const fn, опять-таки, пока не разрешены. Посему придётся принимать массив аргументом и его же и возвращать — как следствие, везде придётся тягать через const generics его размер. Так как слайсы использовать не получится, нам также придётся как-то обозначать место, в которое надо писать. Впрочем, ввиду заполнения с конца доступное место для записи всегда будет начинаться с нулевого индекса, а потому для передачи места записи достаточно одного индекса.

Кажется очевидным, что это должен быть индекс места, куда нужно писать очередной байт. Но не всё так просто. Дело в том, что после записи очередного символа это индекс нужно декрементировать. А что будет при записи нулевого байта — первого по порядку в итоговой строке и последнего по порядку записи? Будет декрементация нулевого индекса — и так как индексы в Rust беззнаковые, а вычислитель константных выражений ловит underflow, после записи нулевого байта произойдёт ошибка вычисления константного выражения, которая зафейлит всю компиляцию.

Для решения этой проблемы есть несколько способов решения разной степени поганости:

* использовать знаковые индексы — но тогда придётся кастить их на каждой индексации.
* каким-то образом отделять последнюю итерацию цикла заполнения — неудобно и придётся прокидывать через все операции.
* вместо условного idx -= 1 писать idx = idx.saturating_sub(1) — громоздко и, вообще говоря, избыточно.
3
Все эти способы объединяет один недостаток: они толком не позволяют отловить ситуацию, когда мы по каким-то причинам ошиблись в подсчёте размера итогового буфера и предоставили его меньшего размера, чем надо. Так что я в итоге выбрал способ, отличный, от всех этих: прокидываемый индекс является индексом, на единицу бо‌льшим места, куда нужно писать очередной байт. Безусловно, это означает - 1 на всех операциях индексации, но нам не нужно делать последнюю итерацию особым случаем, мы получим ошибку при использовании слишком маленького буфера, а убедиться в том, что буфер не слишком большой, мы можем пост-фактум, проверив, что индекс места для записи равен нулю.

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

const fn write_str_at<const N: usize>(
s: &str,
mut bytes: [u8; N],
mut at: usize,
) -> ([u8; N], usize) {
let mut i = s.len();
let s = s.as_bytes();

while i > 0 {
bytes[at - 1] = s[i - 1];
at -= 1;
i -= 1;
}

(bytes, at)
}

Ничего сложного, просто побайтовое копирование строки в нужное место (с конца). Конечно, тот факт, что мы не можем использовать богатый инструментарий std в const fn, делает этот код менее красивым, чем он мог бы быть, но и без этого код довольно понятен. Отмечу, что мы можем передать строку длиннее переданного буфера, но в этом случае код завалится на декременте at и в const-контексте прервёт компиляцию.

С использованием этой вспомогательной функции запись строки с переводом строки становится совсем простой:

const DELIMITER: &str = "\n";

const fn write_str_with_delimiter_at<const N: usize>(
s: &str,
mut bytes: [u8; N],
mut at: usize,
) -> ([u8; N], usize) {
(bytes, at) = write_str_at(DELIMITER, bytes, at);
write_str_at(s, bytes, at)
}

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

const fn write_num_with_delimiter_at<const N: usize>(
mut n: usize,
mut bytes: [u8; N],
mut at: usize,
) -> ([u8; N], usize) {
(bytes, at) = write_str_at(DELIMITER, bytes, at);

if n == 0 {
bytes[at - 1] = b'0';
at -= 1;
return (bytes, at);
}

while n != 0 {
bytes[at - 1] = (n % 10) as u8 + b'0';
at -= 1;
n /= 10;
}

(bytes, at)
}

(я не стал писать не-_delimiter вариант, потому что он и так использовался бы в одном месте)

Хорошо, нужные строительные блоки есть. Теперь нужно собрать их них что-то полезное. Для того, чтобы собрать массив байтиков для гетерогенного списка, нам нужно на шаге индукции получить заполненный массив для хвоста (вместе с местом, куда надо писать дальше) и дописать порцию, соответствующую голове. Базой индукции в этом случае (для Nil) будет чистый массив и at, равный его длине.