108 votes

Comment corriger l'erreur de compilation de GCC lors de la compilation de >2 Go de code ?

J'ai un grand nombre de fonctions totalisant environ 2,8 Go de code objet (malheureusement, il n'y a pas moyen de contourner ce problème, l'informatique scientifique...).

Lorsque j'essaie de les lier, j'obtiens (attendu) relocation truncated to fit: R_X86_64_32S que j'espérais contourner en spécifiant l'indicateur du compilateur -mcmodel=medium . Toutes les bibliothèques qui sont liées en plus et dont j'ai le contrôle sont compilées avec la commande -fpic drapeau.

Pourtant, l'erreur persiste, et je suppose que certaines bibliothèques auxquelles je me lie ne sont pas compilées avec PIC.

Voici l'erreur :

/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crt1.o: In function `_start':
(.text+0x12): relocation truncated to fit: R_X86_64_32S against symbol `__libc_csu_fini'     defined in .text section in /usr/lib64/libc_nonshared.a(elf-init.oS)
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crt1.o: In function `_start':
(.text+0x19): relocation truncated to fit: R_X86_64_32S against symbol `__libc_csu_init'    defined in .text section in /usr/lib64/libc_nonshared.a(elf-init.oS)
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../lib64/crti.o: In function    `call_gmon_start':
(.text+0x7): relocation truncated to fit: R_X86_64_GOTPCREL against undefined symbol      `__gmon_start__'
/usr/lib/gcc/x86_64-redhat-linux/4.1.2/crtbegin.o: In function `__do_global_dtors_aux':
crtstuff.c:(.text+0xb): relocation truncated to fit: R_X86_64_PC32 against `.bss' 
crtstuff.c:(.text+0x13): relocation truncated to fit: R_X86_64_32 against symbol `__DTOR_END__' defined in .dtors section in /usr/lib/gcc/x86_64-redhat-linux/4.1.2/crtend.o
crtstuff.c:(.text+0x19): relocation truncated to fit: R_X86_64_32S against `.dtors'
crtstuff.c:(.text+0x28): relocation truncated to fit: R_X86_64_PC32 against `.bss'
crtstuff.c:(.text+0x38): relocation truncated to fit: R_X86_64_PC32 against `.bss'
crtstuff.c:(.text+0x3f): relocation truncated to fit: R_X86_64_32S against `.dtors'
crtstuff.c:(.text+0x46): relocation truncated to fit: R_X86_64_PC32 against `.bss'
crtstuff.c:(.text+0x51): additional relocation overflows omitted from the output
collect2: ld returned 1 exit status
make: *** [testsme] Error 1

Et les bibliothèques du système que je relie :

-lgfortran -lm -lrt -lpthread

Avez-vous une idée de l'endroit où il faut chercher le problème ?

EDIT :

Tout d'abord, merci pour la discussion...

Pour clarifier un peu, j'ai des centaines de fonctions (d'une taille d'environ 1 Mo chacune dans des fichiers d'objets séparés) comme celle-ci :

