为什么 GCD、ObjC 和 Swift 之间的性能差距如此之大
Why is the performance gap between GCD, ObjC and Swift so large
OC:模拟器iPhoneSE iOS13;
(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 iOS13;
(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;博士
您很可能正在进行调试构建。在 Swift 中,调试版本会进行各种安全检查。如果你做一个发布版本,它会关闭那些安全检查,并且性能在很大程度上与 Objective-C 再现没有区别。
几点观察:
它们与 index
的交互都不是线程安全的,即您与此 属性 的交互未同步。如果您要从多个线程更新 属性,则必须同步您的访问(使用锁、串行队列、reader-writer 并发队列等)。
在您的 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)
}
}
}
结果是
OC:模拟器iPhoneSE iOS13; (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 iOS13; (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;博士
您很可能正在进行调试构建。在 Swift 中,调试版本会进行各种安全检查。如果你做一个发布版本,它会关闭那些安全检查,并且性能在很大程度上与 Objective-C 再现没有区别。
几点观察:
它们与
index
的交互都不是线程安全的,即您与此 属性 的交互未同步。如果您要从多个线程更新 属性,则必须同步您的访问(使用锁、串行队列、reader-writer 并发队列等)。在您的 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)
}
}
}
结果是