当前位置: 首页 > 知识库问答 >
问题:

快速性能:map()和reduce()vs for循环

皮景龙
2023-03-14

我正在用Swift编写一些性能关键的代码。在实现了我能想到的所有优化,并在仪器中分析了应用程序之后,我意识到绝大多数CPU周期都花在了对浮点数组执行map()reduce()操作上。因此,为了看看会发生什么,我用良好的老式for循环替换了mapreduce的所有实例。令我吃惊的是...for循环要快得多!

// Populate array with 1,000,000,000 random numbers
var array = [Float](count: 1_000_000_000, repeatedValue: 0)
for i in 0..<array.count {
    array[i] = Float(random())
}
let start = NSDate()
// Construct a new array, with each element from the original multiplied by 5
let output = array.map({ (element) -> Float in
    return element * 5
})
// Log the elapsed time
let elapsed = NSDate().timeIntervalSinceDate(start)
print(elapsed)
var output = [Float]()
for element in array {
    output.append(element * 5)
}

map的平均执行时间:20.1秒。for循环的平均执行时间:11.2秒。使用整数而不是浮点的结果是相似的。

我创建了一个类似的基准测试Swift的reduce的性能。这一次,reducefor循环在对一个大数组的元素求和时获得了几乎相同的性能。但是当我像这样循环测试100,000次时:

// Populate array with 1,000,000 random numbers
var array = [Float](count: 1_000_000, repeatedValue: 0)
for i in 0..<array.count {
    array[i] = Float(random())
}
let start = NSDate()
// Perform operation 100,000 times
for _ in 0..<100_000 {
    let sum = array.reduce(0, combine: {$0 + $1})
}
// Log the elapsed time
let elapsed = NSDate().timeIntervalSinceDate(start)
print(elapsed)

VS:

for _ in 0..<100_000 {
    var sum: Float = 0
    for element in array {
        sum += element
    }
}

我对斯威夫特在这里的表现有点困惑。难道内置数组方法不应该比执行此类操作的幼稚方法更快吗?也许有比我更低层次的知识的人能对情况有所了解。

共有1个答案

罗允晨
2023-03-14

难道内置数组方法不应该比执行此类操作的幼稚方法更快吗?也许有比我更低层次的知识的人能对情况有所了解。

我只想尝试用“不一定”来解决这部分问题,以及更多从概念层面(我对Swift的优化器的性质了解甚少)。这更多的来自于编译器设计和计算机体系结构的背景,而不是对Swift优化器本质的根深蒂固的了解。

呼叫开销

这种分支/调用开销对于优化器来说是很难消除的,特别是考虑到Swift闭包的灵活性(并非不可能,但在概念上相当困难)。C++优化器可以内联函数对象调用,但需要更多的限制和代码生成技术,编译器实际上必须为您传入的每种类型的函数对象生成一组新的map指令(并在程序员指示用于代码生成的函数模板的明确帮助下)。

因此,发现您的手摇循环执行得更快并不奇怪--它们大大减轻了优化器的压力。我看到一些人提到,这些高阶函数应该能够更快地运行,因为供应商能够做类似于循环并行化的事情,但是要有效地并行化循环,首先需要那种信息,这种信息通常允许优化器将嵌套的函数调用内联到一个点,使它们变得和手工滚动的循环一样便宜。否则,您传入的函数/闭包实现对map/reduce这样的函数将是不透明的:它们只能调用它并支付这样做的开销,并且不能将它并行化,因为它们不能假定这样做的副作用和线程安全性的性质。

当然,这都是概念性的--Swift可能在将来能够优化这些情况,或者现在已经能够这样做了(请参阅-ofast,这是一种经常引用的方法,它以一些安全为代价使Swift走得更快)。但是,至少在手工循环上使用这些函数会给优化器带来更大的压力,您在第一个基准测试中看到的时间差似乎反映了您可能期望的这种额外的调用开销带来的差异。最好的方法是查看程序集并尝试各种优化标志。

标准功能

这并不是要阻止使用这样的功能。他们做更简洁的表达意图,他们可以提高生产力。并且依赖它们可以让您的代码库在未来版本的Swift中变得更快,而不需要您的任何参与。但它们并不一定总是会更快--认为更直接地表达您想要做的事情的更高级库函数会更快是一个很好的一般规则,但该规则总是有例外(但最好是在后知后觉的时候用一个探查器发现,因为在这里,在信任方面出错要比不信任好得多)。

人工基准

至于您的第二个基准测试,它几乎可以肯定是编译器优化代码的结果,没有影响用户输出的副作用。由于优化人员为了消除不相关的副作用(本质上不影响用户输出的副作用)所做的工作,人工基准有一种众所周知的误导性的倾向。因此,在构建基准测试时,您必须小心,因为这些测试的时间似乎太好了,而不是因为优化器跳过了您真正想要进行基准测试的所有工作。至少,您希望您的测试输出从计算中收集到的一些最终结果。

 类似资料:
  • 问题内容: 我正在Swift中编写一些性能关键的代码。在实现了我能想到的所有优化并在Instruments中对应用程序进行了性能分析之后,我意识到,绝大多数CPU周期都花在了Floats数组上的执行和操作上。所以,只是为了看看会发生什么,我更换的所有实例,并具有良好的老式循环。令我惊讶的是,循环快得多了! 对此感到有些困惑,我决定执行一些粗略的基准测试。在一个测试中,我执行了一些简单的算术,然后返

  • 如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Processing on Large Clusters”,你就能大概明白map/reduce的概念。 map 举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个数组[1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map实现如下: 由于map()方法定义在JavaSc

  • Python内建了map()和reduce()函数。 如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Processing on Large Clusters”,你就能大概明白map/reduce的概念。 我们先看map。map()函数接收两个参数,一个是函数,一个是序列,map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。

  • Python内建了map()和reduce()函数。 如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Processing on Large Clusters”,你就能大概明白map/reduce的概念。 我们先看map。map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的It

  • 我是Hadoop新手。我试图根据Apache hadoop站点上给出的示例创建一个hadoop集群。 然而,当我运行map reduce示例时,应用程序卡在map 100%和reduce 0%。 请帮忙 我已经设置了使用Vagrant和Virtual Box的环境。创建了两个实例。 yarn-site.xml

  • 我是Hadoop和Map/reduce框架的新手。在进行第一个程序时,字数问题,我陷入了跟踪者的工作细节。Map/Reduce完成图代表什么?或者通俗地说,x,y轴上代表什么?