В статье об особенностях новой версии Visual Studio одним из главных нововведений (с моей точки зрения) оказалось разделение ранее монолитного процесса среды разработки (devenv.exe) на компоненты, которые будут работать в отдельных процессах. Это уже сделано для системы контроля версий (переезд с libgit на git.exe) и некоторых плагинов, а в будущем и другие части VS будут вынесены в подпроцессы. В связи с этим в комментариях возник вопрос: «А не замедлит ли это работу, ведь обмен данными между процессами требует использования IPC (Inter Process Communications)?»

Нет, не замедлит. И вот почему.

Скорость


Для организации общения между процессами в Windows существуют различные технологии: сокеты, именованные каналы, разделяемая память, обмен сообщениями. Поскольку писать полноценный бенчмарк всего вышеуказанного мне не хочется, давайте быстренько поищем что-нибудь похожее на Хабре и найдём статью 6-летней давности, в которой adontz сравнивал производительность сокетов и именованных каналов. Результаты: сокеты — 160 мегабайт в секунду, именованные каналы — 755 мегабайт в секунду. При этом нужно делать поправку на железо 6-летней давности и использованную для тестов платфому .NET. Т.е. можно смело утверждать, что на современном железе с кодом, например, на С мы получим несколько гигабайт в секунду. При этом, как подсказывает Википедия, скорость, например, работы памяти DDR3 составляет, в зависимости от частоты, от 6400 до 19200 МБ/с — и это ведь идеальных МБ/с в вакууме, на практике всегда будет меньше.

Вывод 1: скорость работы пайпов всего в несколько раз меньше максимально возможной скорости работы оперативной памяти. Не в тысячи раз меньше, не на порядки, а всего в несколько раз. Современные ОС хорошо делают свою работу.

Объёмы данных


Давайте возьмём всё те же 755 МБ/с из абзаца выше, как скорость работы именованных каналов. Много это или мало? Ну, если бы вы, например, писали приложение, которое получало бы из именованного канала несжатое FullHD-видео с частотой 60 кадров в секунду и что-нибудь с ним делали (кодировали или стримили) — то вам бы для этого хватило бы 355 МБ/с. Т.е. даже для такой очень затратной операции скорости именованного канала хватило бы с запасом в два с лишним разом. Чем же оперирует Visual Studio в общении со своими компонентами? Ну, например, командами для git.exe и данными из его ответа. Это считанные килобайты, в очень редких случаях — мегабайты. Обмен данными с плагинами вряд ли можно точно оценить (очень разные бывают плагины). Но в любом случае, ни для одного плагина, который я видел, не нужны сотни мегабайт в секунду.

Вывод 2: с учетом специфики данных, обрабатываемых Visual Studio (текст, код, ресурсы, картинки), скорости работы именованных каналов хватает с многократным запасом.

Latency


Ну ок, скажете вы, скорость-скоростью, но есть же ещё и latency. Каждая операция ведь потребует каких-то накладных расходов на синхронизацию. Да, потребует. И об этом я недавно публиковал статью. Люди переоценивают накладные расходы на блокировки и синхронизацию. Беда там не в самих локах (они занимают наносекунды), а в том, что люди пишут плохой синхронный код, допускают дедлоки, лайвлоки, гонки и повреждение разделяемой памяти. К счастью, в случае с именованными каналами сам API намекает на преимущества асинхронного подхода и написать корректно работающий код не так уж сложно.

Вывод 3: пока в асинхронном\многопоточном коде нет багов — он работает достаточно быстро, даже с блокировками.

Практический пример


Ну ок, скажете вы, хватит теории, нужно практическое доказательство! И оно у нас имеется. Это одно из самых популярных в мире десктопных приложений — браузер Google Chrome. Созданный изначально в виде нескольких взаимодействующих процессов, Chrome сразу показал преимущества этого подхода — одна вкладка перестала вешать остальные, смерть плагина не означала больше падение браузера, нагрузка в прорисовке контента в одном окне больше не гарантировала тормозов в другом и т.д. Хром, если упрощённо, запускает один главный процесс, отдельные процессы для рендеринга вкладок, взаимодействия с GPU, плагинов (на самом деле там правила чуть хитрее, Хром умеет оптимизировать количество дочерних процессов в зависимости от разных обстоятельств, но это сейчас не очень важно).

