amarao (amarao_san) wrote,
amarao
amarao_san

Categories:

Shared memory crazy fast

Итог трёх дней моей возни с проблемой... Грандиозный успех safe and sound Rust'а.

Предистория: Я решил-таки сделать себе в программе рисования удобно, как было на zx-spectrum. То есть - программа рисования рисует себе в цикле, когда нужно точечки на экран ставит, точечки появляются на экране и остаются там пока программа не перезапишет. Никаких спецусилий, никаких event loop'ов и т.д.

Я хотел иметь то же самое в условиях оконного отображения. Оконное отображение - это event loop, надо реагировать на кнопочки и команды выхода. Если мы в цикле 10с считаем Солнышко Лиссажу, то мы 10с не реагируем на кнопки (aka зависли). Классические подходы: резать задачу на кусочки и перемежать их с ответом на event loop. Плюсы: относительно просто. Минусы - не ясно где резать, можно промахнуться с отправкой отрисованного и получить лаг на экране. Второй подход: соседний тред, который шлёт мелкие сообщения об обновлениях, основной тред рисует их на экране и отвечает за кнопки. Плюсы: если сообщения мелкие, практически гарантировано можно успеть ответить на event. Минусы: безумные накладные расходы на сообщения, проблема "очереди" (либо блокирующая, либо память течёт). В целом, если точки генерируются достаточно быстро, то снова та же проблема "когда прекратить принимать сообщения и отправлять кадр на рендеринг"?

Моя идея: shared область памяти, в которую рисующий (считающий) тред пишет непрерывно (как хочет), а отображающий заглядывает для обновления текстуры. Схватил текстуру, отобразил в буффер, дальше библиотека SDL (да и любая другая) с режимом vsync вернёт управление как только будет отрисован кадр на экране. Ляпота, да?

Только была пробелма: Rust не любит двойной доступ к памяти. Это тот самый danger quadrant, где конкуретный read и write.

В процессе я нырнул глубоко в unsafe, прочитал много про pointer'ы, в целом стал лучше понимать rust.

итог: код без атомиков (я себе таки его написал прямо сейчас) не работает от слова вообще. Один тред пишет, второй наслаждается кешем процессора.

Код с атомиками я сначала написал ужасно, с двойным указателем в один и тот же slice, отдаваемый в разные треды. unsafe, unsound and dangerous. Но он работал с офигенной скоростью в 13.5 ГБ/с (в режиме максимального congestion, когда один тред непрерывно меняет ячейку, а второй непрерывно её читает).

Мне сказали "переходи на arc". Для меня было открытием, что atomic позволяет сделать store для значения на non-mut ref, но я пошёл и написал. Стало порядка 9ГБ/с.

И тут мне сказали про Arc<[AtomicU32]>. Это было безумие! У меня скорость стала ещё выше - 17.5ГБ/с. Я подозреваю, что это подозрительно близко к скорости моей памяти (25.5ГБ/с в один канал), и, возможно, ограничена скоростью IPI (который как раз кеши процессорам и инвалидирует).

При этом в коде 0 unsafe!!! Я писаю кипятком.

А ещё я проверил насколько проверка индексов всё тормозит. Ответ: мало тормозит. около 0.2с (с 9.9s на 10 прогонов полного цикла по u32 до 9.7s), и оно того не стоит.

Фух. Сейчас я пойду приделывать это всё в свой основной проект. Подумав головой, я хочу сделать хитрое - засунуть рисовалку в дополнительный тред, а в основном оставить только код рисования.

Чтобы было так:

fn main(){
let mut screen = Screen::new();
for i in foo(){
screen.put_pixel(x, y);
}
screen.done();
}

А всякая SDL'ная фигня была в этом самом screen::new().

Это будет моя спектрумовская мечта. Берёшь и рисуешь, no thinking required. Я думаю, она много кому понравится как библиотека, хотя рисковать делать библиотеку пока я не готов (вероятнее всего, задумаюсь, по результатам успеха).

PS Вот proof-of-concept: https://github.com/amarao/rust_dual/blob/arc/src/main.rs
Tags: equart, rust
Subscribe

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 6 comments