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