📘
ループ内でなるべくActorを切り替えないようにする
はじめに
ループ内でActorを切り替えるときは、まとめてループを一つのActorで実行させたほうがいい。なぜならループ内でスレッドを切り替えることになり、その都度コンテキストスイッチが起こり、CPU利用率が上がってしまう。
内容
WWDC21: Swift concurrency: Behind the scenesで紹介されていた。
コードを見るとだいたいこんな感じで2つのアクターでスレッドが切り替わっている
-
updateArticles
メソッドはidsの個数分ループ-
database.loadArticle
メソッドがdatabaseアクターで動作 -
updateUI
メソッド実行はメインスレッドに切り替わる-
updateArticles
メソッドがMainActor指定されているため
-
-
しかもidsの個数制限がない。つまりこれが2億回くらい回ることもある。
これを改善するためにループで処理せずに配列をそのまままとめて処理するようにする。
↓
これで2つのアクターは切り替えが最小限になる。
他にもWWDC21の動画で説明されていたことは
- アクターはスレッドプールのスレッドを利用する
- スレッドを生成しすぎというわけではない
- 具体的には
- idsが100あっても100個スレッドを使うわけじゃない
- 具体的には
- スレッドを生成しすぎというわけではない
その他参考
What is actor hopping and how can it cause problems? hackingwithswift
hackingwithswiftの記事。上記で説明したupdateUI
的なことをやるとき、@Published
なデータを都度appendしている例を示してる。そうなるとループごとにSwiftUI.View画面をリフレッシュするようなこともある。とすると単純にコンテキストスイッチの頻繁な切り替えよりもViewを更新してしまうことでそれによるリソースの使いすぎにもつながるのかもしれない。
@MainActor
class DataModel: ObservableObject {
@Published var users = [User]()
var database = Database()
// Load all our users, updating the UI as each one
// is successfully fetched
func loadUsers() async {
for i in 1...100 {
let user = await database.loadUser(id: i)
users.append(user)
}
}
}
感想
ベストを尽くすなら改善すりゃいいとは思う。けれどやるかどうかはもループ数によるとは思う。つまり例えば固定値で5回しか処理が繰り返さないと制限がコードで表現されているなら、計算量はたかが知れてるなーとは思う。その上で改善するかどうかは計測してみて判断する。
Discussion