45
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swiftでパフォーマンスが向上したことについての私感

Last updated at Posted at 2014-12-02

今年の6月のWWDCでSwiftが発表された時のアピールポイントの1つとしてパフォーマンス向上がありました。

とはいえ、Objective-Cはメッセージパッシング部分などで多少ボトルネックなどがあるとはいえそこそこ速いし、一般的なGCではなく参照カウンタ形式のメモリ管理なので定期的にもっさりするような挙動もほぼ起こらないので、元々そこをネックに感じていた人は少なかったのではないかと思います。

でも速いに越したことはないですし、実際どのくらい差があるのか気になりますよね。

Swiftの処理速度

いくつか見た中で、良い感じにまとまっているのはこれですかね(8月の記事なので現バージョンでは少し差があるかもですが)。

以下、要点 + 意見です。

  • Swiftは、Optimization LevelNone [-Onone]にするとObjective-Cよりかなり遅い
    • リリースビルドではFastest [-O]になっているので基本的にこれは問題にならない
    • ただ、Fastest [-O]の場合クラッシュするなどの理由で安易にNone [-Onone]にしている記事が散見されるのが気になる
      • Swiftのバグの場合と、書き方が良く無いけどデバッグビルドでは運良く落ちない、の2パターンがあるように見えるけど、ちゃんと書き方を正すなりその部分だけObjective-Cで書いてしのぐなどして、コンパイルオプションは安易に弄らないようにするべき
    • 開発時にすごく処理時間がかかっていて色々苦労した結果、実はデバッグビルドで遅かったせい、などありえるのでちゃんと頭には入れておいた方が良さそう
  • リリースビルドでは、SwiftはObjective-Cより10倍程度速い(ソート系のアルゴリズムにおいて)

あと、この記事で触れているmikeash.com: Friday Q&A 2014-07-04: Secrets of Swift's Speedで、もう少し深い議論がなされています。

この記事はソートアルゴリズムについて10倍程度速くなっているとのことでしたが、実際に僕もすごく単純な処理について比較してみました。

自分ですごく単純な処理においてパフォーマンス比較してみた

以下のような配列にaddを100,000回していくだけのすごく単純な処理の場合、SwiftはObjective-Cより2倍弱速い程度でした。

func testCountMany() {
    self.measureBlock() {
        let toNum = 100000
        var list = [Int]()
        for i in 0..<toNum {
            list.append(i)
        }
    }
}
- (void)testPerformanceExample {
    [self measureBlock:^{
        NSUInteger toNum = 100000;
        NSMutableArray* list = [NSMutableArray new];
        for (NSUInteger i = 0; i < toNum; i++) {
            [list addObject:@(i)];
        }
    }];
}
言語 処理時間
Swift 0.006 sec
Objective-C 0.010 sec

ただ、実際こういう大量の処理を回すのってあまりないですよね。

通常アプリ作るにあたって、パフォーマンス的に効いてくるのは、画面描画・通信・DB周りが支配的で、細々としたロジック部分は無視できる範囲なことがほとんどですし。
(なので、ロジック部分は基本的にはささいなパフォーマンスなどは無視して可読性・メンテナンス性重視が良いと思っています。)

というわけで、Swiftが速くなったというのは、実質そこまで恩恵大きくないだろうということで、軽快なアプリを作るには、これまで通り「画面描画・通信・DB周り」を開発者ががんばって地道に速くしていくしかないと思っています。

と、ここで終わると残念な感じなので、上に挙げたうち、DB周りの高速化としてiOS8・OSX 10.10からバッチ更新が可能となったとのことで、そのAPIを試してみました。

<<<<<<< HEAD

(どさくさに紛れて自作スタンプを宣伝)

tehepero.png

ラヴさん (ラブラドール) - LINE クリエイターズスタンプ

===

=======

LINEスタンプ作ってるので良かったらダウンロードお願いしますヽ(・ω・`)
ラヴさん (ラブラドール) - LINE クリエイターズスタンプ
tehepero.png

===

YOUR_EDITION

バッチ更新でCore Dataの更新を大幅に高速化

iOS8・OSX 10.10からNSBatchUpdateRequestを使えば、Core Dataの大量更新をバッチ処理で行えるようになりました。
(Core Data使わずにSQLiteを生で使っていれば今までも出来たと思いますが)

ちょっと長いので、サンプルコードは末尾に載せますが、以下のように100,000件のデータのプロパティを一気に書き換える処理で、既存の処理に比べてNSBatchUpdateRequestを使った処理では大きな差が出ました。
※速すぎるのでNSBatchUpdateRequest版どこかミスってるかも

言語 既存の処理 NSBatchUpdateRequest版
Swift 1.863 sec 0.002 sec
Objective-C 1.664 sec 0.000 sec

既存の処理の場合、Core Dataの操作がボトルネックになって、言語間の速度差にほとんど反映されないのは予想どおりでしたが、なぜかSwiftの方が微妙に遅いです。
ループ回す処理などの分Swiftの方が微妙に速いことを予想していましたが(´・ω・`)

NSBatchUpdateRequestの注意点

