Подтвердить что ты не робот

Проблемы с производительностью SwiftyJSON

У меня возникают серьезные проблемы с производительностью, анализируя JSON с SwiftyJson из нашего API и заполняя основные данные.

Данные загружаются с помощью Alamofire, который прекрасно работает, но разбирать json с SwiftyJson очень медленно. Чтобы проверить, действительно ли библиотека была проблемой, я переписал json-парсинг в одном из многих мест, где данные анализируются. В приведенном ниже кодексе я разбираю часы работы одной из примерно 400 туристических достопримечательностей.

См. разницу в этих снимках экрана, от 7,7 до 185 мс:

enter image description here

enter image description here

The Swifty way:

    let openDescription:String = json["OpeningHours"]["OpeningHoursGenericExceptions"].string!
    let monOpen:[String]    = json["OpeningHours"]["Monday"]["From"].string!.componentsSeparatedByString(":")
    let monClose:[String]   = json["OpeningHours"]["Monday"]["To"].string!.componentsSeparatedByString(":")
    let tueOpen:[String]    = json["OpeningHours"]["Tuesday"]["From"].string!.componentsSeparatedByString(":")
    let tueClose:[String]   = json["OpeningHours"]["Tuesday"]["To"].string!.componentsSeparatedByString(":")
    let wedOpen:[String]    = json["OpeningHours"]["Wednesday"]["From"].string!.componentsSeparatedByString(":")
    let wedClose:[String]   = json["OpeningHours"]["Wednesday"]["To"].string!.componentsSeparatedByString(":")
    let thuOpen:[String]    = json["OpeningHours"]["Thursday"]["From"].string!.componentsSeparatedByString(":")
    let thuClose:[String]   = json["OpeningHours"]["Thursday"]["To"].string!.componentsSeparatedByString(":")
    let friOpen:[String]    = json["OpeningHours"]["Friday"]["From"].string!.componentsSeparatedByString(":")
    let friClose:[String]   = json["OpeningHours"]["Friday"]["To"].string!.componentsSeparatedByString(":")
    let satOpen:[String]    = json["OpeningHours"]["Saturday"]["From"].string!.componentsSeparatedByString(":")
    let satClose:[String]   = json["OpeningHours"]["Saturday"]["To"].string!.componentsSeparatedByString(":")
    let sunOpen:[String]    = json["OpeningHours"]["Sunday"]["From"].string!.componentsSeparatedByString(":")
    let sunClose:[String]   = json["OpeningHours"]["Sunday"]["To"].string!.componentsSeparatedByString(":")

Собственный способ:

    var monOpen:[String] = []
    var monClose:[String] = []
    var tueOpen:[String] = []
    var tueClose:[String] = []
    var wedOpen:[String] = []
    var wedClose:[String] = []
    var thuOpen:[String] = []
    var thuClose:[String] = []
    var friOpen:[String] = []
    var friClose:[String] = []
    var satOpen:[String] = []
    var satClose:[String] = []
    var sunOpen:[String] = []
    var sunClose:[String] = []
    var openDescription:String = ""

    if let attractionsArray = orgJson as? NSArray{
        if let attraction = attractionsArray[0] as? NSDictionary{
            if let openHours = attraction["OpeningHours"] as? NSDictionary{
                if let day = openHours["Monday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        monOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        monClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Tuesday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        tueOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        tueClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Wednesday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        wedOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        wedClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Thursday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        thuOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        thuClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Friday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        friOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        friClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Saturday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        satOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        satClose = close.componentsSeparatedByString(":")
                    }
                }
                if let day = openHours["Sunday"] as? NSDictionary{
                    if let open = day["From"] as? String{
                        sunOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"] as? String{
                        sunClose = close.componentsSeparatedByString(":")
                    }
                }
                if let desc = openHours["OpeningHoursGenericExceptions"] as? String{
                    openDescription = desc
                }
            }
        }
    }

Это только часть анализируемых данных, поэтому производительность действительно заметна в приложении.

Я думаю, вопрос в том, что я неправильно использовал SwiftyJSON или это можно ожидать?

4b9b3361

Ответ 1

Прежде всего, ваш "родной путь" не эквивалентен "Swifty way".

Версия SwiftyJSON имеет 45 subscript обращений, но на родном пути доступно только 23 subscript.

Чтобы сделать эквивалент "родной", это должно быть примерно так:

let openDescription = attraction["OpeningHours"]!["OpeningHoursGenericExceptions"] as String
let monOpen  = (attraction["OpeningHours"]!["Monday"]!!["From"] as String).componentsSeparatedByString(":")
let monClose = (attraction["OpeningHours"]!["Monday"]!!["To"] as String).componentsSeparatedByString(":")
let tueOpen  = (attraction["OpeningHours"]!["Tuesday"]!!["From"] as String).componentsSeparatedByString(":")
let tueClose = (attraction["OpeningHours"]!["Tuesday"]!!["To"]! as String).componentsSeparatedByString(":")
// ...

