如何利用iOS中的多个处理器内核来实现循环访问共享数据的最快/最短计算?

时间:2019-02-04 10:02:07

标签: ios swift ios-multithreading

当在iOS上运行的Swift中对数组运行for-loop时,我想利用整个CPU,因为我认为从理论上讲这应该可以加快计算速度。但是,我的结果却相反,在单个DispatchQueue而不是多个DispatchQueue上运行所有内容,实际上执行起来更快。我将提供一个示例,并想知道为什么单线程方法更快,并且如果可能的话,我仍然可以通过正确利用多个cpu内核来减少计算所需的时间?

对于那些只想查看我意图代码的人,可以跳过,因为本节仅阐述我的意图方法

示例中的“我的意图”:

我正在确定地图上的一条线(行驶轨迹,每秒的纬度和经度)是否在某个预定区域内,即地图上的多边形(围绕整个区域的纬度和经度)。为此,我有一个函数来计算多边形中是否有单个点。我正在使用for循环,以遍历汽车行驶轨迹中的每个位置,并使用该功能检查该点是否在多边形内。如果每个追踪到的位置都在多边形内,则整个追踪到的汽车行驶都在该区域内进行。

我将iPhone X用于开发目的,并利用整个CPU来加快计算速度。

我的方法:

在提供的示例中,我有3个变体,导致计算所需的时间(以秒为单位):

Time elapsed for single thread variant: 6.490409970283508 s.
Time elapsed for multi thread v1 variant: 24.076722025871277 s.
Time elapsed for multi thread v2 variant: 23.922222018241882 s.

第一种方法最简单,即不使用多个DispatchQueue

第二种方法利用了DispatchQueue.concurrentPerform(iterations: Int)。 我觉得这可能是满足我需要的最佳解决方案,因为它已经实施并且似乎是为我的确切目的而设计的。

第三种方法是我自己的方法,它根据操作系统报告的活动CPU内核数,将数组的大致相等部分调度到在DispatchQueue上运行的for循环中。

我也尝试了使用inout参数(通过引用调用)但没有用的变体。时间保持不变,因此我没有提供更多代码来解决问题。

我还知道,只要找到不在多边形内的单个点,就可以立即返回该函数,但这不属于该问题。

我的代码:

    /**
    Function that calculates wether or not a 
    single coordinate is within a polygon described
    as a pointlist. 
    This function is used by all others to do the work.
    */
    private static func contains(coordinate: CLLocationCoordinate2D, with pointList: [CLLocationCoordinate2D]) -> Bool {
        var isContained = false
        var j = pointList.count - 1
        let lat = coordinate.latitude
        let lon = coordinate.longitude
        for i in 0 ..< pointList.count {

            if (pointList[i].latitude > lat) != (pointList[j].latitude > lat) &&
                (lon < (pointList[j].longitude - pointList[i].longitude) * (lat - pointList[i].latitude) / (pointList[j].latitude - pointList[i].latitude) + pointList[i].longitude) {
                isContained.toggle()
            }
            j = i
        }
        return isContained
    }

