Игра с экспериментальным взаимодействием Swift-C++
Swift очень удобный язык. У него есть некоторые причуды и кривая обучения, но в конечном итоге вы можете довольно быстро выпускать готовый к работе код.
Однако иногда у вас есть разделы, критически важные для производительности, и Swift просто не справляется с ними. В таких случаях популярным выбором является использование C++.
И возникает вопрос «как мне назвать этот C++ func
из Swift»?
Обычно вам нужно написать оболочку Objective-C, которая будет действовать как общедоступный интерфейс для вашего кода на C++. Инструментальная цепочка Swift может импортировать объявления 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 <vector> typedef std::vector<long> VectorLong; VectorLong primes(const long &n); // primes.cpp #include <algorithm> #include "primes.hpp" VectorLong primes(const long &n) { std::vector<char> isPrime(n + 1); // faster than std::vector<bool> 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 с двумя отдельными целями для хранения кода Swift и C++. Чтобы импортировать код C++ из Swift, нам нужна карта модулей.
// module.modulemap module CXX { header "CXX.hpp" requires cplusplus } // CXX.hpp #include "primes.hpp"
И не забудьте передать -enable-experimental-cxx-interop
цели Swift в Package.swift
.
// 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() } }
Теперь мы можем вызвать нашу функцию C++ из Swift и вывести результаты на консоль.
let cxxVector = primes(100) let swiftArray = [Int](cxxVector) print(swiftArray)
Давайте посмотрим, действительно ли наша реализация на C++ работает быстрее.
let signposter = OSSignposter() let count = 100 let n = 10_000_000 for _ in 0..<count { let state = signposter.beginInterval("C++") let _ = primes(n) signposter.endInterval("C++", state) } for _ in 0..<count { let state = signposter.beginInterval("Swift") let _ = primes(n: n) signposter.endInterval("Swift", state) }
Чуть быстрее со средней продолжительностью 26 мс против 28 мс для Swift.
Последние мысли
Мы смогли напрямую использовать std::vector
в Swift. Лично я не нашел удобного способа туда и обратно между Swift Map
и std::map
, Set
и std::set
. Тем не менее, взаимодействие C++ быстро развивается, и будущее кажется светлым.
Папка CppInteroperability
в репозитории Swift содержит дополнительную информацию о функциях взаимодействия, ограничениях и планах.
Полный код смотрите здесь.
Первоначально опубликовано на https://ksemianov.github.io/articles/cpp-interop/