Swift — это язык программирования от Apple, который за последние годы превратился из удобного языка для разработки мобильных приложений в полноценный системный язык со всеми атрибутами высокопроизводительных технологий. Насколько же быстрым может быть Swift по сравнению с традиционными системными языками, такими как Rust, или же языками, более ориентированными на простоту и кроссплатформенность, например, JavaScript? Ответ на этот вопрос будет комплексным и требует глубокого погружения в практическую реализацию задач, близких к вычислительной нагрузке, а не только рассмотрения теоретических характеристик языка. Для оценки производительности часто берутся примеры, имитирующие симуляцию миллионов частиц, где задачи состоят в обновлении их координат и отрисовке на экране, что хорошо тестирует скорость работы с памятью, эффективность кэширования и возможности многопоточной обработки. В одном из таких проектов Swift был протестирован на симуляции до сотен миллионов частиц, что позволило выявить далеко не очевидные ограничения и сильные стороны языка и его экосистемы. Начинается всё с простого CPU-симулятора, где частицы представлены в виде структур с координатами и скоростями.
Код изначально построен вокруг массивов с высокой плотностью данных, что максимально улучшает доступ к кэшу CPU, а сама логика реализует обновление положения каждой частицы и последующий рендер её в пиксели на экране. Однако, первые попытки используют SwiftUI и связанные с ним API для отрисовки, что выливается в существенные проблемы с производительностью из-за особенностей системы ререндеринга и иммутабельности изображений в фреймворке. Для обхода этих ограничений разработчик переключается на использование CGContext и прямого управления байтовыми буферами пикселей, что ближе к прямой работе с памятью и позволяет обновлять изображение намного эффективнее. В это же время становится понятно, что простая отрисовка с помощью CPU и стандартных интерфейсов быстрая только до нескольких миллионов частиц, после чего возникают проблемы с пропускной способностью памяти и когерентностью данных. Следующим шагом в попытке улучшить производительность становится переход к Metal — графическому API Apple, позволяющему работать «ближе к металлу» и использовать GPU для рендеринга.
В версии с Metal частицы обновляются на CPU, а для вывода применяется текстура, которую GPU копирует на экран с помощью шейдера. Здесь впервые появляется возможность синхронизировать совместное использование памяти между CPU и GPU благодаря общему буферу, что устраняет излишнее копирование данных. Несмотря на кажущуюся сложность Metal API, результатом становится более стабильная отрисовка с частотой, синхронизированной с частотой обновления экрана. Однако, самый заметный прирост производительности достигается благодаря многопоточности. Система Grand Central Dispatch позволяет распределить обновление частиц и их отрисовку по нескольким потокам без необходимости сложной синхронизации, что существенно ускоряет выполнение кода на многоядерных процессорах Apple Silicon.
Здесь разбивка массива частиц на блоки и параллельная обработка этих блоков обеспечивает превосходство Swift над одноядерными реализациями и даже над многопоточными вариантами на менее оптимизированных языках. При этом становится ясно, что несмотря на отличные возможности Swift, язык всё еще несколько уступает Rust по скорости. Причина кроется в различиях в подходах к использованию SIMD инструкций, более зрелой системе управления памятью в Rust и иных архитектурных решениях, позволяющих Rust обходить узкие места в производительности. Тем не менее Swift превосходит JavaScript с большим отрывом, особенно на больших объёмах данных, где возможности JVM и JIT-интерпретаторов не позволяют достичь сравнимой скорости. Интересной находкой в ходе оптимизаций становится идея сокращения объёма информации, которую нужно обновлять в памяти.
Переход от хранения информации о каждом пикселе с четырьмя байтами (RGBA) на использование однобайтовой маски видимости частицы на экране сильно снижает нагрузку на пропускную способность памяти. Этот приём вспоминается из JavaScript-версий кода и повторяется в Swift с использованием Metal, что существенно увеличивает частоту кадров и плавность симуляции при больших количествах частиц. Шейдеры GPU, используемые для отрисовки, тоже претерпевают изменения, где блок с простым отображением наличия частицы уступает место более сложным фрагментным шейдерам, позволяющих окрашивать частицы в разные цвета в зависимости от их свойств или индексирования. Такой подход не только повышает визуальную привлекательность симуляции, но и демонстрирует возможности по эффективному использованию GPU для ускорения рендеринга без существенного увеличения нагрузки на CPU. Не обходится и без обсуждения альтернативных путей рендеринга, например GPU-инстансинга.
Несмотря на популярность данного подхода в графике, он показывает себя менее эффективным для задач с огромным количеством частиц и постоянным обновлением их состояний, так как требует большой массовой передачи данных между CPU и GPU. В случае Swift с Metal возможность разделения общей памяти между процессорами снижает эти издержки, но пока технологии не позволяют полностью отказаться от CPU-вычислений для данной задачи. Наглядным примером достижений Sweet в производительности становится эксперимент с полным переносом вычислений на Metal compute shader. Этот подход, благодаря высокой параллелизации и меньшим затратам на синхронизацию, даёт возможность запускать симуляции с десятками миллионов и даже сотнями миллионов частиц с приемлемой частотой кадров на современных Apple Silicon, включая мощнейшие M4 Pro. В таких условиях производительность Swift уже практически не ограничивается самим языком, а зависит от аппаратной платформы и качества реализации низкоуровневого кода.
Итоговое сравнение показывает, что Swift является исключительно конкурентоспособным языком в плане вычислительной скорости: он значительно превосходит JavaScript и немного уступает Rust. Уникальной особенностью Swift становится тесная интеграция с аппаратным обеспечением Apple, позволяющая использовать Metal и высокоуровневые API с низкой задержкой и высокой эффективностью, что при правильной реализации даёт ощутимые преимущества для разработчиков, работающих с графикой и вычислениями в экосистеме Apple. Тем не менее, можно отметить, что Swift остаётся своего рода ограниченным «котом в яблочном саду» — сильным и гибким, но чётко ограниченным рамками экосистемы. Ограничения, налагаемые Apple на доступ к инструментам и лицензированию, усложняют широкое использование языка за пределами платформ Apple, что иногда вызывает дискуссии о свободе и универсальности разработки. Разбирая технические детали и сопоставляя разные итерации реализации симулятора, можно заметить, что процент производительности зависит не только от самого языка, но и от правильного выбора подходов: начиная от организации данных в памяти, использования SIMD, правильной работы с многопоточностью и заканчивая грамотным применением Metal и шейдеров.