///Runs all three variants as are described in the question
    static func testAllVariants(coordinates: [CLLocationCoordinate2D], areInside pointList: [CLLocationCoordinate2D]) {
        var startTime = CFAbsoluteTimeGetCurrent()
        var bool = contains_singleThread(coordinates: coordinates, with: pointList)
        var timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
        print("Time elapsed for single thread variant: \(timeElapsed) s.")

        startTime = CFAbsoluteTimeGetCurrent()
        bool = contains_multiThread_v1(coordinates: coordinates, with: pointList)
        timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
        print("Time elapsed for multi thread v1 variant: \(timeElapsed) s.")

        startTime = CFAbsoluteTimeGetCurrent()
        bool = contains_multiThread_v2(coordinates: coordinates, with: pointList)
        timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
        print("Time elapsed for multi thread v2 variant: \(timeElapsed) s.")
    }

    private static func contains_singleThread(coordinates: [CLLocationCoordinate2D], with pointList: [CLLocationCoordinate2D]) -> Bool {
        var bContainsAllPoints = true
        for coordinate in coordinates {
            if !contains(coordinate: coordinate, with: pointList) {
                bContainsAllPoints = false
            }
        }
        return bContainsAllPoints
    }

    private static func contains_multiThread_v1(coordinates: [CLLocationCoordinate2D], with pointList: [CLLocationCoordinate2D]) -> Bool {
        let numOfCoordinates = coordinates.count
        var booleanArray = Array(repeating: true, count: numOfCoordinates)
        DispatchQueue.concurrentPerform(iterations: numOfCoordinates) { (index) in
            if !contains(coordinate: coordinates[index], with: pointList) {
                booleanArray[index] = false
            }
        }
        return !booleanArray.contains(false)
    }

    private static func contains_multiThread_v2(coordinates: [CLLocationCoordinate2D], with pointList: [CLLocationCoordinate2D]) -> Bool {
        let numOfCoordinates = coordinates.count
        let coreCount = ProcessInfo().activeProcessorCount

        func chunk<T>(array: [T], into size: Int) -> [[T]] {
            return stride(from: 0, to: array.count, by: size).map {
                Array(array[$0 ..< Swift.min($0 + size, array.count)])
            }
        }

        let segments = chunk(array: coordinates, into: numOfCoordinates/coreCount)

        let dg = DispatchGroup()
        for i in 0..<segments.count {
            dg.enter()
        }

        var booleanArray = Array(repeating: true, count: segments.count)

        for (index, segment) in segments.enumerated() {
            DispatchQueue.global(qos: .userInitiated).async {
                for coordinate in segment {
                    if !contains(coordinate: coordinate, with: pointList) {
                        booleanArray[index] = false
                    }
                }
                dg.leave()
            }
        }

        dg.wait()
        return !booleanArray.contains(false)
    }

示例数据

我已经为希望使用数据进行测试的人上传了两个json文件。输入的时间与我输入的时间相同。

追踪的行车路线:Link to json File 地区/区域:Link to json File

1 个答案:

答案 0 :(得分:0)

感谢社区,我解决了这个问题。 该答案包含评论部分带来的各种结果。

有两种方法,一种是使用指针,这是更通用的方法。另一个更具体地针对我的问题,它利用GPU来查看多个点是否在预定义的多边形内。无论哪种方式,这两种方式都可以,因为代码讲的不只是单词;)。

使用指针(注意:基本的“ contains / containsWithPointer”功能可以在问题中找到):

private static func contains_multiThread(coordinates: [CLLocationCoordinate2D], with pointList: [CLLocationCoordinate2D]) -> Bool {
        let numOfCoordinates = coordinates.count
        var booleanArray = Array(repeating: true, count: numOfCoordinates)
        let coordinatePointer: UnsafeBufferPointer<CLLocationCoordinate2D> = {
            return coordinates.withUnsafeBufferPointer { pointer -> UnsafeBufferPointer<CLLocationCoordinate2D> in
                return pointer
            }
        }()
        let pointListPointer: UnsafeBufferPointer<CLLocationCoordinate2D> = {
            return pointList.withUnsafeBufferPointer { pointer -> UnsafeBufferPointer<CLLocationCoordinate2D> in
                return pointer
            }
        }()
        let booleanPointer: UnsafeMutableBufferPointer<Bool> = {
            return booleanArray.withUnsafeMutableBufferPointer { pointer -> UnsafeMutableBufferPointer<Bool> in
                return pointer
            }
        }()

        DispatchQueue.concurrentPerform(iterations: numOfCoordinates) { (index) in
            if !containsWithPointer(coordinate: coordinatePointer[index], with: pointListPointer) {
                booleanPointer[index] = false
            }
        }

    return !booleanArray.contains(false)
}

使用GPU:

private static func contains_gpu(coordinates: [CLLocationCoordinate2D], with pointList: [CLLocationCoordinate2D]) -> Bool {
        let regionPoints = pointList.compactMap {CGPoint(x: $0.latitude, y: $0.longitude)}
        let trackPoints = coordinates.compactMap {CGPoint(x: $0.latitude, y: $0.longitude)}

        let path = CGMutablePath()
        path.addLines(between: regionPoints)
        path.closeSubpath()

        var flag = true
        for point in trackPoints {
            if !path.contains(point) {
                flag = false
            }
        }

        return flag
    }

哪个函数更快取决于系统,点数和多边形的复杂度。我的结果是,多线程变体的速度大约快30%,但是当多边形相当简单或点数达到数百万时,间隙将关闭,最终gpu变体会变得更快。谁知道,针对此特定问题,将两者结合起来可能会得到更好的结果。