ただ、もちろんトレードオフがあって、リンク先にもあるように下記の注意が必要です。

アプリが操作可能な状態で行うのはけっこう難しそうな感じです。
使えそうなところとしては、アプリアップデート後の初回起動後に、画面ブロックして一気にデータ更新とかですかね。(マッピングファイル使ったCore Dataのマイグレーションは遅いですし)

WWDCのデモでは、メールアプリで大量のメッセージを一気に既読化する処理に適用していました。
確かにそういった場合に、UIがしばらく固まるというのはあるあるですし、それが劇的に改善するのは良いですね。

GitHub検索しても、MagicalRecordなど含めてNSBatchUpdateRequestを使ったライブラリが見当たらないので、是非どなかたプルリクエストなどしてほしいと思ったり。
(あるいは使いたくなったら、自分でやろうかなと思っています。)

何かタイトルとかなり内容が逸れてしまった気がしますが、言語間のパフォーマンスのちょっとした差より、こういった強力なAPIだったり賢いアルゴリズムだったりというのがずっと大きい、という一例として捉えていただけたらと思います。

Core Dataの処理のパフォーマンス検証コード

プロジェクトはこちら: mono0926/IOS-PerformanceCheck

import UIKit
import XCTest

class CoreDataTests: XCTestCase {
    
    override func setUp() {
        MagicalRecord.setupCoreDataStack()
        let memberCount = 100000
        let moc = NSManagedObjectContext.MR_contextForCurrentThread()
        
        for i in 0..<memberCount {
            Member.MR_createEntity()
            moc.MR_saveToPersistentStoreAndWait()
        }
        super.setUp()
    }
    
    override func tearDown() {
        Member.MR_deleteAllMatchingPredicate(nil)
        let moc = NSManagedObjectContext.MR_contextForCurrentThread()
        moc.MR_saveToPersistentStoreAndWait()
        super.tearDown()
    }
    
    func testCoreData() {
        self.measureBlock() {
            let moc = NSManagedObjectContext.MR_contextForCurrentThread()
            let members = Member.MR_findAll() as [Member]
            for m in members {
                m.loveIce = true
            }
            moc.MR_saveToPersistentStoreAndWait()
        }
    }
    
    func testCoreDataBatch() {
        self.measureBlock() {
            let moc = NSManagedObjectContext.MR_contextForCurrentThread()
            let entityDescription = NSEntityDescription.entityForName("Member", inManagedObjectContext: moc)!
            let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription)
            batchUpdateRequest.resultType = NSBatchUpdateRequestResultType.UpdatedObjectIDsResultType
            batchUpdateRequest.propertiesToUpdate = ["loveIce": true]
            // Execute Batch Request
            var batchUpdateRequestError: NSError?
            moc.executeRequest(batchUpdateRequest, error:&batchUpdateRequestError)
            if let e = batchUpdateRequestError {
                XCTFail(e.description)
            }
        }
    }
}
@interface CoreDataTests2 : XCTestCase

@end

@implementation CoreDataTests2

- (void)setUp {
    [super setUp];
    [MagicalRecord setupCoreDataStack];
    NSUInteger memberCount = 100000;
    NSManagedObjectContext* moc = [NSManagedObjectContext MR_contextForCurrentThread];
    
    for (NSUInteger i = 0; i <memberCount; i++) {
        [Member MR_createEntity];
        [moc MR_saveToPersistentStoreAndWait];
    }
}

- (void)tearDown {
    [Member MR_deleteAllMatchingPredicate:nil];
    NSManagedObjectContext* moc = [NSManagedObjectContext MR_contextForCurrentThread];
    [moc MR_saveToPersistentStoreAndWait];
    [super tearDown];
}

- (void)testCoreData {
    [self measureBlock:^{
        NSManagedObjectContext* moc = [NSManagedObjectContext MR_contextForCurrentThread];
        NSArray* members = [Member MR_findAll];
        for (Member* m in members) {
            m.loveIce = YES;
        }
        [moc MR_saveToPersistentStoreAndWait];
    }];
}

- (void)testCoreDataBatch {
    [self measureBlock:^{
        NSManagedObjectContext* moc = [NSManagedObjectContext MR_contextForCurrentThread];
        NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Member" inManagedObjectContext:moc];
        
        NSBatchUpdateRequest *batchUpdateRequest = [[NSBatchUpdateRequest alloc] initWithEntity:entityDescription];
        
        [batchUpdateRequest setResultType:NSUpdatedObjectIDsResultType];
        [batchUpdateRequest setPropertiesToUpdate:@{ @"loveIce" : @YES }];
        
        // Execute Batch Request
        NSError *batchUpdateRequestError = nil;
        [moc executeRequest:batchUpdateRequest error:&batchUpdateRequestError];
        if (batchUpdateRequestError) {
            XCTFail(@"%@", batchUpdateRequestError);
        }
    }];
}

@end

実行速度の検証は以下の条件で行いました。

  • iPhone 6 Plus実機
    • iOS 8.2β
  • Xcode 6.2β
  • リリースビルド
    • 最適化オプションはリリースビルド時のデフォルト
45
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?