Swift 性能中的 xy 数组

Posted

技术标签:

【中文标题】Swift 性能中的 xy 数组【英文标题】:xy array in Swift performance 【发布时间】:2017-08-04 14:53:42 【问题描述】:

我有两个带有浮点数的大型(几千个值)数组,并希望将它们组合在一个 xy 点数组中以进行进一步处理,例如绘图。

所以现在在 Xcode 游乐场我正在这样做:

let xArray = // read from datafile, fast
let yArray = // read from another datafile, fast

struct xyPoint 
   let x: Float
   let y: Float


var spectrum: [xyPoint] = []

for i in 0..<xArray.count 
    let xy = xyPoint(x: xArray[i], y: yArray[i])
    spectrum.append(xy)

现在,当我运行操场时,这需要很长时间。

有什么想法可以加快速度吗?

【问题讨论】:

不在操场上运行它会是一个好的开始:) 在实际的发布构建项目中尝试它。 哈哈,是的,你是对的。在实际项目中,数组是立即创建的。 话虽如此,还有其他(更像 Swift 的)方法可以做到这一点,甚至可能更快? 注意应该是0..&lt;xArray.count。在数组上调用reserveCapacity() 有助于避免重新分配。 您创建自己的点结构而不使用 CGPoint 的任何原因? 【参考方案1】:

我检查了针对您的问题的各种解决方案的性能。你可以从this link to github下载我的测试

A) 你的代码

var spectrum: [XYPoint] = []
for i in 0..<xArray.count 
    let xy = XYPoint(x: xArray[i], y: yArray[i])
    spectrum.append(xy)

B) 邮编 + 地图 (Martin R's answer)

let spectrumB = zip(xArray, yArray).map(XYPoint.init)

C) 范围+地图(我的解决方案)

let spectrum = (0 ..< xArray.count).map  i in
    return XYPoint(x: xArray[i], y: yArray[i])

