【问题标题】:Parallel for loop in SwiftSwift 中的并行 for 循环
【发布时间】:2014-06-04 12:35:35
【问题描述】:

与以下 C 和 OpenMP 代码最接近的 Swift 等效项是什么(假设 n 很大,f 很简单):

#openmp parallel for
for (int i = 0; i < n; ++i) {
    a[i] = f(b[i]);
}

用 striding 和 dispatch_apply 并行化 for 循环对于这样的例行任务来说似乎需要做很多工作。有什么巧妙的捷径吗?

【问题讨论】:

  • 也许你需要使用另一个 Swift:swift-lang.org
  • 对,使用 java 来获得高性能,swift-lang.org ..... 嗯不

标签: parallel-processing swift


【解决方案1】:

如果您的代码有循环,并且每次循环完成的工作独立于其他迭代中完成的工作,您可以考虑使用 dispatch_apply 或 dispatch_apply_f 函数重新实现该循环代码。这些函数将循环的每次迭代分别提交到调度队列进行处理。当与并发队列结合使用时,此功能可让您同时执行循环的多次迭代。

在这里阅读:https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/ThreadMigration/ThreadMigration.html#//apple_ref/doc/uid/TP40008091-CH105-SW2

对于迅捷:https://developer.apple.com/documentation/dispatch/dispatchqueue/2016088-concurrentperform

DispatchQueue.concurrentPerform(iterations: 1000) { (index) in
     print("current: \(index)")
}

【讨论】:

    【解决方案2】:

    似乎(来自 iBook)还没有用于并行性的 swift 特定 API/语言功能。在这一点上,使用 GCD 似乎是最好的选择,就性能而言。如果您正在寻找代码简洁,您可以使用标准的 Objective-C 习惯用法进行并发数组枚举:

        var array : Int[] = [1,2,3,4]
        array.bridgeToObjectiveC().enumerateObjectsWithOptions(NSEnumerationOptions.Concurrent, {(obj: AnyObject!, index: Int, outStop: CMutablePointer<ObjCBool>) -> Void in
            // Do stuff
        });
    

    【讨论】:

    • 我很想看到将这种方法与 (1) 简单的单线程 Swift 实现和 (2) OpenMP 并行 C 实现进行比较的基准测试!
    • 如果单线程 swift 实现优于 OpenMP,那么工作负载首先不适合并行化。如果 OpenMP 版本的性能优于单线程 swift,那么……这并不奇怪,真的。我认为更有趣的比较是使用 GCD 进行并行处理与使用 OpenMP 的 C 进行比较。 (而且我希望对于足够并行的工作负载,C 会获胜,但这将是一个有趣的测试。)
    • 我认为您误解了我的评论。我希望看到两个不同的比较:(1) Swift NSEnumerationOptions.Concurrent parallelism vs. Swift 单线程和 (2) Swift NSEnumerationOptions.Concurrent parallelism vs. C OpenMP parallelism。
    • w/r/t #1,我的评论成立。无论使用什么 API,单线程与并行都是无趣的,因为它首先应该完全由工作负载的并行度问题主导。
    • 不,我认为基准测试 (1) 非常重要,以便了解我们是否可以使用 NSEnumerationOptions 实现近乎最佳的加速。Swift 应用程序中的并发并行性。我完全不相信与实现 enumerateObjectsWithOptions(NSEnumerationOptions.Concurrent) 相关的开销可以忽略不计,尤其是当您需要在 Swift 和 ObjC 之间架起桥梁时。众所周知,使用 C 和 OpenMP 可以实现近乎最佳的加速(例如,与单线程版本相比,使用 20 个内核可以获得几乎 20 倍的加速); Swift 也是这样吗?
    【解决方案3】:

    上述答案中关于使用concurrentPerform 的线索是正确的,但没有展开。所以这里是简单的解决方案:

    class ConcurrentCalculator<T> {
    
        func calc(for b: [T], _ f: (T) -> T) -> [T?] {
    
            let n = b.count
    
            var a: [T?] = Array(repeating: nil, count: n)
    
            DispatchQueue.concurrentPerform(iterations: n) { i in
    
                a[i] = f(b[i])
            }
    
            return a
        }
    }
    

    这是测试:

    let myB = Array(1...100)
    
    func myF(b: Int) -> Int {
    
        b * b
    }
    
    let myCalc = ConcurrentCalculator<Int>()
    let myA = myCalc.calc(for: myB, myF)
    
    print(myA)
    

    输出看起来像

    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969, 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776, 5929, 6084, 6241, 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100, 8281, 8464, 8649, 8836, 9025, 9216, 9409, 9604, 9801, 10000]
    

    当然这是一个基本的解决方案:它不是线程安全的,而且它可能会遇到大型数组的内存问题。这可以稍后添加。

    这种方法的美妙之处在于您不需要计算可用线程等。它只会使用所有可用线程。

    【讨论】:

      【解决方案4】:

      对于 Swift 中for 循环的并行性,您必须使用

      DispatchQueue.concurrentPerform(iteration: execute:)

      【讨论】:

        【解决方案5】:

        您可以将 Swift 数组转换为 NSArray 并使用 enumerateObjectsWithOptions 而无需桥接 - 请参见下面的简单示例:

        let kCompanyListFileNames :[String] = [
          "nyse_companylist",
          "nasdaq_companylist",
          "amex_companylist"
        ]
        
        let companyListFileNames:NSArray = kCompanyListFileNames as NSArray
        
        companyListFileNames.enumerateObjectsWithOptions(NSEnumerationOptions.Concurrent) {
              (companyName:AnyObject!, index:Int, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
              println(companyName)
            }
        

        【讨论】:

        • 您是否意识到对于习惯于高性能 c 或 fortran 的人来说,这个解决方案有多么复杂?没有办法用 openmp、mpi 或其他内置的高性能并行计算库来快速处理。我会保留我的 openmp 编译指示并使用可笑的桥接头。
        • @μολὼν.λαβέ 同时,Swift 和 Swift 社区都清楚地知道,Swift 并不是所有编程问题的终极解决方案。 Swift 与它的前身 Objective-C 有很多桥接,但可以说通过将 C 数据类型和函数干净地导入 Swift 来桥接纯 C 付出了更多的努力。这里理智的、预期的实际解决方案是用 C 编写对性能至关重要的东西,将其包装在一个函数中,然后从 Swift 中使用该函数。
        猜你喜欢
        • 2017-04-18
        • 2014-08-05
        • 2012-07-31
        • 1970-01-01
        • 1970-01-01
        • 2014-07-26
        • 2023-03-22
        • 2016-08-10
        • 2013-12-12
        相关资源
        最近更新 更多