- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我有一个单元测试设置来证明同时执行多个繁重的任务比串行执行更快。
现在...在座的每个人都因为上面的说法并不总是正确的事实而失去理智,因为多线程具有许多不确定性,让我解释一下。
我从阅读 apple 文档中了解到,您不能保证在请求时获得多个线程。操作系统 (iOS) 将按其认为合适的方式分配线程。例如,如果设备只有一个内核,它将分配一个内核,并且由于并发操作的初始化代码需要一些额外的时间,因此串行会稍微快一些,同时由于设备只有一个内核而没有提供性能改进。
但是:这种差异应该很小。但在我的 POC 设置中,差异很大。在我的 POC 中,并发速度慢了大约 1/3 的时间。
如果串行在 6 秒 内完成,并发将在 9 秒 内完成。
即使负载更重,这种趋势也会继续。如果串行在 125 秒 内完成,并发将在 215 秒 内竞争。这也不仅会发生一次,而且每次都会发生。
我想知道我是否在创建这个 POC 时犯了错误,如果是,我应该如何证明并发执行多个繁重的任务确实比串行执行更快?
我在快速单元测试中的 POC:
func performHeavyTask(_ completion: (() -> Void)?) {
var counter = 0
while counter < 50000 {
print(counter)
counter = counter.advanced(by: 1)
}
completion?()
}
// MARK: - Serial
func testSerial () {
let start = DispatchTime.now()
let _ = DispatchQueue.global(qos: .userInitiated)
let mainDPG = DispatchGroup()
mainDPG.enter()
DispatchQueue.global(qos: .userInitiated).async {[weak self] in
guard let self = self else { return }
for _ in 0...10 {
self.performHeavyTask(nil)
}
mainDPG.leave()
}
mainDPG.wait()
let end = DispatchTime.now()
let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds // <<<<< Difference in nano seconds (UInt64)
print("NanoTime: \(nanoTime / 1_000_000_000)")
}
// MARK: - Concurrent
func testConcurrent() {
let start = DispatchTime.now()
let _ = DispatchQueue.global(qos: .userInitiated)
let mainDPG = DispatchGroup()
mainDPG.enter()
DispatchQueue.global(qos: .userInitiated).async {
let dispatchGroup = DispatchGroup()
let _ = DispatchQueue.global(qos: .userInitiated)
DispatchQueue.concurrentPerform(iterations: 10) { index in
dispatchGroup.enter()
self.performHeavyTask({
dispatchGroup.leave()
})
}
dispatchGroup.wait()
mainDPG.leave()
}
mainDPG.wait()
let end = DispatchTime.now()
let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds // <<<<< Difference in nano seconds (UInt64)
print("NanoTime: \(nanoTime / 1_000_000_000)")
}
详细信息:
操作系统:macOS High Sierra
机型名称:MacBook Pro
型号标识符:MacBookPro11,4
处理器名称:Intel Core i7
处理器速度:2.2 GHz
处理器数量:1
核心总数:4
这两个测试都是在 iPhone XS Max 模拟器上完成的。这两个测试都是在整个 mac 重启后直接完成的(以避免 mac 忙于运行此单元测试以外的应用程序,结果模糊)
此外,两个单元测试都包含在异步 DispatcherWorkItem 中,因为测试用例用于不阻塞主 (UI) 队列,从而防止串行测试用例在这方面具有优势,因为它消耗主队列而不是与并发测试用例一样的后台队列。
我也会接受显示 POC 可靠地对此进行测试的答案。它不必一直显示并发比串行快(阅读上面关于为什么不这样做的解释)。但至少有一段时间
最佳答案
有两个问题:
我会避免做 print
在循环内。这是同步的,您可能会在并发实现中遇到更大的性能下降。这不是故事的全部,但这并没有帮助。
即使删除了 print
从循环内部来看,计数器的 50,000 个增量根本不足以看到 concurrentPerform
的好处。 .作为Improving on Loop Code说:
... And although this [
concurrentPerform
] can be a good way to improve performance in loop-based code, you must still use this technique discerningly. Although dispatch queues have very low overhead, there are still costs to scheduling each loop iteration on a thread. Therefore, you should make sure your loop code does enough work to warrant the costs. Exactly how much work you need to do is something you have to measure using the performance tools.
在调试构建中,我需要将迭代次数增加到接近 5,000,000 的值才能克服此开销。在发布版本中,即使这样还不够。旋转循环和增加计数器的速度太快,无法对并发行为进行有意义的分析。
因此,在我下面的示例中,我将这个旋转循环替换为计算量更大的计算(使用历史悠久但效率不高的算法计算 π)。
顺便说一句:
与其自己衡量性能,不如在 XCTestCase
内执行此操作单元测试,可以用 measure
基准性能。这会多次重复基准测试、捕获耗时、计算结果的平均值等。只需确保编辑您的方案,以便测试操作使用优化的“发布”构建而不是“调试”构建。
如果您打算使用调度组让调用线程等待它完成,那么将其调度到全局队列是没有意义的。
您不需要使用调度组来等待 concurrentPerform
完成,要么。它同步运行。
作为 concurrentPerform
documentation说:
The dispatch queue executes the submitted block the specified number of times and waits for all iterations to complete before returning.
这不是很重要,但值得注意的是你的 for _ in 0...10 { ... }
正在进行 11 次迭代,而不是 10 次。您显然打算使用 ..<
.
因此,这里有一个示例,将其放入单元测试中,但用计算量更大的东西代替“繁重”的计算:
class MyAppTests: XCTestCase {
// calculate pi using Gregory-Leibniz series
func calculatePi(iterations: Int) -> Double {
var result = 0.0
var sign = 1.0
for i in 0 ..< iterations {
result += sign / Double(i * 2 + 1)
sign *= -1
}
return result * 4
}
func performHeavyTask(iteration: Int) {
let pi = calculatePi(iterations: 100_000_000)
print(iteration, .pi - pi)
}
func testSerial() {
measure {
for i in 0..<10 {
self.performHeavyTask(iteration: i)
}
}
}
func testConcurrent() {
measure {
DispatchQueue.concurrentPerform(iterations: 10) { i in
self.performHeavyTask(iteration: i)
}
}
}
}
在我配备 2.9 GHz Intel Core i9 的 MacBook Pro 2018 上,发布版本的并发测试平均耗时 0.247 秒,而串行测试耗时大约是其四倍,即 1.030 秒。
关于swift - 调度队列 : why does serial complete faster than concurrent?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54635103/
我遇到一种情况,我需要从某个主题读取(正在进行的)消息并将它们放入另一个 Queue 中。我怀疑我是否需要 jms Queue 或者我可以对内存中的 java Queue 感到满意。我将通过同一 jv
队列的定义 队列(Queue):先进先出的线性表 队列是仅在队尾进行插入和队头进行删除操作的线性表 队头(front):线性表的表头端,即可删除端 队尾(rear):线性表的表尾端,即可插入端 由于这
Redis专题-队列 首先,想一想 Redis 适合做消息队列吗? 1、消息队列的消息存取需求是什么?redis中的解决方案是什么? 无非就是下面这几点: 0、数据可以顺序读
0. 学习目标 栈和队列是在程序设计中常见的数据类型,从数据结构的角度来讲,栈和队列也是线性表,是操作受限的线性表,它们的基本操作是线性表操作的子集,但从数据类型的角度来讲,它们与线性表又有着巨大的不
我想在 redis + Flask 和 Python 中实现一个队列。我已经用 RQ 实现了这样的查询,如果你有 Flask 应用程序和任务在同一台服务器上工作,它就可以正常工作。我想知道是否有可能创
我正在使用 Laravel 5.1,我有一个大约需要 2 分钟来处理的任务,这个任务特别是生成报告...... 现在,很明显,我不能让用户在我接受用户输入的同一页面上等待 2 分钟,而是我应该在后台处
我正在使用 Azure 队列,并且有多个不同的进程从队列中读取数据。 我的系统的构建方式假设每条消息只读取一次。 这个Microsoft article声称 Azure 队列具有至少一次传送保证,这可
我正在创建一个Thread::Queue元素数组。 我这样做是这样的: for (my $i=0; $i new; } 但是,当我在每个队列中填充这样的元素时 $queues[$index]->enq
我试图了解如何将我的 Mercurial 补丁推送到远程存储库(例如 bitbucket.org),而不必先应用它们(实际上提交它们)。我的动机是在最终完成之前首先对我的工作进行远程备份,并且能够与其
我的本地计算机上有一个 Mercurial 队列补丁,我需要与同事共享该补丁,但我不想将其提交到上游存储库。有没有一种简单的方法可以打包该补丁并与他分享? 最佳答案 mq 将补丁作为不带扩展名的文
Java 中是否有任何类提供与 Queue 相同的功能,但有返回对象的选项,并且不要删除它,只需将其设置在集合末尾? 最佳答案 Queue不直接提供这样的方法。但是,您可以使用 poll 和 add
我在Windows上使用Tortoise svn客户端,我需要能够一次提交来自不同子文件夹的更改文件-一次提交。像在提交之前将文件添加到队列中之类的?我该怎么做? Windows上是否还有另一个svn
好吧,我正在尝试对我的 DSAQueue 类进行单元测试,它显示我的 isEmpty()、isFull() 和 dequeue() 方法失败。 以下是我的 DSAQueue 代码。我认为我的 Dequ
我想尽量减少对传入请求的数据库查询。它目前需要写入 6 个不同的表。在返回响应之前不需要完成处理。因此,我考虑了 laravel 队列,但我想知道我是否也可以摆脱写入队列/作业表所需的单独查询。我可以
我正在学习队列数据结构。我想用链表创建队列。我想编程输出:10 20程序输出:队列为空-1 队列为空-1 我哪里出错了? 代码如下: class Node { int x; Node next
“当工作人员有空时,他们会根据主题的优先级列表从等待请求池中进行选择。在时间 t 到达的所有请求都可以在时间 t 进行分配。如果两名工作人员同时有空,则安排优先权分配给最近的工作最早安排的人。如果仍然
我正在开发一个巨大的应用程序,它使用一些子菜单、模式窗口、提示等。 现在,我想知道在此类应用程序中处理 Esc 和单击外部事件的正确方法。 $(document).keyup(function(e)
所以 如果我有一个队列 a --> b --> NULL; 当我使用函数时 void duplicate(QueueNodePtr pHead, QueueNodePtr *pTail) 它会给 a
我正在尝试为键盘输入实现 FIFO 队列,但似乎无法让它工作。我可以让键盘输入显示在液晶显示屏上,但这就是我能做的。我认为代码应该读取键盘输入并将其插入队列,然后弹出键盘输入并将值读取到液晶屏幕上。有
我正在学习算法和 DS。如何在 JavaScript 中使用队列? 我知道你可以做这样的事情。 var stack = []; stack.push(2); // stack is now
我是一名优秀的程序员,十分优秀!