или "Swifty way" должен выглядеть следующим образом:

let openHours = json[0]["OpeningHours"]
var day:JSON

day = openHours["Monday"]
if let open = day["From"].string {
    monOpen = open.componentsSeparatedByString(":")
}
if let close = day["To"].string {
    monClose = close.componentsSeparatedByString(":")
}
day = openHours["Tuesday"]
if let open = day["From"].string {
    tueOpen = open.componentsSeparatedByString(":")
}
if let close = day["To"].string {
    tueClose = close.componentsSeparatedByString(":")
}
// ...

В любом случае, Да, SwiftyJSON медленный. Пусть мера:

import Foundation
import XCTest

let orgJson:AnyObject = [
    [
        "OpeningHours": [
            "OpeningHoursGenericExceptions": "test",
            "Monday":    ["From":"1:2:3","To":"1:2:3"],
            "Tuesday":   ["From":"1:2:3","To":"1:2:3"],
            "Wednesday": ["From":"1:2:3","To":"1:2:3"],
            "Thursday":  ["From":"1:2:3","To":"1:2:3"],
            "Friday":    ["From":"1:2:3","To":"1:2:3"],
            "Saturday":  ["From":"1:2:3","To":"1:2:3"],
            "Sunday":    ["From":"1:2:3","To":"1:2:3"],
        ]
    ]
]
let json = JSON(orgJson)

class JSONTestTests: XCTestCase {

    func testNativeSubscript() {
        measureBlock { () -> Void in

            for _ in 0 ..< 400 {
                autoreleasepool {
                    if let attraction = orgJson[0] as? NSDictionary {
                        let openDescription = attraction["OpeningHours"]!["OpeningHoursGenericExceptions"] as String
                        let monOpen  = (attraction["OpeningHours"]!["Monday"]!!["From"] as String).componentsSeparatedByString(":")
                        let monClose = (attraction["OpeningHours"]!["Monday"]!!["To"] as String).componentsSeparatedByString(":")
                        let tueOpen  = (attraction["OpeningHours"]!["Tuesday"]!!["From"] as String).componentsSeparatedByString(":")
                        let tueClose = (attraction["OpeningHours"]!["Tuesday"]!!["To"] as String).componentsSeparatedByString(":")
                        let wedOpen  = (attraction["OpeningHours"]!["Wednesday"]!!["From"] as String).componentsSeparatedByString(":")
                        let wedClose = (attraction["OpeningHours"]!["Wednesday"]!!["To"] as String).componentsSeparatedByString(":")
                        let thuOpen  = (attraction["OpeningHours"]!["Thursday"]!!["From"] as String).componentsSeparatedByString(":")
                        let thuClose = (attraction["OpeningHours"]!["Thursday"]!!["To"] as String).componentsSeparatedByString(":")
                        let friOpen  = (attraction["OpeningHours"]!["Friday"]!!["From"] as String).componentsSeparatedByString(":")
                        let friClose = (attraction["OpeningHours"]!["Friday"]!!["To"] as String).componentsSeparatedByString(":")
                        let satOpen  = (attraction["OpeningHours"]!["Saturday"]!!["From"] as String).componentsSeparatedByString(":")
                        let satClose = (attraction["OpeningHours"]!["Saturday"]!!["To"] as String).componentsSeparatedByString(":")
                        let sunOpen  = (attraction["OpeningHours"]!["Sunday"]!!["From"] as String).componentsSeparatedByString(":")
                        let sunClose = (attraction["OpeningHours"]!["Sunday"]!!["To"] as String).componentsSeparatedByString(":")
                        XCTAssertEqual(monOpen, ["1","2","3"], "")
                        XCTAssertEqual(openDescription, "test")
                    }
                }
            }
        }
    }

