2015-03-25 20 views
13

Mam poważne problemy z wydajnością analizowania JSON z SwiftyJson z naszego API i zapełnianie w podstawowych danych.Problemy z wydajnością SwiftyJSON

Dane są pobierane z Alamofire, która działa ładnie, ale parsowanie json z SwiftyJson jest boleśnie powolne. Aby sprawdzić, czy biblioteka rzeczywiście była problemem, przeprogramowałem json w jednym z wielu miejsc, w których dane są analizowane. W poniższym kodzie analizuję godziny otwarcia jednej z około 400 atrakcji turystycznych.

Zobacz różnicę w tych screenów, 7,7 s do 185 MS:

enter image description here

enter image description here

Swifty sposób:

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(":") 

natywny sposób:

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 
       } 
      } 
     } 
    } 

To jest tylko część danych, które są analizowane, więc wydajność jest bardzo widoczna w aplikacji.

Sądzę, że pytanie brzmi: czy użyłem SwiftyJSON nieprawidłowo, czy można tego oczekiwać?

Odpowiedz

20

Po pierwsze, twój "tradycyjny sposób" nie jest równoważny "Swift".

Dostęp do wersji SwiftyJSON ma 45 subscript, ale dostęp natywny ma tylko 23 subscript.

Aby "natywny sposób" odpowiednik, powinno być coś takiego:

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(":") 
// ... 

lub "Swifty sposób" powinno być:

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(":") 
} 
// ... 

Zresztą Tak, SwiftyJSON jest powolny. Załóżmy miara:

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"], "") 
       } 
      } 
     } 
    } 

} 

Wyjścia:

<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 
  • Twój SwiftyJSON: 4,247
  • swój ojczysty: 0,223
  • Moja SwiftyJSON: 0,804
  • mojego rodzinnego: 0,362

Nawiasem mówiąc, gdybym był tobą, chciałbym zrobić coś takiego:

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] ?? [] 

    // ... 
} 

To dość szybki, bezpieczny, nie tak skomplikowane.


Dlaczego SwiftyJSON jest wolny?

Na subscript dostępu SwiftyJSON robi:

  1. check kluczem jest String
  2. indeks ponownie self[key:sub]
  3. czeku bazowy obiekt jest NSDictionary
  4. indeks bazowego NSDictionary pomocą dostarczonego klucza
  5. skonstruuj obiekt JSON z wynikiem
  6. return

Może kompilator optymalizuje pewne kroki, ale „wolniejszy niż natywny” jest nieco nieuniknione :)

+0

Dziękuję za bardzo dokładny odpowiedź! I z benchmarkami! Ponieważ jestem bardzo nowy w Swift (przypuszczam, że wiele osób jest :) wiele kodowania to first-try-trial-and-error i zrobiłem parsowanie JSON zgodnie z sugestią w pliku SwiftyJSON README. Patrząc na różne przykłady metod analizowania, nie żałuję, że szybko wycofałem się z mojego projektu, złożoność "natywnego" analizowania nie stanowi problemu i dodaje różnicę wydajności, której nie widzę jako opcji. – Simpa

+0

Mam na myśli bez obrazy dla twórcy Swifty, myślę, że może on mieć wielkie zastosowanie dla wielu programistów. Ale "szybka analiza JSONA", która tylko daje wyniki przy użyciu sprytności, dała mi wrażenie, że jest to właściwa droga. Ale może to nie jest dla wszystkich? :) – Simpa

+0

@Simpa w świecie Swift, wielu hitu w Google nie można ufać. Można odnieść wrażenie, że Objective-C nie żyje, Cocoapods jest akceptowany jako standard, itp. Itd. ... w rzeczywistości, wydaje mi się, że wiele z tych zjawisk jest problematycznych. Czy jednak SwiftyJSON jest zbyt wolny w użyciu? To kolejne pytanie, ale gdybyś uzyskał te wyniki pierwotnie, mógłbyś tego nie zauważyć. –

Powiązane problemy