ios - 为什么GCD,ObjC和Swift之间的性能差距如此之大

标签 ios objective-c swift performance grand-central-dispatch

OC:模拟器iPhoneSE iOS 13;
(60-80秒)

    NSTimeInterval t1 = NSDate.date.timeIntervalSince1970;
    NSInteger count = 100000;

    for (NSInteger i = 0; i < count; i++) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            NSLog(@"op begin: %ld", i);
            self.idx = i;
            NSLog(@"op end: %ld", i);
            if (i == count - 1) {
                NSLog(@"耗时: %f", NSDate.date.timeIntervalSince1970-t1);
            }
        });
     }

Swift:模拟器iPhoneSE iOS 13;
(10-14秒)
let t1 = Date().timeIntervalSince1970
let count = 100000
for i in 0..<count {
            DispatchQueue.global().async {
                print("subOp begin i:\(i), thred: \(Thread.current)")
                self.idx = i
                print("subOp end i:\(i), thred: \(Thread.current)")
                if i == count-1 {
                    print("耗时: \(Date().timeIntervalSince1970-t1)")
                }
            }
}

我以前的工作是使用oc编写代码。最近学会了使用swift。我对通用GCD的广泛性能差距感到惊讶

最佳答案

tl; dr

您很可能正在调试构建。在Swift中,调试版本会进行各种安全检查。如果执行发行版构建,则会关闭这些安全检查,并且性能在很大程度上与Objective-C渲染图没有区别。

一些观察:

  • 关于它们与index的交互,它们都不是线程安全的,即您与此属性的交互未同步。如果要从多个线程更新属性,则必须同步访问权限(具有锁,串行队列,读写器并发队列等)。
  • 在您的Swift代码中,您是否正在执行发布版本?在调试版本中,调试版本中没有进行Objective-C演绎的安全检查。对两者进行“发布”构建,以比较苹果与苹果。
  • 您的Objective-C复制使用DISPATCH_QUEUE_PRIORITY_HIGH,但是您的Swift迭代使用.default的QoS。我建议使用.userInteractive.userInitiated的QoS来进行比较。
  • 全局队列是并发队列。因此,仅检查i == count - 1不足以知道当前所有任务是否都已完成。通常,我们会使用调度组或concurrentPerform / dispatch_apply知道它们何时完成。
  • 建议不要使用
  • FWIW,将100,000个任务分配到全局队列中,因为您将快速耗尽工作线程。请勿进行此类线程爆炸。如果这样做,您的应用程序中可能会有意外的锁定。在Swift中,我们将使用concurrentPerform。在Objective-C中,我们将使用dispatch_apply
  • 您正在Objective-C中执行NSLog,在Swift中进行print。那些不是同一回事。如果您想比较效果,建议同时使用NSLog
  • 在进行性能测试时,我建议您使用单元测试的measure例程,该例程会重复多次。

  • 无论如何,在对所有这些进行校正时,Swift代码的时间在很大程度上与Objective-C的性能没有区别。

    这是我使用的例程。在Swift中:
    class SwiftExperiment {
    
        // This is not advisable, because it suffers from thread explosion which will exhaust
        // the very limited number of worker threads.
    
        func experiment1(completion: @escaping (TimeInterval) -> Void) {
            let t1 = Date()
            let count = 100_000
            let group = DispatchGroup()
    
            for i in 0..<count {
                DispatchQueue.global(qos: .userInteractive).async(group: group) {
                    NSLog("op end: %ld", i);
                }
            }
    
            group.notify(queue: .main) {
                let elapsed = Date().timeIntervalSince(t1)
                completion(elapsed)
            }
        }
    
        // This is safer (though it's a poor use of `concurrentPerform` as there's not enough
        // work being done on each thread).
    
        func experiment2(completion: @escaping (TimeInterval) -> Void) {
            let t1 = Date()
            let count = 100_000
    
            DispatchQueue.global(qos: .userInteractive).async() {
                DispatchQueue.concurrentPerform(iterations: count) { i in
                    NSLog("op end: %ld", i);
                }
                let elapsed = Date().timeIntervalSince(t1)
                completion(elapsed)
            }
        }
    }
    

    以及等效的Objective-C例程:
    // This is not advisable, because it suffers from thread explosion which will exhaust
    // the very limited number of worker threads.
    
    - (void)experiment1:(void (^ _Nonnull)(NSTimeInterval))block {
        NSDate *t1 = [NSDate date];
        NSInteger count = 100000;
        dispatch_group_t group = dispatch_group_create();
    
        for (NSInteger i = 0; i < count; i++) {
            dispatch_group_async(group, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
                NSLog(@"op end: %ld", i);
            });
        }
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSTimeInterval elapsed = [NSDate.date timeIntervalSinceDate:t1];
            NSLog(@"耗时: %f", elapsed);
            block(elapsed);
        });
    }
    
    // This is safer (though it's a poor use of `dispatch_apply` as there's not enough
    // work being done on each thread).
    
    - (void)experiment2:(void (^ _Nonnull)(NSTimeInterval))block {
        NSDate *t1 = [NSDate date];
        NSInteger count = 100000;
    
        dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
            dispatch_apply(count, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^(size_t index) {
                NSLog(@"op end: %ld", index);
            });
    
            NSTimeInterval elapsed = [NSDate.date timeIntervalSinceDate:t1];
            NSLog(@"耗时: %f", elapsed);
            block(elapsed);
        });
    }
    

    我的单元测试:
    class MyApp4Tests: XCTestCase {
    
        func testSwiftExperiment1() throws {
            let experiment = SwiftExperiment()
    
            measure {
                let e = expectation(description: "experiment1")
    
                experiment.experiment1 { elapsed in
                    e.fulfill()
                }
    
                wait(for: [e], timeout: 1000)
            }
        }
    
        func testSwiftExperiment2() throws {
            let experiment = SwiftExperiment()
    
            measure {
                let e = expectation(description: "experiment2")
    
                experiment.experiment2 { elapsed in
                    e.fulfill()
                }
    
                wait(for: [e], timeout: 1000)
            }
        }
    
        func testObjcExperiment1() throws {
            let experiment = ObjectiveCExperiment()
    
            measure {
                let e = expectation(description: "experiment1")
    
                experiment.experiment1 { elapsed in
                    e.fulfill()
                }
    
                wait(for: [e], timeout: 1000)
            }
        }
    
        func testObjcExperiment2() throws {
            let experiment = ObjectiveCExperiment()
    
            measure {
                let e = expectation(description: "experiment2")
    
                experiment.experiment2 { elapsed in
                    e.fulfill()
                }
    
                wait(for: [e], timeout: 1000)
            }
        }
    }
    

    导致

    enter image description here

    关于ios - 为什么GCD,ObjC和Swift之间的性能差距如此之大,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61981363/

    相关文章:

    ios - 将整数变量分配给图像

    ios - 如何在导航标题下添加搜索栏

    ios - 线程 1 : signal SIGABRT when getting Reusable cell for TableView

    ios - CollectionView 未渲染

    objective-c - tabBarController 中的一个选项卡有两个 ViewController

    Java REST 服务二进制文件下载

    ios - Swift Compact Map 返回空

    swift - 如何在 Swift 中将 UILabel 与多行文本居中?

    ios - AWS API网关 {"error": "missing authentication token"}

    android - TextField 输入始终以大写开头