在Swift中访问多维数组的最有效方法?

时间:2014-11-26 00:40:04

标签: swift multidimensional-array

我有一个固定大小的多维数字数组(浮点数,通常,但在我的示例代码中使用int来避免被转换开销分散注意力),我想有效地操作它。 Swift不提供这样的多维数组,但您可以通过一维数组阵列获得效果。然而,这些似乎非常非常缓慢。还有更好的方法吗?

我有一个测试问题(我用它来测试其他语言),我将两个2D数组传递给一个子程序,该子程序将一个元素的每个元素设置为另一个元素的相应元素加上两个索引值的总和。 (这意味着每个元素发生的事情取决于它的坐标,这是大多数现实世界中的情况。)

我使用-Ounchecked标志编译。

选项1:使用一维数组阵列,性能非常慢。 10次​​传球耗时1.5秒。

选项2:在http://blog.trolieb.com/trouble-multidimensional-arrays-swift中使用相当简洁的想法,其中Array2D类使用底层1D数组并实现下标()使其看起来像2D数组,事情加速了很多(2个数量级) ): 1000次传球需要1.0秒

选项3:回到曾经在C中使用的非常笨拙的代码类型,你使用1D数组并明确地执行index =(row * columns)+列计算,事情再次加速(不是相当于2个数量级) 100000次传球需要3.6秒。

选项3是我在clang中使用-O3编译的等效C代码的2倍,因此对于早期编译器来说是好的。问题是它真的很难看,很尴尬,容易出错。可以在C中使用一些技巧,比如将指针数组分配到每行的开头(C中的Numerical Recipes这样做)允许你对数组使用2D语法,并且使用面向对象的C你可以做到这一点相当优雅和高效。我的问题是在Swift中是否有一种方法可以获得像数组[Iy] [Ix](或数组[Iy,Ix]或其他任何代码,而不是数组[Iy * Ny + Ix])以快速运行的代码?

我应该说我对Swift很新,而且我喜欢到目前为止看到的内容,并且我很欣赏编译器只会变得更快。我使用固定大小的多维数组对科学应用程序进行了大量编码,我对将来某些时候使用Swift的可能性感兴趣。或者我应该要求Apple为Swift添加真正的多维数组支持吗?

这是我一直在使用的测试代码:

//
//  main.swift
//
//  Tests 3 ways of handling 2D arrays in Swift. Test takes a 2D array and calls a routine
//  that takes each element of an input array and adds the X and Y index values to it and
//  returns an array with the result.
//
//  Command line arguments: Option Nrpt Nx Ny
//
//  Option is type of array used (1: Swift array of arrays, 
//                                2: Array2D 1D array looking like a 2D array
//                                3: 1D array used like a 2D array with explicit index calculation)
//  Nrpt is number of repeats of subroutine call
//  Nx, Ny are array dimensions.
//

import Darwin

//  Array2D comes from http://blog.trolieb.com/trouble-multidimensional-arrays-swift/

class Array2D {
    var cols:Int, rows:Int
    var matrix: [Int]

    init(cols:Int, rows:Int) {
        self.cols = cols
        self.rows = rows
        matrix = Array(count:cols*rows, repeatedValue:0)
    }
    subscript(col:Int, row:Int) -> Int {
        get { return matrix[cols * row + col] }
        set { matrix[cols*row+col] = newValue }
    }
    func colCount() -> Int { return self.cols }
    func rowCount() -> Int { return self.rows }
}

//  Using a 'proper' Swift '2D' array - ie an array of 1D arrays
func Subr (Input: Array<Array<Int>>, Nx: Int, Ny : Int, inout Output: Array<Array<Int>>) {
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            Output[Iy][Ix] = Input[Iy][Ix] + (Ix + Iy)
        }
    }
}

//  Using an Array2D array - wrapping up a 1D array to act as a 2D one.
func Subr2d (Input: Array2D, Nx: Int, Ny : Int, inout Output: Array2D) {
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            Output[Ix,Iy] = Input[Ix,Iy] + (Ix + Iy)
        }
    }
}

//  Using a 1D Swift array and doing the indexing explicitly
func Subr1d (Input: [Int], Nx: Int, Ny: Int, inout Output: [Int]) {
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            Output[Iy * Nx + Ix] = Input[Iy * Nx + Ix] + (Ix + Iy)
        }
    }
}

