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.

53voto

Andrei Points 3394

Donc, vous avez déjà un programme qui produit ce texte :

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] -...

et

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 -...

n'est-ce pas ?

Si toutes vos fonctions ont un "format" similaire (multiplier n nombres m fois et additionner les résultats - ou quelque chose de similaire), je pense que vous pouvez le faire :

  • changer le programme du générateur pour qu'il produise des décalages au lieu de chaînes de caractères (c'est-à-dire qu'au lieu de la chaîne de caractères "s.ds0" il produira offsetof(ProcessVars, ds0)
  • créer un tableau de ces décalages
  • écrire un évaluateur qui accepte le tableau ci-dessus et les adresses de base des pointeurs de structure et produit un résultat

Le tableau+évaluateur représentera la même logique qu'une de vos fonctions, mais seul l'évaluateur sera du code. Le tableau est une "donnée" et peut être généré au moment de l'exécution ou sauvegardé sur le disque et lu par morceaux ou avec un fichier mappé en mémoire.

Pour votre exemple particulier dans func1, imaginez comment vous réécririez la fonction via un évaluateur si vous aviez accès à l'adresse de base de s et csc ainsi qu'une représentation vectorielle des constantes et des décalages que vous devez ajouter aux adresses de base pour atteindre le résultat suivant x14 , ds8 et csc[51370]

Vous devez créer une nouvelle forme de "données" qui décrira la manière de traiter les données réelles que vous transmettez à vos très nombreuses fonctions.

45voto

BЈовић Points 28674

Le site ABI x86-64 utilisée par Linux définit un "grand modèle" spécifiquement pour éviter ces limitations de taille, qui inclut des types de relocalisation de 64 bits pour le GOT et le PLT. (Voir le tableau dans la section 4.4.2, et les séquences d'instructions dans 3.5.5 qui montrent comment ils sont utilisés).

Comme vos fonctions occupent 2,8 Go, vous n'avez pas de chance, car gcc ne prend pas en charge les grands modèles. Ce que vous pouvez faire, c'est réorganiser votre code de manière à pouvoir le diviser en bibliothèques partagées que vous lieriez dynamiquement.

Si ce n'est pas possible, comme quelqu'un l'a suggéré, au lieu de mettre vos données dans le code (en les compilant et en les liant), puisqu'elles sont énormes, vous pouvez les charger au moment de l'exécution (soit comme un fichier normal, soit en les mmapant).

EDIT

Il semble que le grand modèle soit supporté par gcc 4.6 (voir cette page ). Vous pouvez essayer cela, mais ce qui précède s'applique toujours à la réorganisation de votre code.

37voto

bdonlan Points 90068

Avec un programme de ce côté, les manques de cache pour le code sont très probablement supérieurs aux coûts de bouclage au moment de l'exécution. Je vous recommande de retourner à votre générateur de code, et de lui faire générer quelques compact pour ce qu'il veut évaluer (c'est-à-dire une représentation susceptible de tenir dans le D-cache), puis l'exécuter avec un interpréteur dans votre programme. Vous pourriez également voir si vous pouvez factoriser des noyaux plus petits qui ont encore un nombre significatif d'opérations, puis les utiliser comme "instructions" dans le code interprété.

21voto

zvrba Points 14028

L'erreur se produit parce que vous avez trop de CODE, pas de données ! Ceci est indiqué par exemple par __libc_csu_fini (qui est une fonction) étant référencée à partir de _start et la relocalisation est tronquée pour s'adapter. Cela signifie que _start (le véritable point d'entrée du programme) essaie d'appeler cette fonction via un décalage 32 bits SIGNÉ, qui n'a qu'une portée de 2 Go. Puisque la quantité totale de votre code objet est ~2.8 GB, les faits se vérifient.

Si vous pouviez redéfinir vos structures de données, une grande partie de votre code pourrait être "compressée" en réécrivant les énormes expressions en simples boucles.

Aussi, vous pourriez calculer csc[] dans un autre programme, stockez les résultats dans un fichier, et ne les chargez que lorsque c'est nécessaire.

15voto

AlefSin Points 835

Je pense que tout le monde est d'accord pour dire qu'il devrait y avoir une autre façon de faire ce que vous voulez faire. Compiler des centaines de mégaoctets (gigaoctets ?) de code, le lier dans un exécutable de plusieurs gigaoctets et l'exécuter semble très inefficace.

Si je comprends bien votre problème, vous utilisez une sorte de générateur de code, G, pour générer un tas de fonctions func1...N qui prennent un tas de cartes csc1...M en entrée. Ce que vous voulez faire, c'est calculer csc1...M et exécutez une boucle de 1.000.000 de fois pour différentes entrées et trouvez à chaque fois s = func1 + func2 + ... + funcN . Vous n'avez pas précisé comment fucn1...N sont liés à csc1...M cependant.

Si tout cela est vrai, il semble que vous devriez être en mesure de renverser le problème d'une manière différente qui peut potentiellement être beaucoup plus gérable et même peut-être plus rapide (c'est-à-dire en laissant le cache de votre machine fonctionner réellement).

Outre le problème pratique de la taille des fichiers objets, votre programme actuel ne sera pas efficace car il ne localise pas l'accès aux données (trop de cartes énormes) et n'a pas d'exécution de code localisée (trop de fonctions très longues).

Pourquoi ne pas diviser votre programme en 3 phases ? Phase 1 : construire csc1...M et les stocker. Phase 2 : construire un func à la fois, l'exécuter 1 000 000 de fois avec chaque entrée et stocker les résultats. Phase 3 : trouver la somme des résultats des calculs stockés. func1...N résultats pour chaque exécution sur 1 000 000 de fois. L'avantage de cette solution est qu'elle peut être facilement mise en parallèle sur plusieurs machines indépendantes.

Edit : @bbtrb, pourrais-tu rendre un func et un csc disponibles quelque part ? Ils semblent être très réguliers et compressibles. Par exemple, func1 semble être juste une somme d'expressions composées chacune d'un coefficient, de 2 index sur les variables de s et d'un index dans csc. Il peut donc être réduit à une jolie boucle. Si vous mettez à disposition des exemples complets, je suis sûr que l'on peut trouver des moyens de les compresser en boucles plutôt qu'en longues expressions.

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