Почитать об архитектуре Хрома можно у них в документации, но вот вам упрощённая картинка:

image

Что же на этой картинке скрывает под линией с раскраской «зебра» и надписью IPC? А вот как-раз именованные каналы и скрываются. Их можно увидеть, например, с помощью приложения Process Hacker (вкладка Handles):


Ну или с помощью Api Monitor:



Насколько же именованные каналы тормозят Chrome? Вы и сами знаете ответ на этот вопрос: ни на сколько. Мультипроцессная архитектура ускоряет браузер, позволяя лучше распределять нагрузку между ядрами, лучше контролировать производительность процессов, эффективнее использовать память. Давайте, например, оценим, сколько данных Chrome гоняет через свои именованные каналы при проигрывании одной минуты видео с Youtube. Для этого можно воспользоваться хорошей утилитой IO Ninja (нормально поток данных по пайпу, к их стыду, не показывают ни Wireshark, ни API Monitor, ни утилиты Sysinternals — позор!):



Замер показал, что за 1 минуту проигрывания Youtube-видео Chrome передаёт через именованные каналы 76 МБ данных. При этом отдельных операций чтения\записи произошло 79715 штук. Как видите, даже такую серьёзную программу, как Chrome, даже на таком неслабом сайте, как Youtube, именованные каналы ни сколько не смутили. Так что и у Visual Studio есть все шансы выиграть от разделения монолитной IDE на подпроцессы.
Поделиться с друзьями
-->

Комментарии (7)


  1. ZurgInq
    26.10.2016 13:15

    А в каком формате гоняются данные по именованным каналам? Если свой формат сообщений, то их ещё надо расспарсить. Если сырые данные — то всё равно надо разделять пакеты данных друг от друга, плюс накладные расходы на прочитать из канала и записать себе в процесс.


  1. laphroaig
    26.10.2016 14:32

    Скорость в несколько ГБ в секунду вы получите только если будете писать потоком в одну сторону и правильно подберете размер буфера (порядка 16К за раз). При обмене сообщениями размером ~100Б получите скорость на порядок меньше. Для десктопных приложений, и VS возможно это не критично, но вы все равно получите колоссальный оверхед по сравнению с прямым доступом в память. Любая запись в пайп это тяжеловесный системный вызов, поэтому переживать по поводу мьютексов действительно не стоит (но не потому что они такие быстрые). Так что не вводите людей в заблуждение. А VS мог бы и через файлы на ssd с таким же успехом обмениваться без особых «тормозов»


  1. maydjin
    26.10.2016 17:44
    +1

    Лучше бы написали, ради чего это делается скорее всего. Вот это действительно интересная тема, почему-то, до сих пор не освещённая на хабре.


  1. xtraroman
    26.10.2016 19:40

    Интересная статья, только я бы не стал переводить на русский устоявшийся термин Named Pipes.


    1. Ivanhoe
      27.10.2016 20:31

      Множество раз видел переведенный термин, в т.ч. в книгах Рихтера, Соломона и Руссиновича.


  1. isotoxin
    26.10.2016 22:02

    Я гонял через pipe между двух процессов несжатое full-hd видео. Все было ok, но меня напрягали огромные цифры в колонке I/O в process hacker-е. Немного переделал. Один процесс создает именованный memory mapped файл и отдает его имя через pipe. Так получилось даже быстрее, т.к. я подготавливал все данные в этом файле и весь блок (разжатый full-hd кадр) без копирования как есть отправлялся в другой процесс.


  1. Lauren
    27.10.2016 07:04

    хочу отметить, что максимальная скорость передачи между процессами может быть больше скорости оперативной памяти. всё дело в том, что на самом деле копировать данные нет необходимости, достаточно передать только указатель.Но из-за безопасности в лоб так сделать нельзя, только через разделяемую память. К сожалению у меня всё руки не доходят выложить свою библиотеки высокоскоростной передачи данных между процессами(скорость равна размер блока данных/(скорость вызова 2 мютексов и 1 события)).