    func testJSONSubscript() {
        measureBlock { () -> Void in
            for _ in 0 ..< 400 {
                autoreleasepool {
                    let attraction = json[0]
                    let openDescription:String = attraction["OpeningHours"]["OpeningHoursGenericExceptions"].string!
                    let monOpen:[String]    = attraction["OpeningHours"]["Monday"]["From"].string!.componentsSeparatedByString(":")
                    let monClose:[String]   = attraction["OpeningHours"]["Monday"]["To"].string!.componentsSeparatedByString(":")
                    let tueOpen:[String]    = attraction["OpeningHours"]["Tuesday"]["From"].string!.componentsSeparatedByString(":")
                    let tueClose:[String]   = attraction["OpeningHours"]["Tuesday"]["To"].string!.componentsSeparatedByString(":")
                    let wedOpen:[String]    = attraction["OpeningHours"]["Wednesday"]["From"].string!.componentsSeparatedByString(":")
                    let wedClose:[String]   = attraction["OpeningHours"]["Wednesday"]["To"].string!.componentsSeparatedByString(":")
                    let thuOpen:[String]    = attraction["OpeningHours"]["Thursday"]["From"].string!.componentsSeparatedByString(":")
                    let thuClose:[String]   = attraction["OpeningHours"]["Thursday"]["To"].string!.componentsSeparatedByString(":")
                    let friOpen:[String]    = attraction["OpeningHours"]["Friday"]["From"].string!.componentsSeparatedByString(":")
                    let friClose:[String]   = attraction["OpeningHours"]["Friday"]["To"].string!.componentsSeparatedByString(":")
                    let satOpen:[String]    = attraction["OpeningHours"]["Saturday"]["From"].string!.componentsSeparatedByString(":")
                    let satClose:[String]   = attraction["OpeningHours"]["Saturday"]["To"].string!.componentsSeparatedByString(":")
                    let sunOpen:[String]    = attraction["OpeningHours"]["Sunday"]["From"].string!.componentsSeparatedByString(":")
                    let sunClose:[String]   = attraction["OpeningHours"]["Sunday"]["To"].string!.componentsSeparatedByString(":")
                    XCTAssertEqual(monOpen, ["1","2","3"], "")
                }
            }
        }
    }

    func testNativeBinding() {
        measureBlock { () -> Void in
            for _ in 0 ..< 400 {
                autoreleasepool {
                    var monOpen:[String] = []
                    var monClose:[String] = []
                    var tueOpen:[String] = []
                    var tueClose:[String] = []
                    var wedOpen:[String] = []
                    var wedClose:[String] = []
                    var thuOpen:[String] = []
                    var thuClose:[String] = []
                    var friOpen:[String] = []
                    var friClose:[String] = []
                    var satOpen:[String] = []
                    var satClose:[String] = []
                    var sunOpen:[String] = []
                    var sunClose:[String] = []
                    var openDescription:String = ""

                    if let attractionsArray = orgJson as? NSArray{
                        if let attraction = attractionsArray[0] as? NSDictionary{
                            if let openHours = attraction["OpeningHours"] as? NSDictionary{
                                if let day = openHours["Monday"] as? NSDictionary{
                                    if let open = day["From"] as? String{
                                        monOpen = open.componentsSeparatedByString(":")
                                    }
                                    if let close = day["To"] as? String{
                                        monClose = close.componentsSeparatedByString(":")
                                    }
                                }
                                if let day = openHours["Tuesday"] as? NSDictionary{
                                    if let open = day["From"] as? String{
                                        tueOpen = open.componentsSeparatedByString(":")
                                    }
                                    if let close = day["To"] as? String{
                                        tueClose = close.componentsSeparatedByString(":")
                                    }
                                }
                                if let day = openHours["Wednesday"] as? NSDictionary{
                                    if let open = day["From"] as? String{
                                        wedOpen = open.componentsSeparatedByString(":")
                                    }
                                    if let close = day["To"] as? String{
                                        wedClose = close.componentsSeparatedByString(":")
                                    }
                                }
                                if let day = openHours["Thursday"] as? NSDictionary{
                                    if let open = day["From"] as? String{
                                        thuOpen = open.componentsSeparatedByString(":")
                                    }
                                    if let close = day["To"] as? String{
                                        thuClose = close.componentsSeparatedByString(":")
                                    }
                                }
                                if let day = openHours["Friday"] as? NSDictionary{
                                    if let open = day["From"] as? String{
                                        friOpen = open.componentsSeparatedByString(":")
                                    }
                                    if let close = day["To"] as? String{
                                        friClose = close.componentsSeparatedByString(":")
                                    }
                                }
                                if let day = openHours["Saturday"] as? NSDictionary{
                                    if let open = day["From"] as? String{
                                        satOpen = open.componentsSeparatedByString(":")
                                    }
                                    if let close = day["To"] as? String{
                                        satClose = close.componentsSeparatedByString(":")
                                    }
                                }
                                if let day = openHours["Sunday"] as? NSDictionary{
                                    if let open = day["From"] as? String{
                                        sunOpen = open.componentsSeparatedByString(":")
                                    }
                                    if let close = day["To"] as? String{
                                        sunClose = close.componentsSeparatedByString(":")
                                    }
                                }
                                if let desc = openHours["OpeningHoursGenericExceptions"] as? String{
                                    openDescription = desc
                                }
                            }
                        }
                    }
                    XCTAssertEqual(monOpen, ["1","2","3"], "")
                }
            }
        }
    }
    func testJSONBinding() {
        measureBlock { () -> Void in
            for _ in 0 ..< 400 {
                autoreleasepool {
                    var monOpen:[String] = []
                    var monClose:[String] = []
                    var tueOpen:[String] = []
                    var tueClose:[String] = []
                    var wedOpen:[String] = []
                    var wedClose:[String] = []
                    var thuOpen:[String] = []
                    var thuClose:[String] = []
                    var friOpen:[String] = []
                    var friClose:[String] = []
                    var satOpen:[String] = []
                    var satClose:[String] = []
                    var sunOpen:[String] = []
                    var sunClose:[String] = []
                    var openDescription:String = ""

                    let openHours = json[0]["OpeningHours"]
                    var day:JSON

                    day = openHours["Monday"]
                    if let open = day["From"].string {
                        monOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"].string {
                        monClose
                            = close.componentsSeparatedByString(":")
                    }
                    day = openHours["Tuesday"]
                    if let open = day["From"].string {
                        tueOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"].string {
                        tueClose = close.componentsSeparatedByString(":")
                    }
                    day = openHours["WednesDay"]
                    if let open = day["From"].string {
                        wedOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"].string {
                        wedClose = close.componentsSeparatedByString(":")
                    }
                    day = openHours["Thursday"]
                    if let open = day["From"].string {
                        thuOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"].string {
                        thuClose = close.componentsSeparatedByString(":")
                    }
                    day = openHours["Friday"]
                    if let open = day["From"].string {
                        friOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"].string {
                        friClose = close.componentsSeparatedByString(":")
                    }
                    day = openHours["Saturday"]
                    if let open = day["From"].string {
                        satOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"].string {
                        satClose = close.componentsSeparatedByString(":")
                    }
                    day = openHours["Sunday"]
                    if let open = day["From"].string {
                        sunOpen = open.componentsSeparatedByString(":")
                    }
                    if let close = day["To"].string {
                        sunClose = close.componentsSeparatedByString(":")
                    }
                    XCTAssertEqual(monOpen, ["1","2","3"], "")
                }
            }
        }
    }

}

