Chemin principal : Accueil > LaTeX > Bugs et debugs > Comment ma bibliographie a saturé la mémoire de (Xe)LaTeX

Autres chemins : (Aller directement au contenu de l'article)

Comment ma bibliographie a saturé la mémoire de (Xe)LaTeX

dimanche 23 octobre 2016, mise à jour jeudi 20 avril 2017, par Maïeul
Suivre la vie du site RSS 2.0 Forum

Cette semaine, compilant ma thèse pour une nouvelle relecture de la bibliographie, j’ai eu la désagréable surprise d’obtenir un message d’erreur libellé ainsi « TeX capacity exceeded, sorry [main memory size=5000000 ». J’ai passé un certain à trouver la source du bug. Je vous livre ici l’explication, d’une part parce que dans certaines circonstances d’autres que moi pourraient être amenés à obtenir le même bug, et d’autre part parce que l’explication de celui-ci s’avère intéressante pour comprendre les mécanismes sous-jacents à biblatex, à biber et à XeLaTeX.

Résumé du bug

Lançant ma série de compilations avec latexmk, j’obtiens, à la deuxième compilation XeLaTeX, le message suivant :

TeX capacity exceeded, sorry [main memory size=5000000]

D’habitude, ce type de message est provoqué par une boucle infinie, généralement liée à une faute de code. J’étais relativement sceptique sur l’existence d’une telle boucle, puisque je n’avais pas apporté beaucoup de changement de code depuis la dernière compilation complète effectuée un mois en amont.

Recherche de l’origine [1]

J’ai toutefois procédé à une méthode simple, mais généralement efficace pour trouver la source de ce type de bug : commenter la moitié du contenu LaTeX, compiler, voir si le bug se reproduit, si oui commenter la moitié du code restant, sinon inverser et faire un test sur la moitié qu’on n’avait pas testé, et recommencer, et ainsi de suite, jusqu’à cerner la ligne ou le bloc de code qui pose problème. Il s’agit d’une résolution classique par dichotomie, pas nécessairement la plus rapide, mais la plus simple à mettre en œuvre avec LaTeX.

Néanmoins, dans le cas qui m’occupait, une telle recherche s’est révélée infructueuse : le bug ne se produisait que si je compilais l’ensemble de ma thèse, et non pas l’une ou l’autre des moitiés.

Fort heureusement, je savais que j’avais réussi à compiler il y a quelques semaines, et qu’à l’époque j’avais posé un tag git. Grâce à la géniale fonction git bisect, j’ai pu trouver le commit ayant entraîné le bug, en testant par dichotomie l’ensemble de mes commits entre ma dernière compilation réussie et mon tout dernier commit.

Fort heureusement également, ce commit était relativement petit. J’avais remplacé :

\printbibliography[title={Manuscrits},subtype=ms,check=ms_principaux]

par

\begin{refcontext}[sorting=manuscripts]{}
\printbibliography[title={Manuscrits},subtype=ms,check=ms_principaux]
\end{refcontext
}

Ceci permet que ma bibliographie consacrée aux manuscrits soit triée selon un ordre spécifique, d’abord par ville, puis par bibliothèque, par collection et par cote.

Recherche de la cause

La modification effectuée est donc la source du problème, mais pourquoi pose-t-elle problème ?

Fonctionnement interne de la bibliographie avec LaTeX + Biber

Un élément a éveillé ma curiosité : le bug se produisait AVANT même que la compilation n’ait eu le temps d’arriver à la ligne sur la bibliographie, comme je pouvais le constater à travers les messages défilant à l’écran.

Cependant, je touchais à un élément concernant la bibliographie. Or, voici comment fonctionne la bibliographie avec LaTeX + biblatex + Biber :

  • Lors de la première compilation, LaTeX lit le fichier .tex, repère les citations à l’intérieur, et écrit dans un fichier .bcf les clefs de citations utilisées ainsi que certaines informations relatives au style bibliographique, dont les informations sur le tri de la bibliographie.
  • Lors de la compilation Biber, ce dernier lit le fichier .bcf et le mettant en relation avec le fichier .bib, il trie et formate la bibliographie sous la forme d’une suite de commande LaTeX, qu’il écrit dans un fichier .bbl
  • Lors de la seconde compilation, LaTeX lit non seulement le fichier .tex, mais aussi le fichier .bbl, ce qui lui permet d’ajouter les références bibliographiques et de composer la ou les bibliographies finales.

Ceci est résumé dans le schéma ci-dessous.

Comment fonctionne la compilation de bibliographie dans LaTeX {PNG}

Puisque le bug n’arrive qu’à la seconde compilation LaTeX, et qu’il est lié à la bibliographie, le problème se situe probablement dans ma bibliographie. Problème : le commit qui introduit le bug n’a rien changé au fichier .bib. Le problème se situe donc plus vraisemblablement au moment de la production du fichier .bbl.

Analyse du fichier .bbl

J’ai alors constaté que la taille du fichier .bbl doublait approximativement entre la compilation avant mon commit et celle après mon commit, passant à 4 Mo dans le second cas. Grâce à un logiciel d’affichage de différence entre fichiers, je constate aisément l’origine de la modification : après mon commit, les entrées bibliographiques sont présentes deux fois dans mon fichier .bbl : une fois triées selon mon classement par défaut, anonymous+realauthor, l’autre fois triées selon le classement introduit par mon commit : manuscripts.

Ceci est du reste parfaitement logique, le tri bibliographique étant effectué par Biber et non par LaTeX, il est normal de trouver pour deux demandes de tri,deux listes bibliographiques dans le fichier .bbl, chaque liste commençant par \sortlist et finissant par \endsortlist. Le problème est qu’avec 1253 entrées bibliographiques, cela produit un fichier très lourd, qui, par conséquent, remplit considérablement la mémoire de LaTeX, et aboutit donc à mon TeX capacity exceeded, sorry [main memory size=5000000]

Résolution du problème

Une fois la cause trouvée, il reste à voir comment se sortir de la situation. Trois solutions s’offrent à nous.

Première solution : utiliser LuaLaTeX

TeX, et XeTeX, les moteurs derrière LaTeX et XeLaTeX fonctionnent selon un modèle à quantité de mémoire disponible fixe : lorsque la compilation est lancée, une certaine quantité de mémoire seulement est utilisable.

En revanche, LuaTeX, derrière LuaLaTeX, fonctionne selon un modèle, plus récent, d’allocation dynamique de la mémoire : la mémoire vive utilisée est étendue au fur et à mesure des besoins, le système d’exploitation veillant simplement à ce que LuaLaTeX n’empiète pas sur les autres programme en activité [2]. Conséquence : la quantité de mémoire utilisable par LuaLaTeX ne dépend que de mon ordinateur, et on peut espérer que ma bibliographie doublée n’en vienne quand même pas à saturer toute la mémoire vive et virtuelle [3] de mon ordinateur.

Problème : LuaTeX est un moteur différent de XeTeX, avec ses spécificités, et, étant dans la phase terminale de ma thèse, je ne préfère pas effectuer une migration avec toutes les vérifications, parfois tenues, que cela implique.

Deuxième solution : augmenter la mémoire de XeLaTeX

Puisque je souhaite continuer à utiliser XeLaTeX, deux solutions s’offrent à moi : résoudre mon problème de double tri ou augmenter la mémoire de XeLaTeX. La seconde solution, moins écologique, est plus rapide à mettre en œuvre et consiste à augmenter la mémoire utilisable par XeLaTeX [4].

Une simple recherche sur le web m’indique la démarche à suivre, pour une installation TeXLive [5] :

  • trouver le fichier de configuration de texlive texmf.cnf en saisissant kpsewhich texmf.cnf dans mon terminal ;
  • dans mon cas, le fichier est /usr/local/texlive/2016/texmf.cnf ;
  • ouvrant le fichier je lis les lignes suivantes :
    % This texmf.cnf file should contain only your personal changes from the
    % original texmf.cnf (for example, as chosen in the installer).

    Ceci signifie que je peux y mettre tous mes réglages personnels sans risque de perdre les réglages par défaut que je n’aurais pas explicitement modifiés.

  • j’ajoute donc le réglage sur de mémoire suivant
    main_memory = 7999999<code>, ayant lu que cela correspond à la quantité maximum gérable par (Xe)TeX.
    - puis dans mon terminal, saisir <code>sudo fmtutil-sys --all

    pour relancer la création des scripts XeLaTeX, LaTeX et co, à partir de mes nouveaux réglages.

Pour m’assurer que mes réglages aient bien été pris en compte, je lance la compilation avec XeLaTeX d’un fichier minimum :

\documentclass{article}
\begin{document}
\end{document
}

J’ouvre le fichier .log généré, et je lis peu avant la fin :

Here is how much of TeX's memory you used:
196 strings out of 493589
2014 string characters out of 6143511
53785 words of memory out of 7999999
3697 multiletter control sequences out of 15000+600000
3640 words of font info for 14 fonts, out of 8000000 for 9000
1347 hyphenation exceptions out of 8191
23i,1n,17p,127b,36s stack positions out of 5000i,500n,10000p,200000b,80000s

La ligne « 53785 words of memory out of 7999999 » m’indique donc que ma quantité de "words of memory [6]" est passée à 7999999, ce qui correspond à mon nouveau réglage. Ouf !

En relance ma compilation de thèse, tout se passe bien [7].

Troisième solution : éviter d’avoir deux bibliographies triées

Cependant, dans le cas où ma bibliographie grossirait encore sensiblement (ce que je n’espère pas), et pour le principe, il paraîtrait plus utile d’avoir la bibliographie triée une seule fois dans le fichier .bbl, que ce soit pour les manuscrits ou pour les autres types d’entrées. De toute façon, je filtre les types d’entrées lors de l’affichage final, pour séparer les manuscrits des autres types.

C’est pourquoi, la version 2.7.0 de biblatex-realauthor, que je viens d’envoyer sur le CTAN propose un nouveau schéma de tri, anonymous+realauthor+manuscripts, que je peux passer comme option global de biblatex, ce qui me permet de supprimer l’environnement refcontext que mon commit problématique avait introduit.

En guise de conclusion

  • Versionner son travail est très utile. Apprenez à le faire si ce n’est pas déjà fait.
  • Si vous avez des problèmes de TeX capacity exceeded, sorry [main memory size=5000000] après une compilation biber, et qu’une recherche par dichotomie ne donne rien, regardez la taille du fichier .bbl.

Notes

[1En réalité, je suis arrivé moins directement au but, mais je donne ici la méthode que j’aurais du suivre…

[2Je schématise, très grossièrement, d’autant plus que je ne suis pas du tout spécialiste de ces questions d’allocation de la mémoire

[3Pour rappel : la mémoire vive, dont le contenu se vide à l’extinction de l’ordinateur, est plus rapide d’accès, mais plus coûteuse que la mémoire morte (disque dur, SSD), qui ne se vide pas à l’extinction de l’ordinateur. Pour faire leur calcul, les logiciels utilisent de la mémoire vive. Cependant, le système d’exploitation leur alloue également une part de mémoire morte pour simuler la mémoire vive, cette part est appelée « mémoire virtuelle ». Cette mémoire virtuelle, bien qu’utilisant un support à long terme, est destinée à n’être utilisée qu’à court terme.

[4Sur ce point, XeTeX reste calé TeX, lequel a été inventé en 1977, à une époque où la mémoire informatique était une ressource rare.

[5Pour MikTeX, j’ignore la démarche

[6Si j’ai bien compris, un « Word of memory » est l’unité minimale de donnée que traite un processeur. Aujourd’hui, la plupart des ordinateurs neufs ont des « words of memory » de 64 bit, mais il y encore quelques années, la norme était de 32 bit. Avec 7 999 999 « words of memory », je dispose donc de 511 999 936 bits de mémoire, soit environ 511 Mo. Cela étant, comme j’ignore comme XeTeX structure ses données en interne, je n’ai aucune idée de combien d’éléments bibliographiques je pourrai encore ajouter.

[7J’ai simplement utilisé 4010951 words of memory sur 7999999, soit un peu plus de la moitié

Vos commentaires

  • Le 24 octobre 2016 à 23:13, par Robert Alessi En réponse à : Comment ma bibliographie a saturé la mémoire de (Xe)LaTeX

    Excellent article, très intéressant et utile à d’autres titres que celui-ci. Merci également pour l’ajout de ce nouveau schéma de tri.

    Pour ma part, je compile tous mes fichiers à l’aide de LuaLaTeX depuis presque un an. Pour les fichiers les plus complexes, j’ai fait la migration par petits morceaux, en m’assurant à chaque fois que tout passait bien avant d’intégrer le morceau suivant. Le versionnage m’a beaucoup aidé à corriger les erreurs !

  • Le 29 janvier à 11:22, par Paul Gaborit En réponse à : Comment ma bibliographie a saturé la mémoire de (Xe)LaTeX

    Bonne analyse du problème et des différentes solutions. Il est vrai que le premier réflexe avec un telle erreur n’est pas de penser à un problème de bibliographie (d’un autre côté, il est rare de manipuler des documents avec plus de 1000 citations).

    Deux remarques de rédaction : (1) j’aurais employé le terme consacré « allocation dynamique de mémoire » plutôt que « attribution dynamique de mémoire » ; (2) dans le graphique décrivant les étapes de compilation, j’inverserais le sens des flèches de lecture (en remplaçant "lit" par "est lu par") pour bien montrer dans quel sens circule l’information.

  • Le 29 janvier à 11:42, par Maïeul En réponse à : Comment ma bibliographie a saturé la mémoire de (Xe)LaTeX

    Effectivement, « allocation » est meilleur qu’« attribution ». J’ai corrigé. Merci.

    Par contre, je suis plus réticent à utiliser un passif. J’adore personnellement les passifs, et c’est mon mode de raisonnement le plus courant. Cependant, mon expérience actuelle de reécriture de ma thèse pour correspondre aux logiques les plus courantes montre qu’en général les gens préférent et comprennent mieux l’actif.

    De plus, dans le cas présent, je pense que le plus intéressant n’est pas "dans quel sens circule l’information", mais "ce qui se passe à chaque étape de la compilation". Et dans ce cas partir des programmes plutôt que des fichiers est meilleurs. D’où le fait d’avoir mis les 3 blocs de programme au centre, avec une flèche transversale signifiant la chronologie.

  • Le 29 janvier à 15:10, par Paul Gaborit En réponse à : Comment ma bibliographie a saturé la mémoire de (Xe)LaTeX

    Je partage votre avis au sujet de l’usage du passif ou de l’actif. Donc, comme ce sont bien les fichiers « source » qui alimentent les différents outils, je propose l’inversion des flèches et un étiquetage par « alimente » (forme active ;-)).

  • Le 29 janvier à 15:33, par Maïeul En réponse à : Comment ma bibliographie a saturé la mémoire de (Xe)LaTeX

    Ahah ! Bien joué. Je pense cependant qu’il faut rester au niveau du processus de l’utilisateur, qui lance des programmes et ne donne pas directement à manger à ceux-ci.

Qui êtes-vous ?

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici
  • Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.

À propos

Titulaire d’un doctorat en théologie et d’un doctorat en histoire, sous la direction conjointe de Frédéric Amsler et d’Élisabeth_Malamut, je commence à partir du 1er août 2017 un travail d’édition critique des Actes de Barnabé.

Dans le cadre de la rédaction de mon mémoire de master puis de ma thèse de doctorat, j’ai été emmené à utiliser LaTeX, et j’ai donc décider de partager mes techniques. En effet, au cours de mes premiers apprentissages, j’ai découvert que les ressources indiquant les outils pour l’utilisation de LaTeX en sciences humaines étaient rares. Ceci m’a conduit à maintenir ou créer plusieurs packages LaTeX et à donner plusieurs formations.

Par ailleurs, je suis membre actif de la communauté SPIP, au sein de laquelle j’administre le site Spip-Contrib. Je propose sur ce site quelques notes sur SPIP, en général à destination de webmestre.

Il m’arrive également de faire un petit peu de Python, de temps en temps.

Enfin, je tiens un blog de réflexions politiques et religieuses.

Maïeul