Swiftの標準ライブラリ中で定義されてるシンボルを(otoolで)眺めていたところ,'atomic'という文字列を含むものがいくつかありました.これは使わない手はない!ということで,使い方をメモしておきます.
アトミック関連のクラス,関数
-
公開クラス _stdlib_AtomicInt
- 公開クラスでXcodeのエディタで補完が効くので使い方は簡単に分かると思います.
- メソッドはload(), store(), addAndFetch(), fetchAndAdd()と,Swift 2.0からcompareExchange()が使える.
-
公開関数 _stdlib_atomicCompareExchangeStrongPtr()
- アトミックにポインタを交換する.
- 引数はUnsafeMutablePointer<UnsafeMutablePointer<T>>, UnsafeMutablePointer<UnsafeMutablePointer<T>>, T
-
非公開関数(通常は使えない)たち
- swift_stdlib_atomicStoreUInt32
- swift_stdlib_atomicStoreUInt64
- swift_stdlib_atomicFetchAddUInt32
- swift_stdlib_atomicFetchAddUInt64
- swift_stdlib_atomicCompareExchangeStrongUInt32
- swift_stdlib_atomicCompareExchangeStrongUInt64
クラス_stdlib_AtomicIntを使う簡単な例: Lock
public struct Lock {
private let value = _stdlib_AtomicInt(0)
public mutating func trylock() -> Bool {
var zero = 0
return value.compareExchange(expected: &zero, desired: 1)
//let result = value.addAndFetch(1) // Swift 1.2
//return result == 1 ? true : false
}
public mutating func unlock() {
value.store(0)
}
}
という感じで使い方は簡単です.
しかし,使う場面によってはパフォーマンス的に少々問題があるかもしれません.
_stdlib_AtomicIntはクラスであり参照型なので常にARCの管理下にあります.そのため,この変数を参照すると必ずretain,releaseに挟まれます.retain,releaseは参照カウントをアトミックに増減するので,全体では3度もアトミック操作をすることになってしまい結構重いかもしれません.
非公開関数を使ってしまおう.
非公開でもシンボル名と処理内容さえ分かれば禁断の技?が使えます.
ここで知ったのですが,Swiftには@asmname("NAME")
という隠し属性があって,リンク時の関数シンボル名を自由に設定できます.これを逆手にとって,@asmname("見つけた非公開シンボル名")
というふうに使って白実のもとに晒してしまいます.
具体的には,
@asmname("swift_stdlib_atomicCompareExchangeStrongUInt64")
func atomicCAS64(#target: UnsafeMutablePointer<UInt64>, #expected: UnsafeMutablePointer<UInt64>, #desired: UInt64) -> Bool
こんなふうに.SwiftではatomicCAS64という関数名だけどリンク時にはアレにしてね,という感じです.
引数に関しては,逆アセンブルした非公開関数のレジスタの使い方をみて決めないといけませんが,そんなに難しくはないです.さて,これを仕込んでおけば:
var x = Uint64(0)
var y = UInt64(0)
let sucess = atomicCAS64(&x, expected: &y, desired: 1)
このように,非公開関数を呼び出すことができるようになります.
速いかもしれないLock
クラスを使うとretain,releaseが挟まって遅くなると書きました.なのでストラクトと白実の下にさらした非公開関数を使って高速版Lockを実装してみます.(本気で使う場合はTest and Test and Setにしたほうがよい)
@asmname("swift_stdlib_atomicCompareExchangeStrongUInt64")
func atomicCAS64(target target: UnsafeMutablePointer<UInt64>,
expected: UnsafeMutablePointer<UInt64>, desired: UInt64) -> Bool
@asmname("swift_stdlib_atomicStoreUInt64")
func atomicStore64(#target: UnsafeMutablePointer<UInt64>, #value: UInt64) -> ()
public struct FastLock {
private let ptr: UnsafeMutablePointer<UInt64>
public init() {
ptr = UnsafeMutablePointer<UInt64>.alloc(1)
ptr.initialize(0)
}
public func dispose() {
ptr.dealloc(1)
}
public mutating func trylock() -> Bool {
var zero = UInt64(0)
return atomicCAS64(target: ptr, expected: &zero, desired: 1)
}
public mutating func unlock() {
atomicStore64(target: ptr, value: 0)
}
}
FastLockは値型ですがロック対象である整数はポインタでアクセスする(ヒープ上に割り付けてある)ので,この値型コピーしてもロック対象は必ず共有されています.
ARCの管理下にないのでFastLockを使い終わったらdispose()するのを忘れないように.