Benchmark spatial KNN : de SQL à Rust | de Padron Carlos | août 2023

[ad_1]
Dans mon précédent articlej’ai présenté comment résoudre le problème du voisin le plus proche en utilisant SQL et les bibliothèques Python Geopandas et Scikit-learn.
Je passerai en revue les solutions précédentes et en présenterai de nouvelles en utilisant cette fois d’autres bibliothèques et langages. À la fin, je comparerai ces solutions en termes de rapidité et de complexité.
Les solutions à présenter sont :
- SQL : utilisation des jointures distinctes et latérales sur PostgreSQL
- SQL : utiliser Google Big Query
- Python : utiliser Geopandas, Shapely, PyGeos et Scikit-Learn
- Apache Sedona : utiliser SQL
- Kotlin : utiliser JTS
- Scala : utiliser JTS
- Rust : utiliser Geo et Rstar
Ma motivation est simple : les analystes de données spatiales passent des heures à résoudre ces problèmes, donc savoir à quoi s’attendre des différents langages, bibliothèques et technologies vous aide à planifier vos projets. D’un côté plus personnel, j’ai utilisé ce projet pour explorer d’autres langages tels que Scala, Kotlin et Rust.
Toutes les solutions utiliseront les mêmes données et renverront le même résultat. Les ensembles de données sont sur ce lien. Ils ont été rassemblés en 2021.
L’objectif est de trouver le centroïde de code postal le plus proche dans l’ensemble de données Code Point de chaque UPRN dans l’ensemble de données Open UPRN. Par souci de simplicité, les deux ensembles de données étaient limités au district de Vale of White Horse trouvé dans l’ensemble de données Boundary Line.
Dans les cas où l’UPRN est à égale distance de deux ou plusieurs centroïdes de code postal, toutes les solutions mises en œuvre renvoient le premier code postal de l’ensemble classé par ordre alphabétique et croissant.
Il y a 3 972 centroïdes de code postal et 82 464 UPRNS dans les ensembles de données filtrés. Cela donne 327 547 008 combinaisons.
J’ai utilisé deux approches:
- Tous contre tous (force brute)
- Trier l’arbre récursif des tuiles (STRT)
Dans l’approche All vs All, chaque UPRN est testé par rapport à chaque centroïde de code postal et le plus proche est choisi. Il s’agit d’une approche inefficace mais simple. Tester toutes les combinaisons possibles prend du temps. Comme je l’ai dit, il existe 327 547 008 combinaisons.
Un arbre récursif de tuiles de tri (Wikipedia, 2023) pré-classe les géométries en sous-ensembles d’éléments proches, de sorte que lorsque vous avez besoin de trouver l’élément le plus proche d’une coordonnée particulière, vous n’avez pas besoin de tester tous les éléments de l’ensemble de données, seulement ceux du sous-ensemble auquel appartient la coordonnée de test.
Par exemple, il existe dix géométries, et l’arbre STR les classe dans ce cas en deux sous-ensembles. Si vous souhaitez trouver la géométrie la plus proche d’un certain point, vous devez vérifier quel sous-ensemble est le plus proche et tester chaque élément de l’ensemble. Cela réduit le nombre de combinaisons à tester.
Le code de toutes les solutions est ici dépôt. Le bloc-notes contient les solutions SQL, Python, Apache Sedona et des comparaisons de vitesse. Les solutions Scala, Kotlin et Rust se trouvent dans leurs propres sous-dossiers.
Le tableau 1 montre la comparaison entre le temps d’exécution des différentes implémentations.
Scikit-Learn, en position 0, est clairement le gagnant. Cependant, cela ne fonctionne que si les données sont des points. Les polygones ne sont pas autorisés comme entrées (scikit-learn, sd b).
Ce n’est pas une surprise que Rust vienne ensuite. Il est connu pour être rapide (Rust Team, 2018). Cette solution est rapide ; il peut gérer d’autres géométries, telles que des lignes et des polygones. J’ai aimé écrire sur Rust, mais le concept d’emprunt et de transfert de propriété m’a fait trébucher à plusieurs reprises.
L’une des choses que j’ai le plus appréciées dans Rust (également très présente dans Scala et Kotlin) est que vous pouvez parcourir facilement des collections telles que des listes sans boucles for. Il nettoie le code et est plus rapide qu’une boucle normale (doc.rust-lang.org, sd ; Scala Documentation, sd ; Aide Kotlin, sd a).
Étonnamment, de nombreuses solutions Python s’exécutent très rapidement pour un langage réputé pour être lent (positions 0, 2 et 3). Les équipes de développement derrière ces bibliothèques (Scikit-Learn, Geopandas et Pygeos) se concentrent grandement sur les optimisations. Scikit-Learn dispose d’une documentation pour améliorer la vitesse (scikit-learn, sd b), tandis que GeoPandas, la version que j’ai utilisée, utilise des opérations vectorisées prototypées par PyGeos puis adoptées par Shapely 2.0 (geopandas.org, sd).
Comme vous pouvez le voir sur le code, je viens d’exécuter les fonctions, tandis que dans les autres langages, je devais construire mes solutions. Si vous avez besoin de créer quelque chose par vous-même, vous remarquerez que Python n’est pas rapide par nature et nécessite des connaissances très spécifiques sur la façon d’accélérer les processus. Très probablement, vous finirez par jouer avec Cython, Numba, etc., et pour moi, ces options semblent plus ésotériques que simplement apprendre à utiliser un langage plus rapide.
En position 4, nous avons Google Big Query (GBQ). GBQ est assez rapide et facile à utiliser (c’est juste du SQL), mais il y a quelques mises en garde :
- J’ai dû réécrire mes commandes SQL car « distinct on » et « jointures latérales » n’étaient pas prises en charge (Google Cloud, sd b).
- GBQ ne prend pas en charge les systèmes de coordonnées projetées (Google Cloud, sd a), ce qui m’a obligé à travailler sur EPSG 4326 au lieu de EPSG 27700. Cela a provoqué une divergence dans certaines sorties.
Notez qu’il y a une autre entrée pour GBQ presque à la fin. GBQ fonctionne rapidement car il divise le problème en travailleurs parallèles (Google Cloud, sd c). Mais si l’on additionne le temps que chacun met pour faire le travail, cela se terminerait en position 16. Cependant, pour l’utilisateur final, le travail est fait rapidement, c’est ce qui compte.
Aux positions 5 et 6, nous avons les langages Java Virtual Machine (JVM), Kotlin et Scala, utilisant un arbre STR de Java Topological Suite (JTS). Je les ai trouvés plus faciles à écrire et à déboguer que Rust et généralement plus rapides que Python. Scala est assez présent dans Apache Spark (Apache Spark, 2019) et d’autres excellents outils (index.scala-lang.org, sd), et Kotlin est souvent utilisé pour le développement Android (Kotlin Help, sd b).
Comme indiqué précédemment, Scala, Kotlin et Rust disposent d’un ensemble très riche de collections et de méthodes que je recommande à tout le monde d’explorer.
En position 7, nous avons obtenu Shapely en utilisant un arbre STR. Cette bibliothèque a adopté certaines des optimisations de PyGeos (shapely.readthedocs.io, sd), mais elle n’est toujours pas aussi rapide.
Jusqu’à présent, toutes les solutions sont hautement optimisées ou utilisent des arbres STR. Cela montre que les arbres récursifs Sort Tiles sont fondamentaux pour une solution KNN efficace.
Nous avons Rust, Scala et Kotlin aux trois endroits suivants en utilisant une approche All-vs-All. Cette approche par force brute démontre à quelle vitesse ces langages peuvent s’exécuter même lors de l’exécution d’une solution inefficace.
Aux positions 11, 12, 14 et 15, on retrouve SQL sur PostgreSQL. Les performances SQL dépendent de nombreux facteurs (Documentation PostgreSQL, 2023), mais je me concentrerai sur les paramètres et les requêtes.
Deux SQL différents ont été utilisés : une clause Lateral Join et une clause Distinct On. Le premier est plus rapide en fonction des résultats. Les deux requêtes ont été exécutées avec des allocations de mémoire différentes, mais n’ont pas eu beaucoup d’impact sur les résultats.
Il est injuste de juger PostgreSQL uniquement sur sa vitesse. Il peut faire ce que GeoPandas ne fera jamais : gérer plusieurs utilisateurs, plusieurs requêtes (simples ou complexes) et plusieurs tables à la fois. Ici, j’utilise simplement deux tables, mais dans le monde réel, je m’en tiendrai très probablement à PostgreSQL car la plupart des données sont déjà sur le serveur et je ne veux pas étouffer la mémoire de mon PC.
Aux positions 13 et 17, nous avons les implémentations All-vs-All de PyGeos et Shapely. Je ne m’attends pas à ce que quiconque utilise une approche par force brute pour résoudre un problème KNN. Celles-ci ont été réalisées pour démontrer que ces solutions sont très lentes.
En fin de compte, nous avons PySpark utilisant l’extension Apache Sedona et l’API SQL. Le résultat de la vitesse pourrait être imputé à plusieurs facteurs :
- Mon incompétence à configurer correctement Apache Sedona.
- Il est destiné à fonctionner dans un environnement distribué, résolvant des problèmes liés à de grands ensembles de données (sedona.apache.org, sd), et non dans un ordinateur portable exécutant un petit benchmark.
Je suis convaincu qu’Apache Sedona brillerait dans un environnement approprié et en utilisant un vaste ensemble de données que je ne peux pas simplement charger sur Geopandas.
Différentes bibliothèques et langages ont été testés pour résoudre le même problème Spatial KNN. Chaque bibliothèque et chaque langue a son objectif. Python a accès à d’excellentes bibliothèques optimisées et faciles à utiliser. Scala, Kotlin et Rust sont des langages rapides et ont accès à de nombreuses bibliothèques, mais vous avez besoin de plus de connaissances pour obtenir le même résultat. SQL est très simple à utiliser, vous permettant d’effectuer des requêtes très complexes, mais ne vous attendez pas à une exécution rapide sauf si vous utilisez Google Big Query.
Tous les codes et résultats sont stockés dans ce dépôt.
Je suivrais les règles suivantes la prochaine fois que je devrai choisir une bibliothèque ou un langage pour un problème Spatial KNN :
- GéoPandas
– les données correspondent à la mémoire
– vous n’envisagez pas de faire des requêtes complexes - SKApprendre
– les conditions précédentes
– les données ne sont que des points
– GeoPandas n’était pas assez rapide - PostgreSQL
– les données ne rentrent pas dans la mémoire
– les données sont déjà dans la base de données et leur lecture dans la mémoire prendra du temps
– vous envisagez d’effectuer des requêtes complexes - BigQuery
– les conditions du point PostgreSQL
– vous voulez un résultat très rapide, ou vos données sont massives
– vous êtes heureux d’utiliser des coordonnées géographiques
– vous êtes également heureux de réécrire votre requête pour l’adapter aux normes BigQuery SQL - PyGeos : vous ne souhaitez pas utiliser GeoPandas, vous souhaitez accélérer les choses et rester dans Python
- Shapely : vous ne souhaitez pas utiliser GeoPandas ou PyGeos et vous souhaitez rester dans Python
- Scala/Kotlin
– les données correspondent à la mémoire
– vous devez écrire un programme complexe dont KNN n’est qu’une partie, et vous avez besoin que l’ensemble du programme s’exécute rapidement - Rouiller
– les données correspondent à la mémoire
– vous devez écrire un programme complexe dont KNN n’est qu’une partie, et vous avez besoin que l’ensemble du programme s’exécute très rapidement
– vous êtes heureux d’avoir affaire à un univers réduit de bibliothèques - Apache Sedona
– vos données sont massives et doivent être réparties sur différents clusters
Apache Spark (2019). Apache SparkTM — Moteur d’analyse unifié pour le Big Data. (en ligne) Apache.org. Disponible à: https://spark.apache.org/.
doc.rust-lang.org. (sd). Comparaison des performances : boucles et itérateurs – Le langage de programmation Rust. (en ligne) Disponible sur : (Consulté le 1er août 2023).
géopandas.org. (sd). Feuille de route — Documentation GeoPandas 0.13.2+0.gd5add48.dirty. (en ligne) Disponible sur : (Consulté le 3 août 2023).
Google Cloud. (sd)a. Types de données | BigQuery. (en ligne) Disponible sur : (Consulté le 3 août 2023).
Google Cloud. (sd)b. Syntaxe de requête | BigQuery. (en ligne) Disponible sur : https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax.
Google Cloud. (sd)c. Comprendre les machines à sous | BigQuery. (en ligne) Disponible sur : https://cloud.google.com/bigquery/docs/slots.
index.scala-lang.org. (sd). Scala génial. (en ligne) Disponible sur : (Consulté le 3 août 2023).
Aide Kotlin. (sd a). Aperçu des opérations de collecte | Kotlin. (en ligne) Disponible sur : (Consulté le 1er août 2023).
Aide Kotlin. (sd b). Kotlin pour Android — Aide | Kotlin. (en ligne) Disponible sur : https://kotlinlang.org/docs/android-overview.html.
osdatahub.os.uk. (2021). Centre de données du système d’exploitation. (en ligne) Disponible sur : https://osdatahub.os.uk/.
Documentation PostgreSQL. (2023). Chapitre 14. Conseils en matière de performances. (en ligne) Disponible sur : (Consulté le 5 août 2023).
Équipe Rust (2018). Langage de programmation Rust. (en ligne) Rust-lang.org. Disponible à: https://www.rust-lang.org/.
Documentation Scala. (sd). Méthodes de recouvrement. (en ligne) Disponible sur : (Consulté le 1er août 2023).
scikit-learn. (sd a). Comment optimiser la vitesse. (en ligne) Disponible sur : (Consulté le 3 août 2023).
scikit-learn. (sd b). sklearn.neighbours.NearestNeighbours. (en ligne) Disponible sur : (Consulté le 5 août 2023).
sedona.apache.org. (sd). Apache SedonaTM. (en ligne) Disponible sur : (Consulté le 5 août 2023).
shapely.readthedocs.io. (sd). Migration depuis PyGEOS — Documentation Shapely 2.0.1. (en ligne) Disponible sur : (Consulté le 3 août 2023).
[ad_2]
Source link