double func1(std::tr1::unordered_map<int, double> & csc, 
             std::vector<EvaluationNode::Ptr> & ti, 
             ProcessVars & s)
{
    double sum, prefactor, expr;

    prefactor = +s.ds8*s.ds10*ti[0]->value();
    expr =       ( - 5/243.*(s.x14*s.x15*csc[49300] + 9/10.*s.x14*s.x15*csc[49301] +
           1/10.*s.x14*s.x15*csc[49302] - 3/5.*s.x14*s.x15*csc[49303] -
           27/10.*s.x14*s.x15*csc[49304] + 12/5.*s.x14*s.x15*csc[49305] -
           3/10.*s.x14*s.x15*csc[49306] - 4/5.*s.x14*s.x15*csc[49307] +
           21/10.*s.x14*s.x15*csc[49308] + 1/10.*s.x14*s.x15*csc[49309] -
           s.x14*s.x15*csc[51370] - 9/10.*s.x14*s.x15*csc[51371] -
           1/10.*s.x14*s.x15*csc[51372] + 3/5.*s.x14*s.x15*csc[51373] +
           27/10.*s.x14*s.x15*csc[51374] - 12/5.*s.x14*s.x15*csc[51375] +
           3/10.*s.x14*s.x15*csc[51376] + 4/5.*s.x14*s.x15*csc[51377] -
           21/10.*s.x14*s.x15*csc[51378] - 1/10.*s.x14*s.x15*csc[51379] -
           2*s.x14*s.x15*csc[55100] - 9/5.*s.x14*s.x15*csc[55101] -
           1/5.*s.x14*s.x15*csc[55102] + 6/5.*s.x14*s.x15*csc[55103] +
           27/5.*s.x14*s.x15*csc[55104] - 24/5.*s.x14*s.x15*csc[55105] +
           3/5.*s.x14*s.x15*csc[55106] + 8/5.*s.x14*s.x15*csc[55107] -
           21/5.*s.x14*s.x15*csc[55108] - 1/5.*s.x14*s.x15*csc[55109] -
           2*s.x14*s.x15*csc[55170] - 9/5.*s.x14*s.x15*csc[55171] -
           1/5.*s.x14*s.x15*csc[55172] + 6/5.*s.x14*s.x15*csc[55173] +
           27/5.*s.x14*s.x15*csc[55174] - 24/5.*s.x14*s.x15*csc[55175] +
           // ...
           ;

        sum += prefactor*expr;
    // ...
    return sum;
}

L'objet s est relativement faible et conserve les constantes nécessaires x14, x15, ..., ds0, ..., etc. alors que ti renvoie simplement un double provenant d'une bibliothèque externe. Comme vous pouvez le voir, csc[] est une carte de valeurs précalculée qui est également évaluée dans des fichiers objets séparés (à nouveau des centaines avec une taille d'environ 1 Mo chacun) de la forme suivante :

void cscs132(std::tr1::unordered_map<int,double> & csc, ProcessVars & s)
{
    {
    double csc19295 =       + s.ds0*s.ds1*s.ds2 * ( -
           32*s.x12pow2*s.x15*s.x34*s.mbpow2*s.mWpowinv2 -
           32*s.x12pow2*s.x15*s.x35*s.mbpow2*s.mWpowinv2 -
           32*s.x12pow2*s.x15*s.x35*s.x45*s.mWpowinv2 -
           32*s.x12pow2*s.x25*s.x34*s.mbpow2*s.mWpowinv2 -
           32*s.x12pow2*s.x25*s.x35*s.mbpow2*s.mWpowinv2 -
           32*s.x12pow2*s.x25*s.x35*s.x45*s.mWpowinv2 +
           32*s.x12pow2*s.x34*s.mbpow4*s.mWpowinv2 +
           32*s.x12pow2*s.x34*s.x35*s.mbpow2*s.mWpowinv2 +
           32*s.x12pow2*s.x34*s.x45*s.mbpow2*s.mWpowinv2 +
           32*s.x12pow2*s.x35*s.mbpow4*s.mWpowinv2 +
           32*s.x12pow2*s.x35pow2*s.mbpow2*s.mWpowinv2 +
           32*s.x12pow2*s.x35pow2*s.x45*s.mWpowinv2 +
           64*s.x12pow2*s.x35*s.x45*s.mbpow2*s.mWpowinv2 +
           32*s.x12pow2*s.x35*s.x45pow2*s.mWpowinv2 -
           64*s.x12*s.p1p3*s.x15*s.mbpow4*s.mWpowinv2 +
           64*s.x12*s.p1p3*s.x15pow2*s.mbpow2*s.mWpowinv2 +
           96*s.x12*s.p1p3*s.x15*s.x25*s.mbpow2*s.mWpowinv2 -
           64*s.x12*s.p1p3*s.x15*s.x35*s.mbpow2*s.mWpowinv2 -
           64*s.x12*s.p1p3*s.x15*s.x45*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.p1p3*s.x25*s.mbpow4*s.mWpowinv2 +
           32*s.x12*s.p1p3*s.x25pow2*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.p1p3*s.x25*s.x35*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.p1p3*s.x25*s.x45*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.p1p3*s.x45*s.mbpow2 +
           64*s.x12*s.x14*s.x15pow2*s.x35*s.mWpowinv2 +
           96*s.x12*s.x14*s.x15*s.x25*s.x35*s.mWpowinv2 +
           32*s.x12*s.x14*s.x15*s.x34*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.x14*s.x15*s.x35*s.mbpow2*s.mWpowinv2 -
           64*s.x12*s.x14*s.x15*s.x35pow2*s.mWpowinv2 -
           32*s.x12*s.x14*s.x15*s.x35*s.x45*s.mWpowinv2 +
           32*s.x12*s.x14*s.x25pow2*s.x35*s.mWpowinv2 +
           32*s.x12*s.x14*s.x25*s.x34*s.mbpow2*s.mWpowinv2 -
           32*s.x12*s.x14*s.x25*s.x35pow2*s.mWpowinv2 -
           // ...

       csc.insert(cscMap::value_type(192953, csc19295));
    }

    {
       double csc19296 =      // ... ;

       csc.insert(cscMap::value_type(192956, csc19296));
    }

    // ...
}

C'est à peu près tout. L'étape finale consiste alors simplement à appeler tous ces func[i] et en résumant le résultat.

Concernant le fait que c'est un cas plutôt spécial et inhabituel : Oui, il l'est. C'est ce à quoi les gens doivent faire face lorsqu'ils essaient de faire des calculs de haute précision pour la physique des particules.

EDIT2 :

Je dois également ajouter que x12, x13, etc. ne sont pas vraiment des constantes. Elles sont fixées à des valeurs spécifiques, toutes ces fonctions sont exécutées et le résultat est renvoyé, puis un nouvel ensemble de x12, x13, etc. est choisi pour produire la valeur suivante. Et cela doit être fait 10 5 à 10 6 temps...

EDIT3 :

Merci pour les suggestions et la discussion jusqu'à présent... Je vais essayer d'enrouler les boucles lors de la génération du code d'une manière ou d'une autre, je ne sais pas exactement comment le faire, pour être honnête, mais c'est la meilleure solution.

BTW, je n'ai pas essayé de me cacher derrière le fait que "c'est du calcul scientifique - pas moyen d'optimiser".
C'est juste que la base de ce code est quelque chose qui sort d'une "boîte noire" à laquelle je n'ai pas vraiment accès et, de plus, tout cela fonctionnait très bien avec des exemples simples, et je me sens surtout dépassé par ce qui se passe dans une application du monde réel...

EDIT4 :

J'ai donc réussi à réduire la taille du code de l'application csc d'environ un quart en simplifiant les expressions dans un système de calcul formel ( Mathematica ). Je vois maintenant aussi un moyen de le réduire d'un autre ordre de grandeur en appliquant d'autres astuces avant de générer le code (ce qui ramènerait cette partie à environ 100 Mo) et j'espère que cette idée fonctionnera.

Maintenant, en rapport avec vos réponses :

J'essaie d'enrouler les boucles à nouveau dans le func où un CAS ne sera pas d'une grande aide, mais j'ai déjà quelques idées. Par exemple, trier les expressions par les variables comme x12, x13,... , analyser le csc avec Python et générer des tableaux qui les relient les uns aux autres. Ensuite, je peux au moins générer ces parties sous forme de boucles. Comme cela semble être la meilleure solution jusqu'à présent, je marque ceci comme la meilleure réponse.

Cependant, j'aimerais aussi donner du crédit à VJo. GCC 4.6 fonctionne en effet beaucoup meilleur, produit un code plus petit et est plus rapide. L'utilisation du grand modèle permet de travailler sur le code tel quel. Donc, techniquement, c'est la bonne réponse, mais changer l'ensemble du concept est une bien meilleure approche.

Merci à tous pour vos suggestions et votre aide. Si cela intéresse quelqu'un, je vais poster le résultat final dès que je serai prêt.

REMARQUES :

Juste quelques remarques sur d'autres réponses : Le code que j'essaie d'exécuter ne provient pas d'une expansion de fonctions/algorithmes simples et d'un stupide déroulage inutile. Ce qui se passe en fait, c'est que le matériel avec lequel nous commençons est constitué d'objets mathématiques assez compliqués et que nous les amenons à un niveau numériquement plus élevé. Calculable génère ces expressions. Le problème réside en fait dans la théorie physique sous-jacente. La complexité des expressions intermédiaires s'échelonne de façon factorielle, ce qui est bien connu, mais lorsqu'on combine tout cela à quelque chose de physiquement mesurable - un observable - cela se résume à une poignée de très petites fonctions qui forment la base des expressions. (Il y a définitivement quelque chose de "faux" à cet égard avec la théorie générale et la théorie de l'infini. uniquement disponible sur ansatz Nous essayons d'amener cet ansatz à un autre niveau, qui n'est plus faisable analytiquement et où la base des fonctions nécessaires n'est pas connue. Nous essayons donc de le forcer brutalement comme ceci. Ce n'est pas la meilleure méthode, mais nous espérons qu'elle nous aidera à comprendre la physique en question...

DERNIÈRE MODIFICATION :

Grâce à toutes vos suggestions, j'ai réussi à réduire considérablement la taille du code, en utilisant Mathematica et une modification du générateur de code pour l'outil d'analyse de l'environnement. func qui va dans le sens de la réponse ci-dessus :)

J'ai simplifié le csc avec Mathematica, ce qui le ramène à 92 Mo. C'est la partie irréductible. Les premières tentatives ont pris une éternité, mais après quelques optimisations, le programme s'exécute maintenant en 10 minutes environ sur un seul processeur.

L'effet sur le func a été spectaculaire : La taille totale du code est tombée à environ 9 Mo, et le code total est maintenant de l'ordre de 100 Mo. Il est maintenant logique d'activer les optimisations et l'exécution est assez rapide.

Encore une fois, merci à tous pour vos suggestions, j'ai beaucoup appris.

5voto

AProgrammer Points 31212

Si je lis correctement vos erreurs, ce qui vous fait dépasser la limite est la section des données initialisées (si c'était le code, vous auriez beaucoup plus d'erreurs IMHO). Avez-vous de grands tableaux de données globales ? Si c'est le cas, je restructurerais le programme pour qu'ils soient alloués dynamiquement. Si les données sont initialisées, je les lirais depuis un fichier de configuration.

J'ai vu ça :

(.text+0x20) : référence indéfinie à `main'.

Je pense que vous avez un autre problème.

3voto

malkia Points 1272

Quelques suggestions : - Optimisez pour la taille (-Os). Faites de vos appels de fonctions en ligne des appels de fonctions normaux. Activez la mise en commun des chaînes.

Essayez de diviser les choses en différentes DLL (objets partagés, .so pour Linux, .dylib pour Mac OS X). Assurez-vous qu'ils peuvent être déchargés. Ensuite, implémentez quelque chose pour charger les choses à la demande, et les libérer lorsqu'elles ne sont pas nécessaires.

Si ce n'est pas le cas, divisez votre code en différents exécutables, et utilisez quelque chose pour communiquer entre eux (pipes, sockets, voire écriture/lecture dans un fichier). C'est maladroit, mais quelles sont les options dont vous disposez ?

Une alternative totale : - Utiliser un langage dynamique avec JIT . Juste au dessus de ma tête - utiliser LuaJIT - et réécrire (régénérer ?) un grand nombre de ces expressions en Lua ou d'autres langages et moteurs d'exécution qui permettent de récupérer le code.

LuaJIT est assez efficace, battant parfois C/C++ pour certaines choses, mais souvent très proche (il peut parfois être lent en raison d'une mauvaise collecte des déchets). Vérifiez par vous-même :

http://luajit.org/performance_x86.html

Télécharger le scimark2.lua à partir de là, et comparez-le avec la version "C" (googlez-le) - les résultats sont souvent très proches.

3voto

Donal Fellows Points 56559

Il me semble que le code effectue une intégration numérique en utilisant une sorte de méthode de profondeur adaptative. Malheureusement, le générateur de code (ou plutôt l'auteur du générateur de code) est tellement stupide afin de générer une fonction par patch plutôt qu'une par type de patch. En tant que tel, il a produit trop de code pour être compilé, et même s'il pouvait l'être, son exécution serait douloureuse car rien n'est jamais partagé nulle part. (Pouvez-vous imaginer la douleur résultant de l'obligation de charger chaque page de code objet à partir du disque parce que rien n'est jamais partagé et qu'il s'agit donc toujours d'un candidat à l'éviction pour le système d'exploitation. Pour ne rien dire des caches d'instructions, qui vont être inutiles).

La solution est d'arrêter de tout dérouler ; pour ce type de code, vous voulez maximiser le partage car la surcharge d'instructions supplémentaires pour accéder aux données selon des schémas plus complexes sera de toute façon absorbée par le coût du traitement de l'ensemble de données sous-jacent (vraisemblablement important). Il est également possible que le générateur de code le fasse par défaut, et que le scientifique ait vu quelques options de déroulement (avec la remarque que celles-ci améliorent parfois la vitesse) et les ait toutes activées d'un seul coup, et qu'il insiste maintenant pour que le désordre qui en résulte soit accepté par l'ordinateur, plutôt que d'accepter les véritables restrictions de la machine et d'utiliser la version numériquement correcte qui est générée par défaut. Mais si le générateur de code ne le fait pas, trouvez-en un qui le fera (ou piratez le code existant).

L'essentiel : Compiler et lier 2,8 Go de code ne fonctionne pas et ne devrait pas être forcé de fonctionner. Trouvez un autre moyen.

2voto

Brian Points 430

Ces expressions ressemblent beaucoup à une série alternée pour moi. Je ne sais pas à quoi ressemble le reste du code, mais il ne semble pas qu'il soit très difficile de dériver l'expression génératrice. Cela en vaudrait probablement la peine au moment de l'exécution aussi, surtout si vous avez 2,8 Go de code non laminé de 2 Ko.

2voto

ajklbahu8geag Points 11

L'éditeur de liens tente de générer des décalages de relocalisation de 32 bits dans un binaire qui a en quelque sorte dépassé ces limites. Essayez de réduire les besoins en espace d'adressage du programme principal.

Pouvez-vous diviser une partie ou la majorité du code objet en une ou plusieurs bibliothèques (également compilées avec -fpic / -fPIC) ? Puis générer un binaire non statique qui se lie à ces bibliothèques. Les bibliothèques vivront dans des blocs de mémoire distincts et vos décalages de relocalisation seront dynamiques/absolus (64 bits) plutôt que relatifs (32 bits).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X