Как использовать несколько процессорных ядер в iOS для достижения самого быстрого / самого короткого вычисления цикла доступа к общим данным? - PullRequest
0 голосов
/ 04 февраля 2019

При запуске цикла for для массива в Swift, работающего на iOS, я бы хотел использовать весь процессор, так как считаю, что это теоретически должно ускорить вычисления.Однако мой результат противоположен, что при выполнении всего на одном DispatchQueue вместо кратного DispatchQueue s, на самом деле работает быстрее.Я приведу пример и хотел бы знать, почему однопотоковый подход является более быстрым, и, если возможно, я мог бы еще сократить время, необходимое для расчета, правильно используя несколько ядер ЦП?

Для тех, кто просто хочет увидеть кодмоего намерения может быть пропущено, поскольку в следующем разделе описываются только мои намерения и подход .

Мое намерение в представленном примере:

Я определяю, находится ли линия на карте (след автомобильной поездки, широта и долгота за каждую секунду) в пределах определенного заранее определенного региона, многоугольника на карте (широты и долготы, которые проходят вокруг всего региона),Для этого у меня есть функция, которая вычисляет, если одна точка находится в пределах многоугольника.Я использую цикл for, чтобы перебрать каждое место в отслеживаемой линии автомобильной поездки, и с помощью этой функции проверить, находится ли точка в пределах многоугольника.Если каждое отслеживаемое местоположение находится внутри многоугольника, то вся поездка отслеживаемого автомобиля происходила в указанном регионе.

Я использую iPhone X в целях разработки и для использования всего ЦП, чтобы ускорить этот расчет.

Мой подход:

В представленных примерах у меня есть 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).Мне казалось, что это может быть оптимальным решением для моих нужд, поскольку оно уже реализовано и, похоже, сделано именно для моей цели.

Третий подход - мой, и он планирует примерно равные части массива дляЦиклы for, работающие на DispatchQueue s, основанные на количестве активных ядер ЦП, сообщаемых ОС.

Я также пробовал варианты, в которых используются параметры 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-файлы для тех, кто хочет иметь данные для запуска тестов.Это тот же самый ввод, который привел к моим зарегистрированным временам.

Отслеживаемая поездка на автомобиле: Ссылка на файл json Регион / область: Ссылка на файл json

1 Ответ

0 голосов
/ 07 марта 2019

Я решил проблему, спасибо сообществу.Этот ответ включает в себя различные результаты, представленные в разделе комментариев.

Существует два способа, один из которых заключается в использовании указателей, который является более общим подходом.Другая более специфична для моей проблемы и использует графический процессор, чтобы увидеть, находится ли множество точек в пределах заранее определенного многоугольника.В любом случае, здесь есть два способа, так как код говорит больше, чем слова;).

Используйте указатели (примечание: в вопросе можно найти базовую функцию "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)
}

Использование графического процессора:

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 становится быстрее.Кто знает, вы могли бы получить еще лучшие результаты, комбинируя два для этой конкретной проблемы.

...