Пожалуйста, поймите меня правильно. Си я люблю и уважаю. В студенческие годы я провел много увлекательных вечеров за изучением WinAPI и POSIX , написанием своих архиваторов и даже драйверов для Windows XP. Однако так сложилось, что в своей профессиональной деятельности я пишу на более высокоуровневых языках. Вот о том, почему в программах, написанных на таких языках, следует избегать использования Си, мне и хотелось бы поведать.
История идет по кругу.
Приходишь ты в новую команду поддерживать проект на Erlang . Ура, наконец-то функциональщина! Неизменяемые переменные, честная сборка мусора, акторы, все дела! Виртуальная машина сама распределяет время между тысячами легковесных процессов. Можно обслуживать десятки тысяч TCP соединений и не думать особо головой. Как Erlang это удается? Очень просто — все, что делает виртуальная машина, имеет некую стоимость в так называемых редукциях. Когда процесс выполняет определенное количество редукций (по умолчанию 2000) управление передается следующему процессу.
К сожалению, работает это не очень быстро. Точных цифр я уже не помню, но условно говоря декодирование 50 мегабайт JSON’а на чистом Erlang может запросто растянутся на минуты . То есть, обработка довольно простого HTTP запроса может занимать минуту только за счет времени, необходимого на его декодирование. Беда. Что делать? А давайте просто возьмем либу, написанную на Си . Си же быстрый! Что может пойти не так? И действительно, теперь один запрос обрабатывается всего лишь за 5 секунд. Жизнь прекрасна и удивительна.
А потом происходит страшное. К вам приходит десяток запросов одновременно и сервер перестает отвечать. Что случилось? Просто виртуальная машина Erlang’а ничего не контролирует внутри кода, написанного на Си. Никаких редукций, никакого распределения времени между процессами. У вас было N ниток операционной системы. Пришло N запросов, все нитки бросились декодировать JSON. Процессы, отвечающие за работу всей остальной системы, спят и видят во сне лямбдочки. Звонок домой в два часа ночи, срочная отладка, курение логов, метрик, кода, ничего непонятно, хотфикс, еще хотфикс, кровь, кишки, содомия! Справедливости ради отметим, что в Erlang R17 появился способ кое-как обойти эту проблему, но в те времена R17 еще не было.
Приходишь ты в новую команду поддерживать проект на Scala . Ура, наконец-то строгая статическая типизация! JIT компилятор, никаких накладных расходов на копирование данных из одной кучи в другую, тонна готовых библиотек, акторы, все дела! Можно спокойно перемножать матрички и скорее всего это почти всегда будет работать достаточно быстро. К сожалению, по каким-то причинам команда полна решимости использовать в проекте новую модную библиотеку, которая появилась на свет всего лишь пару лет назад. Нет, библиотека отличная. Одна проблема — она написана на Си и под JVM ее еще никто не переписал.
Что делать? А может быть просто возьмем чуть менее новую библиотеку, зато проверенную временем и переписанную на все мыслимые языки? Но зачем, если можно просто воспользоваться JNI! Что может пойти не так? И действительно, библиотека без особого труда прицепилась к сервер сайду, все работает, все отлично. Жизнь прекрасна и удивительна.
А потом происходит страшное. Библиотеку пытаются прикрутить разработчики клиент сайда. Проблема в том, что клиент сайд — это приложения для десктопа, Android и iOS. Тонны непонятной ругани Clang при компиляции, неправильные пути к .so’шникам, локально собирается, а в TeamCity — нет, кросскомпиляция под разные платформы, человеконедели потерянного времени, кровь, кишки, содомия!
Если у вас есть выбор — использовать в проекте Си или не использовать, лучше не используйте. На моей памяти это еще ни разу не заканчивалось ничем хорошим. Напишите отдельный сервис на Scala или Go, заведите кэшик, покажите пользователю «ваш запрос обрабатывается», убедите начальство купить пару лишних серверов. Избегайте использования Си любой ценой. Себе же сэкономите время и нервы.
Дополнение: Также вам может быть интересно, что в 12-ой главе чудной книги Java Performance: The Definitive Guide автор категорически не рекомендует использовать JNI, так как в этом случае программа скорее всего будет работать медленнее обычного кода на Java (ведь у нас есть JIT!) из-за накладных расходов на копирование данных между JVM и нативным кодом. Собственно, это справедливо не только в отношении JVM.