Jouer avec l’interopérabilité Swift-C++ expérimentale
Swift est un langage très confortable. Il a quelques bizarreries et une courbe d’apprentissage, mais en fin de compte, vous pouvez expédier assez rapidement du code prêt pour la production.
Cependant, vous avez parfois des sections critiques pour les performances et Swift ne le coupe tout simplement pas. Dans de tels cas, un choix populaire consiste à utiliser C++.
Et la question se pose « comment puis-je appeler ce C++ func
de Swift » ?
Normalement, il faut écrire un wrapper Objective-C qui servira d’interface publique pour votre code C++. Et la chaîne d’outils Swift peut importer des déclarations Objective-C dans Swift. La principale limitation est que vous ne pouvez pas utiliser de classes C++ en Objective-C, uniquement de simples structures POD.
Nous écrirons un Tamis d’Eratosthène algorithme avec C++ et Swift. Découvrez ensuite comment activer l’interopérabilité C++, appeler du code C++ depuis Swift et comparer les performances d’implémentation.
Gardez à l’esprit que la fonctionnalité est expérimentale et sujette à modifications. Cette publication compile sur Xcode Version 14.2
Le crible d’Ératosthène trouve tous les nombres premiers inférieurs ou égaux à N. Un nombre premier est un entier divisible uniquement par lui-même et 1. L’algorithme crée un tableau booléen pour indiquer si chaque nombre est premier. Et itère progressivement sur eux, marquant tous les multiples comme non premiers.
Voici l’implémentation de Swift.
// primes.swiftfunc 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
}
Pour C++, nous avons besoin d’un en-tête et d’un fichier source. A noter, que nous typedef
avoir un nom plus propre pour se référer à 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;
}
Nous allons créer un package Swift avec deux cibles distinctes pour contenir notre code Swift et C++. Pour importer du code C++ depuis Swift, nous avons besoin d’un modulemap.
// module.modulemapmodule CXX {
header "CXX.hpp"
requires cplusplus
}
// CXX.hpp
#include "primes.hpp"
Et n’oubliez pas de passer -enable-experimental-cxx-interop
à la cible Swift dans Package.swift
.
// swift-tools-version: 5.7import 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"])]
)
]
)
Voir la doc d’Apple pour plus d’infos sur comment activer l’interopérabilité C++.
Il est beaucoup plus facile d’utiliser notre VectorLong
de Swift conformément à RandomAccessCollection
et, heureusement, c’est vraiment facile à faire.
import CXXextension VectorLong: RandomAccessCollection {
public var startIndex: Int { 0 }
public var endIndex: Int { size() }
}
Nous pouvons maintenant appeler notre fonction C++ depuis Swift et imprimer les résultats sur la console.
let cxxVector = primes(100)
let swiftArray = [Int](cxxVector)
print(swiftArray)
Voyons si notre implémentation C++ fonctionne réellement plus rapidement.
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)
}
Légèrement plus rapide avec une durée moyenne de 26 ms contre 28 ms pour Swift.
Nous avons pu utiliser directement std::vector
à Swift. Personnellement, je n’ai pas trouvé de moyen pratique d’aller-retour entre Swift Map
et std::map
, Set
et std::set
. Néanmoins, l’interopérabilité C++ se développe rapidement et l’avenir semble prometteur.
CppInteroperability
Le dossier dans le dépôt Swift contient plus d’informations sur les fonctionnalités, les limitations et les plans d’interopérabilité.
Voir le code complet ici.