var Option:Int = 1
if let argStr = String.fromCString(C_ARGV[1]) {
    if let argInt = argStr.toInt() { Option = argInt }
}

var Nrpt:Int = 100
if let argStr = String.fromCString(C_ARGV[2]) {
    if let argInt = argStr.toInt() { Nrpt = argInt }
}

var Nx:Int = 2000;
if let argStr = String.fromCString(C_ARGV[3]) {
    if let argInt = argStr.toInt() { Nx = argInt }
}

var Ny:Int = 10;
if let argStr = String.fromCString(C_ARGV[4]) {
    if let argInt = argStr.toInt() { Ny = argInt }
}


println("Repeats: \(Nrpt), Array \(Nx) by \(Ny)")

switch Option {
case 1:

    println ("Using an ordinary Swift '2D' array of arrays")

    var array = Array(count:Ny, repeatedValue:Array(count:Nx, repeatedValue:Int()))

    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            array[Iy][Ix] = (Ix + Iy)
        }
    }

    var output = Array(count:Ny, repeatedValue:Array(count:Nx, repeatedValue:Int()))

    let start : UInt64 = mach_absolute_time()

    for Irpt in 0...Nrpt-1 {
       Subr(array,Nx,Ny,&output)
    }

    let duration : UInt64 = mach_absolute_time() - start

    check:
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            let Expected = array[Iy][Ix] + (Ix + Iy)
            if (output[Iy][Ix] != Expected) {
                println("Error at \(Ix),\(Iy) Got \(output[Iy][Ix]) expected \(Expected)")
                break check
            }
        }
    }

    var info : mach_timebase_info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&info)

    let total = (duration * UInt64(info.numer) / UInt64(info.denom)) / 1_000_000
    println("2D array took:\(total) ms.")

case 2:

    println ("Using the Array2D class")

    var array2 = Array2D(cols: Nx, rows: Ny)
    var output2 = Array2D(cols: Nx, rows: Ny)

    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            array2[Ix,Iy] = (Ix + Iy)
        }
    }

    println("Timing array2D version")

    let start2 : UInt64 = mach_absolute_time()

    for Irpt in 0...Nrpt-1 {
        Subr2d(array2,Nx,Ny,&output2)
    }

    let duration2 : UInt64 = mach_absolute_time() - start2

    check:
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            let Expected = array2[Ix,Iy] + (Ix + Iy)
            if (output2[Ix,Iy] != Expected) {
                println("Error at \(Ix),\(Iy) Got \(output2[Ix,Iy]) expected \(Expected)")
                break check
            }
        }
    }


    var info2 : mach_timebase_info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&info2)

    let total2 = (duration2 * UInt64(info2.numer) / UInt64(info2.denom)) / 1_000_000
    println("Array2D version took:\(total2) ms.")

case 3:

    println ("Using an a 1D array and handling the indexing explicitly")

    var array3 = Array(count:Ny * Nx, repeatedValue:Int())

    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            array3[Iy * Nx + Ix] = (Ix + Iy)
        }
    }

    var output3 = Array(count:Ny * Nx, repeatedValue:Int())

    let start3 : UInt64 = mach_absolute_time()

    for Irpt in 0...Nrpt-1 {
        Subr1d(array3,Nx,Ny,&output3)
    }

    let duration3 : UInt64 = mach_absolute_time() - start3

    check:
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            let Expected = array3[Iy * Nx + Ix] + (Ix + Iy)
            if (output3[Iy * Nx + Ix] != Expected) {
                println("Error at \(Ix),\(Iy) Got \(output3[Iy * Nx + Ix]) expected \(Expected)")
                break check
            }
        }
    }

    var info3 : mach_timebase_info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&info3)

    let total3 = (duration3 * UInt64(info3.numer) / UInt64(info3.denom)) / 1_000_000
    println("1D array took:\(total3) ms.")

default:
    println ("Invalid option code. Must be 1,2, or 3")
}

2 个答案:

答案 0 :(得分:1)

Chris Lattner自己在Apple开发论坛上回应了这个问题,听起来像#2 /#3是我们最好的解决方案,直到即将进行编译器修复。