Выходы:

<unknown>:0: Test Case '-[JSONTestTests.JSONTestTests testJSONBinding]' measured [Time, seconds] average: 0.804, relative standard deviation: 5.592%, values: [0.835687, 0.814827, 0.819685, 0.841900, 0.764961, 0.845202, 0.691442, 0.779255, 0.818213, 0.830698], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
<unknown>:0: Test Case '-[JSONTestTests.JSONTestTests testJSONSubscript]' measured [Time, seconds] average: 4.247, relative standard deviation: 3.496%, values: [4.019640, 4.004123, 4.146146, 4.194535, 4.487171, 4.300971, 4.310613, 4.408405, 4.318354, 4.279362], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
<unknown>:0: Test Case '-[JSONTestTests.JSONTestTests testNativeBinding]' measured [Time, seconds] average: 0.223, relative standard deviation: 2.773%, values: [0.221099, 0.227395, 0.218860, 0.225989, 0.227128, 0.222370, 0.229956, 0.214535, 0.210818, 0.229868], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
<unknown>:0: Test Case '-[JSONTestTests.JSONTestTests testNativeSubscript]' measured [Time, seconds] average: 0.362, relative standard deviation: 17.528%, values: [0.346285, 0.316185, 0.333650, 0.339416, 0.330243, 0.354034, 0.378730, 0.269519, 0.486904, 0.467607], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
  • Ваш SwiftyJSON: 4.247
  • Ваш родной: 0.223
  • My SwiftyJSON: 0.804
  • My Native: 0.362

Кстати, если бы я был вами, я бы сделал что-то вроде:

if let hours = orgJson[0]?["OpeningHours"] as? NSDictionary {
    let openDescription = hours["OpeningHoursGenericExceptions"] as? String ?? ""
    let monOpen  = hours["Monday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let monClose = hours["Monday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let tueOpen  = hours["Tuesday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let tueClose = hours["Tuesday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let wedOpen  = hours["Wednesday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let wedClose = hours["Wednesday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let thuOpen  = hours["Thursday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let thuClose = hours["Thursday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let friOpen  = hours["Friday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let friClose = hours["Friday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let satOpen  = hours["Saturday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let satClose = hours["Saturday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let sunOpen  = hours["Sunday"]?["From"]??.componentsSeparatedByString?(":") as? [String] ?? []
    let sunClose = hours["Sunday"]?["To"]??.componentsSeparatedByString?(":") as? [String] ?? []

    // ...
}

Это достаточно быстро, безопасно, не так сложно.


Почему SwiftyJON медленный?

В subscript доступ, SwiftyJSON делает:

  • проверить ключ String
  • с индексом self[key:sub]
  • проверить базовый объект NSDictionary
  • под NSDictionary с указанным ключом
  • построить объект JSON с результатом
  • return

Может быть, компилятор оптимизирует некоторые шаги, но "медленнее, чем native" несколько неизбежно:)