[OSM-dev-fr] [info - LibOsm] Etat d'avancement de "ma" libosm (C++)
Sébastien Dinot
sebastien.dinot at free.fr
Jeu 23 Sep 23:45:06 BST 2010
Bonsoir,
Marc SIBERT a écrit :
> Après une semaine de parsing, j'ai analysé et stocké dans une BDD un
> planet complet et depuis je joue des diffs pour arriver à un répliqua
> local à J-1 (voir moins car je parse aussi les hourly-diff et pourquoi
> pas les minutes, mais cela n'est pas encore testé ni automatisé). Cela
> tourne sur un "simple" PC de bureau Core i5 / 3.2 GHz avec 1 DD
> standard de 1 To (le goulot d'étranglement, c'est le DD).
J'ai effectué des tests chez moi avec le fichier couvrant la France
(19 Go décompressé). Par curiosité, pourrais-tu me dire combien de temps
il faut à ta machine pour le traiter ?
De mon côté, je ne dispose que d'un Athlon 64 bits à 2,4 GHz mais je
crée la base de données SQLite sur un disque différent de celui sur
lequel je lis le fichier XML (j'ai vérifié, cela a bien un impact sur
les performances). Mon système est une distribution Debian GNU/Linux
64 bits (Sid). J'ai compilé ton outil avec GCC 4.4.5, en activant
l'optimisation (-O3), en lui demandant d'appliquer le standard C++0X
(-std=c++0x) au lieu d'ANSI et de lier dynamiquement les bibliothèques
SQLite (3.7.2) et Spatialite (2.4.0).
J'ai ensuite lancé l'outil avec un cache de 1 million de pages :
./parser -c 1000000 $OUT/france.db < $IN/france.osm > parser.log
Le processus a rapidement englouti 1,3 Go de RAM et a injecté le fichier
en base en 2 heures et 13 minutes.
En optant pour une édition de liens statique, je gagne 1,5 % de
performance.
Par contre, en ne convertissant pas les dates en structures de données
mais en les manipulant de bout en bout comme des chaînes (puisque, in
fine, SQLite ne dispose pas de type approprié et stocke la date sous
forme d'une chaîne au format ISO8601, c'est à dire sous la forme
proposée dans le fichier XML), je gagne 3 % de temps d'exécution.
J'ai tenté différents types d'optimisation parmi lesquelles une
regénération des requêtes préparées en cours de traitement afin de
laisser à l'optimiseur l'opportunité de réévaluer sa stratégie en
fonction du contenu de la base qui évolue massivement. Je n'ai obtenu
aucun résultat intéressant. Au contraire, l'opération s'avère plutôt
contre-productive puisque j'en profite pour lancer une requête de type
ANALYZE pour donner du grain à moudre à l'optimiseur. Une preuve que
SQLite est bien conçue même si ce mini-SGBDR reste frugal en termes de
fonctions, de types de données et de possibilités de manipulation des
schémas.
Il y a par contre deux détails qui me chagrinent et sur lesquels je n'ai
pas encore eu le temps de me pencher sérieusement :
- J'ai vu que pour l'insertion des utilisateurs, tu effectuais une
requête « INSERT OR IGNORE ». Cette stratégie me laisse perplexe. Vu
qu'OSM ne compte pas plus de 300 000 contributeurs, il est possible et
il serait sans doute plus performant de stocker en mémoire dans une
table de hachage (unordered_map) dont la clé serait le nom de
l'utilisateur la liste des utilisateurs déjà insérés en base. Pour
chaque élément traité, le programme vérifierait alors si l'auteur est
déjà référencé dans la table de hachage. Le cas échéant, l'insertion
serait inutile ; une requête et du temps seraient économisés. Vu que
ces requêtes se comptent en millions, voire milliards, au final, ça
doit compter.
- J'ai demandé un profiling de ton outil à Valgrind (module Callgrind et
interprétation des résultats sous KCachegrind) et j'ai constaté que
10 % du temps d'exécution est du au transtypage (dynamic_cast<>). Si
ces transtypages sont sans doute élégants aux yeux d'un concepteur,
ils le sont moins aux yeux pragmatiques d'une personne en quête de
performance.
Ces réflexions t'inspirent-elles ? Peut-être que je n'enfonce que des
portes ouvertes et que tu t'es déjà posé ces questions. Dans ce cas,
quelles ont été tes conclusions ?
> Ce code est normalement portable sur d'autres plateforme (ouverte par
> exemple).
Avec GCC, je préconise les options de compilation -Wall et -Wextra.
À part cela, tu as oublié de prendre en compte (ou à minima, tu as
oublié de pousser dans ton référentiel public) les fichiers sources que
je t'avais signalés comme non référencés dans ton Makefile ainsi que le
renommage des fichiers source de ta classe d'encapsulation de
Spatialite.
De mon côté, j'aime bien que :
- le calcul des dépendances soit exhaustif afin que toute modification
de code soit prise en compte sans qu'une commande « make clean » ne
soit nécessaire ;
- les produits intermédiaires de compilation ne polluent pas les
répertoires de source.
Je joins à ce message un Makefile qui satisfait ces exigences. Je l'ai
repris de l'un de mes projets personnels et je l'ai adapté pour compiler
ton outil. Je l'ai placé dans un sous-répertoire (nommé « build » mais
cela n'entre pas en ligne de compte). Lorsque je lance la commande
« make all », tous les produits de compilation, intermédiaires ou
finaux, sont générés dans ce sous-répertoire.
Par défaut, l'exécutable est compilé en incluant les informations de
débogage et sans optimisation. Pour modifier cela, il suffit d'invoquer
la commande suivante :
$ DEBUG=no OPTIM=yes make all
Tu peux aussi, avec GCC, choisir de produire un code optimisé et
débogable. Pour ce faire, tu dois lancer la commande :
$ OPTIM=yes make all
Tu peux à ta guise ignorer ce Makefile (il en faut plus pour me vexer),
y piocher le code qui t'intéresse ou le réutiliser tel quel dans ton
projet.
Petite remarque, pour éviter la corvée du report dans le Makefile de
tout nouveau fichier source que tu ajoutes à ton projet, j'ai opté pour
la construction automatique de la liste des fichiers sources :
PARSER_SRC = $(shell cd .. ; ls -1 *.cpp ) \
$(shell cd ../xml ; ls -1 *.cpp ) \
$(shell cd ../osm ; ls -1 *.cpp)
Si cela te chagrine, tu peux remplacer les instructions ci-dessus par
une liste plus classique :
PARSER_SRC = changeset.cpp element.cpp ...
Car l'inclusion systématique, c'est pratique mais ça peut aussi devenir
pénible. ;)
Sébastien
--
Sébastien Dinot, sebastien.dinot at free.fr
http://sebastien.dinot.free.fr/
Ne goûtez pas au logiciel libre, vous ne pourriez plus vous en passer !
-------------- section suivante --------------
# OPTIONS=-g -DDEBUG
OPTIONS=-O3 -DNDEBUG
CC=gcc
CXX=g++
INC=-I/usr/include -I.
CFLAGS=-Wall -Wextra -std=c99 -pedantic $(INC) $(OPTIONS)
CXXFLAGS=-Wall -Wextra -std=c++0x -pedantic $(INC) $(OPTIONS)
LDFLAGS=-L/lib -L/usr/lib
EXEC=parser
LIBS=-ldl -lstdc++ -lpthread -lgeos -lgeos_c -lproj -lexpat -lsqlite3 -lspatialite
SRC_OSM=changeset.cpp element.cpp member.cpp node.cpp point.cpp relation.cpp \
top.cpp way.cpp
SRC_XML=parserbounds.cpp parser.cpp parsernd.cpp parserosmchange.cpp \
parserplanet.cpp parsertag.cpp parserchangeset.cpp parsermember.cpp \
parsernode.cpp parserosm.cpp parserrelation.cpp parserway.cpp \
parsercreate.cpp parsermodify.cpp parserdelete.cpp
SRC_MAIN=baseosm.cpp basesqlite3.cpp main.cpp wrapper_spatialite.cpp
OBJ_OSM=$(SRC_OSM:%.cpp=osm/%.o)
OBJ_XML=$(SRC_XML:%.cpp=xml/%.o)
OBJ_MAIN=$(SRC_MAIN:%.cpp=%.o)
OBJ=$(OBJ_OSM) $(OBJ_XML) $(OBJ_MAIN)
all: $(EXEC)
$(EXEC): $(OBJ)
$(CXX) -o $@ $(OBJ) $(LDFLAGS) $(LIBS)
%.o: %.cpp
$(CXX) -c $< -o $@ $(CXXFLAGS)
%.o: %.c
$(CC) -c $< -o $@ $(CFLAGS)
clean:
rm -rvf $(OBJ)
rm -rvf $(EXEC)
run: $(EXEC)
rm -f test.db
bzcat andorra.osm.bz2 | ./$(EXEC) test.db > $(EXEC).log
More information about the dev-fr
mailing list