“这是一个众所周知的问题:2D阵列......可能会导致极差的性能,因为它们所基于的写入时复制(COW)优化在某些情况下会失败......

它的修复只是差点错过6.1版本,因为它需要一些内部基础架构工作。也就是说,它将在swift编译器的下一个重要更新中发布。

与此同时,您可以使用(丑陋但有效)的解决方法。例如,如果您的数组是矩形,则可以使用一个大小为m * n个元素的数组,并手动索引它。

-Chris“

答案 1 :(得分:1)

在某种程度上,Apple已经回答了我的问题。我现在已经有一段时间不看这个了 - 事实上,甚至还没有使用过Swift。但是我刚安装了XCode 9和Swift 4,并且认为我看看情况是否有所改变。我不得不做一些快速的修改来建立测试程序,并且我已经再次尝试了。

底线:所有三个选项现在大约在同一时间运行,而且速度也不差。我认为这是一个显着的改进,这意味着标准的Swift处理2D阵列的方式 - 作为一个阵列阵列 - 不再有性能损失,并且,至少在此测试的基础上,显然现在的路要走。这正是我认为每个人都想要的。 (我使用-Ounchecked构建,这确实产生了因素2的差异。)

与C ++中的等效代码相比,考虑到你必须通过一些环节将多维数组传递给C ++例程,我认为现在在Swift中编写代码要容易得多。上次我说最快的Swift选项(凌乱的&#39;自己做索引&#39;选项)只比同等的C ++慢了2倍。实际上,我现在看到使用C ++和clang加速了4倍,但那是因为clang现在似乎已经改进了它已经令人印象深刻的优化(它以最巧妙的方式抛出矢量指令 - 它确实之前,但现在似乎已经摆脱了一些额外的开销)。我想象的可能会随着时间的推移而来到斯威夫特;我想到的重要一点是 - 再次,在这一个测试的基础上--Swift似乎不再受其阵列处理的限制。自从我最初发布这个问题以来,clang / C ++已经提高了2倍,但是Swift已经改善了。

以下是修改后的代码:

//
//  main.swift
//
//  Tests 3 ways of handling 2D arrays in Swift. Test takes a 2D array and calls a routine
//  that takes each element of an input array and adds the X and Y index values to it and
//  returns an array with the result.
//
//  Command line arguments: Option Nrpt Nx Ny
//
//  Option is type of array used (1: Swift array of arrays,
//                                2: Array2D 1D array looking like a 2D array
//                                3: 1D array used like a 2D array with explicit index calculation)
//  Nrpt is number of repeats of subroutine call
//  Nx, Ny are array dimensions.
//

import Foundation

//  Array2D comes from http://blog.trolieb.com/trouble-multidimensional-arrays-swift/

class Array2D {
    var cols:Int, rows:Int
    var matrix: [Int]

    init(cols:Int, rows:Int) {
        self.cols = cols
        self.rows = rows
      matrix = Array(repeating:0, count:cols*rows)
    }
    subscript(col:Int, row:Int) -> Int {
        get { return matrix[cols * row + col] }
        set { matrix[cols*row+col] = newValue }
    }
    func colCount() -> Int { return self.cols }
    func rowCount() -> Int { return self.rows }
}

//  Using a 'proper' Swift '2D' array - ie an array of 1D arrays
func Subr (Input: Array<Array<Int>>, Nx: Int, Ny : Int, Output: inout Array<Array<Int>>) {
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            Output[Iy][Ix] = Input[Iy][Ix] + (Ix + Iy)
        }
    }
}

//  Using an Array2D array - wrapping up a 1D array to act as a 2D one.
func Subr2d (Input: Array2D, Nx: Int, Ny : Int, Output: inout Array2D) {
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            Output[Ix,Iy] = Input[Ix,Iy] + (Ix + Iy)
        }
    }
}

//  Using a 1D Swift array and doing the indexing explicitly
func Subr1d (Input: [Int], Nx: Int, Ny: Int, Output: inout [Int]) {
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            Output[Iy * Nx + Ix] = Input[Iy * Nx + Ix] + (Ix + Iy)
        }
    }
}

