Знакомимся с совместимостью Swift и C++

Знакомимся с совместимостью Swift и C++

Развлекаемся с экспериментальными функциями 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; }

Структура проекта

Знакомимся с совместимостью Swift и C++

Мы создадим контейнер/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) }
Знакомимся с совместимостью Swift и C++

Мы видим, что производительность на С++ в среднем чуть быстрее, 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

Подписывайся на наши соцсети: Telegram / VKontakte

Вступай в открытый чат для iOS-разработчиков: t.me/swiftbook_chat

Начать дискуссию