D) ReserveCapacity + 追加 (Duncan C's answer)

var spectrum: [XYPoint] = []
spectrum.reserveCapacity(xArray.count)
for (index, value) in xArray.enumerated() 
    spectrum.append(XYPoint(x: xArray[index], y: yArray[index]))


我的结果(以秒为单位)

            ╭──────────────┬──────────────┬──────────────┬──────────────╮
            │       A      │       B      │       C      │       D      │
╭───────────╬══════════════╪══════════════╪══════════════╪══════════════╡
│       100 ║  0.000009426 │  0.000002401 │  0.000000571 │  0.000000550 │
│       200 ║  0.000003356 │  0.000002629 │  0.000000911 │  0.000000866 │
│       500 ║  0.000005610 │  0.000007288 │  0.000002236 │  0.000002012 │
│      1000 ║  0.000010638 │  0.000009181 │  0.000003905 │  0.000005030 │
│      2000 ║  0.000019377 │  0.000013316 │  0.000007116 │  0.000008732 │
│      5000 ║  0.000023430 │  0.000019304 │  0.000019809 │  0.000019092 │
│     10000 ║  0.000050463 │  0.000031669 │  0.000035121 │  0.000035420 │
│     20000 ║  0.000087040 │  0.000058664 │  0.000069300 │  0.000069456 │
│     50000 ║  0.000272357 │  0.000204213 │  0.000176962 │  0.000192996 │
│    100000 ║  0.000721436 │  0.000459551 │  0.000415024 │  0.000437604 │
│    200000 ║  0.001114534 │  0.000924621 │  0.000816374 │  0.000896202 │
│    500000 ║  0.002576687 │  0.002094998 │  0.001860833 │  0.002060462 │
│   1000000 ║  0.007063596 │  0.005924892 │  0.004319181 │  0.004869024 │
│   2000000 ║  0.014474969 │  0.013594134 │  0.008568550 │  0.009388957 │
│   5000000 ║  0.038348767 │  0.035136008 │  0.021276415 │  0.023855382 │
│  10000000 ║  0.081750925 │  0.078742713 │  0.043578664 │  0.047700495 │
│  20000000 ║  0.202616669 │  0.199960563 │  0.148141266 │  0.145360923 │
│  50000000 ║  0.567078563 │  0.552158644 │  0.370327555 │  0.397115294 │
│ 100000000 ║  1.136993625 │  1.101725386 │  0.713406642 │  0.740150322 │
└───────────╨──────────────┴──────────────┴──────────────┴──────────────┘

【讨论】:

哇-感谢基准测试,我稍后会自己尝试一下。看起来 B 和 C 是要走的路。 您是否在发布配置中编译并运行了测试(即优化)?我为 1,000,000 个元素和 zip 方法测量了 12 milli 秒。 @MartinR 我更新了我的答案。现在我创建了一个“命令行工具”并在 Release 中运行。您可以从github.com/Decybel07/xy-array-in-Swift-performance 下载。在这些测试中,您的解决方案不是很有效 @AdrianBobrowski:我从未声称 zip 解决方案是最有效的,事实上我试图解释为什么它不是禁食的。使用(0 ..&lt; xArray.count).map 是个好主意! 我的结果(n=10,8750 个数据点):A:0.003419374; B:0.010853558; C:0.00808398; D:0.007784076。【参考方案2】:

创建点数组的最简单方法是

let spectrum = zip(xArray, yArray).map(XYPoint.init)

(我冒昧地将结构体XYPoint 称为 Swift 类型 应该以大写字母开头。)这也允许定义 结果数组为常量。

但是,就执行时间而言,它并不是最快的。 原因可能是

zip() 对一般序列进行操作,不利用 的输入是数组。 zip() 返回 Sequence,因此返回 map() 不知道要创建的元素数量。 结果,目标数组将被重新分配几个 次。

因此,如果您保留所需的显式循环,则速度会更快 提前容量:

var spectrum: [XYPoint] = []
spectrum.reserveCapacity(xArray.count)
for i in 0..<xArray.count 
    let xy = XYPoint(x: xArray[i], y: yArray[i])
    spectrum.append(xy)

在我的测试中(在 1.2 GHz Intel Core m5 MacBook 上,在 Release 中编译 模式)具有两个 10,000 个元素的数组,第一种方法采用 大约 0.65 毫秒,第二种方法大约 0.42 毫秒。 对于 1,000,000 个元素,我测量了 12 毫秒与 6 毫秒。

【讨论】:

使用 zip 的好建议。 (已投票。)我在 SO 上看过一两次,但忘记了。这是我见过的最“迅速”的解决方案,即使它有点慢。但是,对于 12 毫秒和 6 毫秒的一百万个项目,我认为我们可以忽略这一点并选择更简洁的单行解决方案。 (6 毫秒的差异是无法察觉的。即使是 60 毫秒的差异也无法真正察觉。) 有趣。为Collection 专门制作一个zip 不值得吗? (哪位有已知的count)? 我想知道for (index, value) in array.enumerated() 版本是否比使用整数索引对两个数组进行索引要快得多? @DuncanC:我无法测量出显着差异。多次运行我的测试后,枚举的速度大多稍慢,但有时更快。 有趣。 Apple 暗示for...in 版本明显快于for i in 0..n let x = array[i] 版本。 (快速枚举和所有。)【参考方案3】:

一旦你有 2 个独立的数组,将它们组合起来有点尴尬,而且没有一种简洁的“Swifty”方法可以做到这一点。如果您有一个结构数组,其中每个结构包含一个 x 和 y 值,您可以使用 map 语句将该数组转换为 CGPoint 对象数组(实际上是另一种 Struct 类型)。

你先告诉我们:

let xArray = // read from datafile, fast
let yArray = // read from another datafile, fast

重新编写您未显示的代码可能会更好,这样您就可以:

读取一个 x 点 读取y点 为该 X/Y 对创建一个 CGPoint 将新的 CGPoint 添加到 CGPoint 值的输出数组中

甚至,重构创建数据文件的代码,以便编写包含 X/Y 对数组的文件,而不是 2 个单独的文件。

如果您有 2 个单独的数组,则可以使用 for... 的变体,它为每个数组条目提供索引和值:

let xArray: [CGFloat] = [0.1, 0.2, 0.3, 0.4]
let yArray: [CGFloat] = [0.4, 0.3, 0.2, 0.1]

var output = [CGPoint]()
output.reserveCapacity(xArray.count)
for (index, value) in xArray.enumerated() 
    let yValue = yArray[index]
    let aPoint = CGPoint (x: value, y: yValue)
    output.append(aPoint)

如果yArray 的值少于xArray,上面的代码将崩溃,如果它包含更多 值而不是xArray,则将错过yArray 中的最后一个值。一个完整的实现应该首先进行错误检查并处理数组具有不同数量值的情况。

【讨论】:

我的回答差不多完成了,与你的相似之处不是故意的,而是不可避免的:) 数据文件是外部的,是两个 base64 编码的文件,它们被转换为 x 或 y 点的数组。因此,不幸的是,改造该部分不是一种选择。 哦,好吧。使用 Martin R 的代码或我的代码,然后收工。 我喜欢 zip 版本,性能差异在大多数情况下没有意义,所以我可能会使用它。【参考方案4】:

当您在主操场文件中运行代码时,您可能会启用日志记录。这给代码带​​来了巨大的性能损失。

我在问题中尝试了您的代码作为函数。将该函数放入主 swift 文件中以处理大小为 10000 的数组需要 10 多分钟!

我将函数移动到 Playground 的源文件夹中的一个单独的 swift 文件中,具有相同大小的数组,它立即完成。

我使用的代码来自您的问题(在函数内),而不是优化版本。

【讨论】:

以上是关于Swift 性能中的 xy 数组的主要内容,如果未能解决你的问题,请参考以下文章

Swift子数组提取性能优化分析

技术学习-Swift子数组提取性能优化分析

技术学习-Swift子数组提取性能优化分析

Swift之深入解析子数组提取的性能优化

Swift 中的 NSAttributedString draw(in: rect) 性能不佳?

swift_dynamiccast 处理过多并影响 iOS 中的性能