Pull to refresh

Comments 22

Я понимаю, что статья посвящена использованию ASIO, но у меня по первому "наивному" примеру с использованием треда на каждый запрос, есть такой вопрос - почему не сделать тред не на запрос, а на диск, и какую то очередь с заданиями (может несколько, если нужны приоритеты)?

Кажется, что такое решение заняло бы несколько десятков строк, но было бы полностью кросс платформенным, и не тянуло бы за собой достаточно увесистую библиотеку?

Если поток запросил операцию ввода-вывода обычным линуховым API (не io_uring), он останавливается до завершения операции, так как обычные операции являются чисто синхронными. Соответственно, несколько операций ввода-вывода одновременно выполняться не будут, завершение их будет происходить в строго определённом порядке и т.п. -- а не как при асинхронном вводе-выводе.

Вопрос отличный!) Дело в том что если смотреть на внутренности asio то там в некоторых случаях так и происходит. Вы можете создать asio::thread_pool и использовать его только с одним диском. Да на самом деле создать поток, и запустить в нем asio::io_context сделает то же самое. И потом использовать thread_pool или io_context (библиотека называет их execution_contexts) вместе с нужным диском. В каждом контексте действительно есть своя очередь которая и будет разгребаться потоком/потоками.

Если говорить про размер asio, позволю себе отметить что standalone версия библиотеки представляет из себя header only библиотеку что, по моему скромному мнению, достаточно легковесно:)

на Windows используется winiocp

Странное какое-то название. Не гуглится совсем. Нет ли у вас про него ссылки, чтобы можно было понять, что это вообще?

И непонятно зачем оно нужно - в Windows, ещё со времен NT поддерка асинхронного ввода-вывода для любых устройств встроена прямо в планировщик операций ввода-вывода в ядре, и доступна через стандартные системные вызовы ReadFile/WriteFile Win32 API(т.е. NtReadFile/NtWriteFile Native API ядра). Возможно - это какая-то прослойка, которая обеспечивает независимость библиотеки от платформы?

Про I/Completion Ports знаю (ещё со времен Win2K - они там появились), а про winiocp - не знаю, так что хочу подтверждения от автора. А лучше - добавление в текст статьи, чтобы сразу было понятно, какой именно системный механизм используется версией Asio под Windows. Либо можно вообще про Windows не упоминать - она к теме статьи с боку припёку.

Я имел ввиду Windows IO Conpletion Ports. asio называет этот функционал winiocp в исходниках, поэтому так и написал :) К сожалению я не уделял много внимания тому как работает asio на винде, знаю только то что asio::random_access_file будет вам доступен если вы на windows благодаря io completion ports. Упомянул я об этом только для того чтобы дать понять что эта фича библиотеки работает не на всех платформах.

Так понятно. Благодарю.

Подозреваю, здесь автор имел ввиду windows IO completion ports. Об этом много написано в MSDN, отмечу лишь, что есть база родом из 90х, о которой Вы справедливо упомянули. И есть новаторство из Vista or higher - см TP_IO и пр, где предоставили новые расширяемые и кастомизируемые интерфейсы и улучшения для многопоточного асинхронного ввода вывода, в т.ч. удобства для организации thread pools (TP), очередей, шедулеров и пр. Обвес удобный можно найти в WIL от MSFT, примеры использования в spdlog, IIRC.

Специально для решения этих проблем есть корутины, которые асио поддерживает

Думаю стоило добавить что мы ещё не используем C++20 :) Я точно помню что видел в asio поддержку boost::fiber, однако сейчас что-то не могу найти

https://think-async.com/Asio/asio-1.28.0/doc/asio/reference.html

В итоге на C++17 остаётся использовать только колбеки или std::future из коробки

Вы правы!

The spawn() function is a high-level wrapper over the Boost.Coroutine library.

К сожалению ещё не встречался с этим корутинами, обязательно обращу на них внимание :) Спасибо!

Поддержу boost::coroutine - вполне рабочее решение.

Но для борьбы с callback hell при работе с асинхронными API может быть стрельбой по воробъям. В состав boost::asio входят stackless coroutines: https://think-async.com/Asio/asio-1.22.0/doc/asio/overview/core/coroutine.html

По сути, это duff device на стероидах и имеет определенные (серьезные) ограничения. Но при правильном использовании позволяет очень эффективно реализовывать конечные автоматы, а оверхед при их использовании минимален - весь механизм запоминания состояния использует один int.

Автор хочет выжать производительность и вместо знаний системного апи полагается на asio. Замечу, последний обладает оверхедом, который контролируется сторонним производителем. Сделать тонкую и лаконичную обвязку системных функций для своих нужд ядро не может?

Большое спасибо за ваш комментарий! Сейчас опишу все как есть:

  1. Я еще раз перечитал статью и не нашел, где я говорил, что хочу выжать максимум производительности.

  2. Мы не используем Asio на самом нагруженном пути, лишь во вспомогательных задачах.

  1. Воспринял, что речь идёт именно о производительности, поскольку Вы написали, что занимаетесь "реализацией эффективных IO-bound программ".

  2. Поддерживаю.

Хотите ещё быстрее, чем на io_uring? Можно всё прямо в user-space делать на Storage Performance Development Kit SPDK. В режиме poll, драйвер NVMe вам через неблокирующиеся ring buffer будет отдавать в user-space блоки, где можно sched_setaffinity() треды прибить гвоздями к CPU конкретным, и вообще всё будет хорошо с процессорными кэшами.

Крутые дядьки объединяют Data Plane Development Kit DPDK с SPDK для быстрых сетевых хранилищ данных.

В DPDK также сетевая карточка не видна ядру, а через ring buffer пробрасывается в user space, где у вас свой "быстрый" сетевой стек. Одна только структура sk_buff чего стоит, ни в какие кеши не лезет, не то что FreeBSD'шный mbuf простой.

Спасибо, у Вас получился интересный и полезный мини-обзор! А не можете пояснить что значит проброс через ring buffer? Имеется в виду какой-то конкретный API или просто некая реализация кольцевого буфера с его mmap-ом в userspace? Если последнее, то интересно, а можно ли программу заставить не спинлочно ждать новые данные в кольцевом буфере (на ум приходит только ioctl дёргать)? Буду очень признателен за пояснение!

Да, это конкретный API фреймворка DPDK, вот конкретно [ring buffer])https://doc.dpdk.org/guides/prog_guide/ring_lib.html)
Там вся соль в том, чтобы без ioctl(), чтобы всё в user-space отрабатывать и не ходить в ядро.

RTE ring — это таки очереди в памяти. С очередями сетевых карт напрямую работают poll mode drivers через компонент ethdev.

Спасибо за пояснение и за наводку на dpdk. Коварные ручонки иногда чешутся промапить переферию в свои объятия. Очень рад, что для этого нашёлся сборник рецептов!

Sign up to leave a comment.