var Option:Int = 1
if (CommandLine.argc > 1) {
    let argStr = CommandLine.arguments[1]
    if let argInt = Int(argStr) { Option = argInt }
}

var Nrpt:Int = 100
if (CommandLine.argc > 2) {
    let argStr = CommandLine.arguments[2]
    if let argInt = Int(argStr) { Nrpt = argInt }
}
var Nx:Int = 2000;
if (CommandLine.argc > 3) {
    let argStr = CommandLine.arguments[3]
    if let argInt = Int(argStr) { Nx = argInt }
}

var Ny:Int = 10;
if (CommandLine.argc > 4) {
    let argStr = CommandLine.arguments[4]
    if let argInt = Int(argStr) { Ny = argInt }
}

print("Repeats: \(Nrpt), Array \(Nx) by \(Ny)")

switch Option {
case 1:

    print ("Using an ordinary Swift '2D' array of arrays")

    var array = Array(repeating:Array(repeating:Int(), count:Nx), count:Ny)

    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            array[Iy][Ix] = (Ix + Iy)
        }
    }

    var output = Array(repeating:Array(repeating:Int(), count:Nx), count:Ny)

    let start : UInt64 = mach_absolute_time()

    for _ in 0...Nrpt-1 {
      Subr(Input: array,Nx: Nx,Ny: Ny,Output: &output)
    }

    let duration : UInt64 = mach_absolute_time() - start

    check:
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            let Expected = array[Iy][Ix] + (Ix + Iy)
            if (output[Iy][Ix] != Expected) {
                print("Error at \(Ix),\(Iy) Got \(output[Iy][Ix]) expected \(Expected)")
                break check
            }
        }
    }

    var info : mach_timebase_info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&info)

    let total = (duration * UInt64(info.numer) / UInt64(info.denom)) / 1_000_000
    print("2D array took:\(total) ms.")

case 2:

    print ("Using the Array2D class")

    let array2 = Array2D(cols: Nx, rows: Ny)
    var output2 = Array2D(cols: Nx, rows: Ny)

    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            array2[Ix,Iy] = (Ix + Iy)
        }
    }

    print("Timing array2D version")

    let start2 : UInt64 = mach_absolute_time()

    for _ in 0...Nrpt-1 {
      Subr2d(Input: array2,Nx: Nx,Ny: Ny,Output: &output2)
    }

    let duration2 : UInt64 = mach_absolute_time() - start2

    check:
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            let Expected = array2[Ix,Iy] + (Ix + Iy)
            if (output2[Ix,Iy] != Expected) {
                print("Error at \(Ix),\(Iy) Got \(output2[Ix,Iy]) expected \(Expected)")
                break check
            }
        }
    }


    var info2 : mach_timebase_info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&info2)

    let total2 = (duration2 * UInt64(info2.numer) / UInt64(info2.denom)) / 1_000_000
    print("Array2D version took:\(total2) ms.")

case 3:

    print ("Using an a 1D array and handling the indexing explicitly")

    var array3 = Array(repeating:Int(), count:Ny * Nx)

    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            array3[Iy * Nx + Ix] = (Ix + Iy)
        }
    }

    var output3 = Array(repeating:Int(), count:Ny * Nx)

    let start3 : UInt64 = mach_absolute_time()

    for _ in 0...Nrpt-1 {
      Subr1d(Input: array3,Nx: Nx,Ny: Ny,Output: &output3)
    }

    let duration3 : UInt64 = mach_absolute_time() - start3

    check:
    for Iy in 0...Ny-1 {
        for Ix in 0...Nx-1 {
            let Expected = array3[Iy * Nx + Ix] + (Ix + Iy)
            if (output3[Iy * Nx + Ix] != Expected) {
                print("Error at \(Ix),\(Iy) Got \(output3[Iy * Nx + Ix]) expected \(Expected)")
                break check
            }
        }
    }

    var info3 : mach_timebase_info = mach_timebase_info(numer: 0, denom: 0)
    mach_timebase_info(&info3)

    let total3 = (duration3 * UInt64(info3.numer) / UInt64(info3.denom)) / 1_000_000
    print("1D array took:\(total3) ms.")

default:
    print ("Invalid option code. Must be 1,2, or 3")
}