
Развлекаемся с экспериментальными функциями swift toolchain.
Вступление
Swift - очень удобный язык, хотя и у него есть свои причуды и своеобразная нелинейность обучения. Тем не менее, с его помощью вы можете довольно быстро выпустить готовый к работе код. Как бы то ни было, иногда у вас возникают чувствительные к производительности разделы, и Swift не очень помогает сократить их количество. В таких случаях популярным выбором становится использование языка C++.
И тут возникает вопрос: "как вызвать С++ функцию через Swift"? Чтобы сделать такой вызов, зачастую вам нужно будет написать оболочку Objective-C, которая будет выступать в качестве публичного интерфейса для вашего кода на C++. В подобных случаях Swift toolchain может помочь импортировать код Objective-C в Swift. Основное ограничение этого метода заключается в том, что вы не сможете использовать классы C++ в Objective-C, только простые POD-структуры.
Мы напишем сито алгоритма Эратосфена как на C++, так и на Swift. Затем узнаем, как активировать совместимость с C++ и вызвать код C++ из Swift, а за одно сравним производительность этих реализаций. Имейте в виду, что эта режим совместимости является экспериментальной функцией и она может быть в любой момент изменена. Данная статья писалась на версии Xcode 14.2.
Алгоритм
Сито Эратосфена находит все простые числа, меньшие или равные N. Простое число — это целое число, которое делится только на себя и на 1. Алгоритм создает логический массив, чтобы определить, является ли каждое число простым. Далее он постепенно перебирает их, помечая все кратные как не простые.
Вот реализация алгоритма на Swift.
// primes.swift func primes(n: Int) -> [Int] { var isPrime = [Bool](repeating: true, count: n + 1) for value in stride(from: 2, to: n + 1, by: 1) where isPrime[value] { if value * value > n { break } for multiple in stride(from: value * 2, to: n + 1, by: value) { isPrime[multiple] = false } } var result = [Int]() for value in stride(from: 2, to: n + 1, by: 1) where isPrime[value] { result.append(value) } return result }
Для реализации на C++ нам нужен заголовок и исходный файл. Обратите внимание, что мы ввели typedef, чтобы иметь более чистое имя для обращения к std::vector<long>.
// primes.hpp #include typedef std::vector VectorLong; VectorLong primes(const long &n); // primes.cpp #include #include "primes.hpp" VectorLong primes(const long &n) { std::vector isPrime(n + 1); // faster than std::vector std::fill(isPrime.begin(), isPrime.end(), true); for (long value = 2; value * value <= n; ++value) { if (!isPrime[value]) { continue; } for (long multiple = value * 2; multiple <= n; multiple += value) { isPrime[multiple] = false; } } VectorLong result; for (long value = 2; value <= n; ++value) { if (!isPrime[value]) { continue; } result.push_back(value); } return result; }
Структура проекта

Мы создадим контейнер/package Swift с двумя отдельными targets - для хранения нашего кода Swift и C++. Чтобы импортировать код C++ из Swift, нам нужна карта модуля.
// module.modulemap module CXX { header "CXX.hpp" requires cplusplus } // CXX.hpp #include "primes.hpp"
Не забудьте передать enable-experimental-cxx-interop в Package.swift в swift targets
// swift-tools-version: 5.7 import PackageDescription let package = Package( name: "SwiftCXXInteropExample", platforms: [ .macOS(.v12), ], products: [ .library(name: "CXX", targets: ["CXX"]), .executable(name: "CLI", targets: ["CLI"]) ], dependencies: [], targets: [ .target(name: "CXX"), .executableTarget( name: "CLI", dependencies: ["CXX"], swiftSettings: [.unsafeFlags(["-enable-experimental-cxx-interop"])] ) ] )
Обратитесь к документации от Apple, если вы хотите более подробно узнать о том, как включить совместимость с C++.
Тестируем результаты
Гораздо удобнее и проще использовать наш VectorLong из swift в соответствии с RandomAccessCollection.
import CXX extension VectorLong: RandomAccessCollection { public var startIndex: Int { 0 } public var endIndex: Int { size() } }
Теперь мы можем вызвать нашу С++ функцию из swift и вывести результат в консоль.
let cxxVector = primes(100) let swiftArray = [Int](cxxVector) print(swiftArray)
А теперь давайте проверим будет ли наша реализация на С++ работать быстрее.
let signposter = OSSignposter() let count = 100 let n = 10_000_000 for _ in 0.. let state = signposter.beginInterval("C++") let _ = primes(n) signposter.endInterval("C++", state) } for _ in 0.. let state = signposter.beginInterval("Swift") let _ = primes(n: n) signposter.endInterval("Swift", state) }

Мы видим, что производительность на С++ в среднем чуть быстрее, 26 секунд на C++ против 28 cекунд на swift.
Заключительные мысли
Мы смогли напрямую использовать std::vector в Swift. Лично я не нашел удобного способа переключения между Swift Map и std::map, Set и std::set. Тем не менее, совместимость swift c C++ быстро развивается, и будущее этой совместимости вселяет оптимизм.
Папка CppInteroperability в репозитории Swift содержит дополнительную информацию о функциях взаимодействия, ограничениях и дальнейших планах.
С полным кодом можно ознакомиться по адресу https://github.com/ksemianov/SwiftCXXInteropExample
