JSON : les performances entre SwiftyJSON et Freddy

Hier nous vous parlions d’une nouvelle bibliothèque pour parser du JSON en Swift : Freddy. Nous l’avons comparez à SwiftyJSON et la bibliothèque native NSJSONSerialization d’un point de vue fonctionnel, mais il nous restait à comparer les performances entre ces 3 librairies. En effet, Freddy se définit comme plus rapide pour parser de gros fichier. C’est ce que nous allons vérifier maintenant.

Condition du test

Nous allons utiliser Xcode et faire tourner les tests de performances dans des XCTestCase en utilisant le méthode fournit measureBlock. Le projet qui sert de référence est sur github. Pour l’utiliser il vous faut cloner le repo, et l’initialiser avec Cocoapod :

$ git clone git@github.com:swifttuto/JSONComparator.git
$ cd JSONComparator
$ pod install
$ open JSONComparator.xcworkspace

Pour les tests il nous faut tout d’abords les donnée d’entrées. On va générer un JSON plus ou moins gros en utilisant cette méthode dans notre test :

class JSONComparatorTests: XCTestCase {
  static let iterationCount = 100000
  static var data: NSData?  = nil

  override class func setUp() {
    var persons: [[String: AnyObject]] = []

    for i in 0 ..< iterationCount {
      let person: [String: AnyObject] = [
        "firstname": "Firstname \(i)",
        "lastname": "Lastname \(i)",
        "age": i
      ]

      persons.append(person)
    }

    do {
      data = try NSJSONSerialization.dataWithJSONObject(persons, options: .PrettyPrinted)
    }
    catch {}
  }
}

La méthode de classe setUp est appelé une seule fois lors des tests et elle permet de générer un JSON sous forme de NSData prêt à être parser par les différentes bibliothèques. Le JSON générer est simplement une liste de personne contenant 3 attributs chacun (un nom, un prénom et un age). Nous pouvons faire varier le nombre d’entré dans le JSON en changeant la variable iterationCount.

Les tests

Voici les méthodes qui vont nous servir de comparaison :

func testNativePerformance() {
  guard let data = JSONComparatorTests.data else {
    return XCTFail("Data should not be empty")
  }

  self.measureBlock {
    do {
      let _ = try NSJSONSerialization.JSONObjectWithData(data, options: [])
    }
    catch let error {
      XCTFail("\(error)")
    }
  }
}

func testSwiftyJSONPerformance() {
  guard let data = JSONComparatorTests.data else {
    return XCTFail("Data should not be empty")
  }

  self.measureBlock {
    let _ = SwiftyJSON.JSON(data: data)
  }
}

func testFreddyPerformance() {
  guard let data = JSONComparatorTests.data else {
    return XCTFail("Data should not be empty")
  }

  self.measureBlock {
    do {
      let _ = try Freddy.JSON(data: data)
    } catch {
      XCTFail("\(error)")
    }
  }
}

Pour chacun des librairies on ne fait que parser le JSON générer. Il ne devrait pas y avoir beaucoup de différence entre SwiftyJSON et NSJSONSerialization car le premier utilise le deuxième comme parser.

Veuillez aussi noter que les tests sont exécutés sur le simulateur en mode Release.

Les résultats

Le parsing

Voici les résultats d’un parsing avec un JSON de 100 entrées :

  • Freddy : 0.271 secondes
  • NSJSONSerialization : 0.259 secondes
  • SwiftyJSON : 0.259 secondes

Comme on pouvait s’y attendre SwiftyJSON et NSJSONSerialization sont équivalent en performance, et Freddy accuse un petit coup de retard (1,05 fois plus lent).

Essayons maintenant avec 10000 entrées :

  • Freddy : 0.764 secondes
  • NSJSONSerialization : 0.556 secondes
  • SwiftyJSON : 0.554 secondes

Les différences se creusent un peu, maintenant Freddy est 1,37 fois plus lent que NSJSONSerialization.

Et avec 1 000 000 d’entrées ?

  • Freddy : 49.539 secondes
  • NSJSONSerialization : 27.506 secondes
  • SwiftyJSON : 27.991 secondes

Encore pire ! Freddy est plus de 1,8 fois plus lent que NSJSONSerialization.

Le downcasting

Ce que nous avions oublié de comparer la première fois c’est le downcasting. En effet les JSON retournés sont des AnyObject, c-à-d que l’on ne connait pas leur type. Afin de récupérer un String, un Int, un Dictionary ou n’importe quel autre type il faut downcaster le AnyObject vers le type correspondant. Cette opération à un coût non négligeable et sur des ensemble de données importants cela se ressent beaucoup.

Je vous renvoie vers le benchmark fournit par Freddy qui montre qu’il devance et largement les NSJSONSerialization et SwiftyJSON.

Conclusion

Pour conclure Freddy apporte bien de gros gain de performance sur la partie downcasting qui est la partie la plus chronophage lorsque l’on manipule du JSON. De plus Freddy est une bibliothèque très propre, très bien conçu qui peut être utiliser sans hésitation. Sur des petits ensembles de données vous pouvez utiliser n’importe quel librairie car il y aura peu d’impact sur vos performance, en revanche sur de gros ensemble utilisez Freddy !

Si vous avez des remarques, amélioration ou autre, n’hésitez pas à laisser des commentaires.

À bientôt

1 Etoile2 Etoiles3 Etoiles4 Etoiles5 Etoiles (1 votes, average: 5,00 out of 5)
Loading...

Edit :

Sur les précisions de John Gallagher, ce post a été mis à jour en faisant les tests en mode Release plutôt qu’en mode Debug. De plus comme il l’explique dans son commentaire le parsing du JSON n’est qu’une petite partie de son utilisation. Accéder aux éléments de celui ci est une autre grosse partie et il est très gourmand en ressource.

Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInPin on PinterestShare on RedditDigg this

2 commentaires

Time limit is exhausted. Please reload CAPTCHA.

  1. John Gallagher · 3 février 2016

    Disclaimers: I’m one of the authors of Freddy, and I don’t speak French so I’m hoping Google translate did an OK job with your post. 🙂

    There are a couple of issues with the benchmarks here. The biggest is that it appears your timing results were run with the project compiled in Debug mode. This severely slows down Freddy (since it’s pure Swift) but doesn’t slow down native/SwiftyJSON at all since they’re using the shipped, release-mode-compiled version of NSJSONSerialization. I ran your benchmarks in release mode, and on my machine, Freddy was a little worse than 2x slower than native. This is very close to our own measurements (https://github.com/bignerdranch/Freddy/wiki/JSONParser); it’s obviously not as fast as we’d like, but it’s much better than being 10x slower.

    The second issue is that pure parsing is only part of the story. One of the slowest parts of using NSJSONSerialization from Swift (as SwiftyJSON does) is the runtime downcast from AnyObject to determine which JSON type a particular value is. This benchmark does not include that step, but it’s a step that will be required in any real-world use of SwiftyJSON. If you have a JSON document where you only need to extract a small portion of it, SwiftJSON may very well still be faster, but the difference will close as you need to get more and more of the data out of the JSON.

    • yannickl · 3 février 2016

      No prob, Google is smart enough to translate from French to English into a intelligible text.

      Indeed I forgot the Debug vs Release mode. I didn’t know that NSJSONSerialiation was a release-mode-compiled version, so yes I understand the difference between both.

      Of course, I forgot this part in this post, I’m going to update it to point on your benchmark and explain this part. You have convinced me!

      Thank you for taking time to bring these clarifications! That’s great.