J'ai un grand nombre de fonctions totalisant environ 2,8 Go de code objet (malheureusement, il n'y a pas moyen de contourner, le calcul scientifique ...)
Lorsque j'essaye de les lier, j'obtiens (attendu) relocation truncated to fit: R_X86_64_32S
erreurs, que j'espérais contourner en spécifiant le drapeau du compilateur -mcmodel=medium
. Toutes les bibliothèques liées en plus dont j'ai le contrôle sont compilées avec le -fpic
drapeau.
Pourtant, l'erreur persiste, et je suppose que certaines bibliothèques auxquelles je 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 système contre lesquelles je fais un lien:
-lgfortran -lm -lrt -lpthread
Des indices où chercher le problème?
EDIT: Tout d'abord, merci pour la discussion ... Pour clarifier un peu, j'ai des centaines de fonctions (chacune d'environ 1 Mo de taille dans des fichiers objets séparés) comme ceci:
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 petit et conserve les constantes nécessaires x14, x15, ..., ds0, ..., etc. tandis que ti
renvoie juste un double à partir 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 (encore des centaines avec environ ~ 1 Mo de taille 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 ça. La dernière étape consiste alors simplement à appeler tous ces func[i]
et résumer le résultat.
Concernant le fait qu'il s'agit d'un cas assez spécial et inhabituel: Oui, il l'est. C'est à cela que 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. Ils sont définis sur des valeurs spécifiques, toutes ces fonctions sont exécutées et le résultat 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 fois ...
EDIT3: Merci pour les suggestions et la discussion jusqu'à présent ... J'essaierai de faire le tour des boucles lors de la génération de code d'une manière ou d'une autre, je ne sais pas exactement comment faire cela, pour être honnête, mais c'est le meilleur pari.
BTW, je n'ai pas essayé de me cacher "c'est de l'informatique scientifique - pas moyen d'optimiser". C'est juste que la base de ce code est quelque chose qui sort d'une "boîte noire" où je n'ai pas vraiment accès et, de plus, le tout a très bien fonctionné avec des exemples simples, et je me sens principalement submergé par ce qui se passe dans un vrai application mondiale ...
EDIT4: J'ai donc réussi à réduire la taille du code des définitions de csc
d'environ une en simplifiant les expressions dans un système d'algèbre informatique ( 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 fonctionne.
Maintenant lié à vos réponses: j'essaie de faire remonter les boucles dans les func
s, où un CAS n'aidera pas beaucoup, mais j'ai déjà quelques idées. Par exemple, trier les expressions par les variables comme x12, x13,...
, analyser les csc
s 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 le meilleur solution jusqu'à présent, je marque cela comme la meilleure réponse.
Cependant, je voudrais également rendre hommage à VJo. GCC 4.6 fonctionne en effet beaucoup mieux, produit un code plus petit et est plus rapide. L'utilisation du grand modèle fonctionne tel quel. Donc, techniquement, c'est la bonne réponse, mais changer le concept dans son ensemble est une bien meilleure approche.
Merci à tous pour vos suggestions et votre aide. Si quelqu'un est intéressé, je vais publier le résultat final dès que je serai prêt.
REMARQUES: Juste quelques remarques à d'autres réponses: Le code que j'essaie d'exécuter ne trouve pas son origine dans une extension de fonctions/algorithmes simples et de déroulements inutiles stupides. Ce qui se passe réellement, c'est que le truc avec lequel nous commençons est des objets mathématiques assez compliqués et les amener à une forme numérique 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 évolue de manière factorielle, ce qui est bien connu, mais en combinant 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 certainement quelque chose de "faux" à cet égard avec le général et seulement disponible ansatz qui est appelé "théorie de la perturbation") Nous essayons d'amener cet ansatz à un autre niveau, qui n'est plus réalisable analytiquement et où la base des fonctions nécessaires n'est pas connue. Nous essayons donc de le forcer comme ça. Ce n'est pas la meilleure façon, mais j'espère que celle qui contribuera à notre compréhension de la physique à la fin ...
DERNIÈRE ÉDITION: 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 le func
s un peu dans le sens de la réponse du haut :)
J'ai simplifié les fonctions csc
avec Mathematica, le ramenant à 92 Mo. Ceci est la partie irréductible. Les premières tentatives ont pris une éternité, mais après quelques optimisations, cela s'exécute maintenant en environ 10 minutes sur un seul processeur.
L'effet sur les func
était dramatique: la taille totale du code pour eux est réduite à environ 9 Mo, donc le code totalise maintenant dans la plage de 100 Mo. Il est désormais judicieux d'activer les optimisations et l'exécution est assez rapide.
Encore une fois, merci à tous pour vos suggestions, j'ai beaucoup appris.
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 -...
droite?
Si toutes vos fonctions ont un "format" similaire (multipliez n nombres m fois et ajoutez les résultats - ou quelque chose de similaire) alors je pense que vous pouvez le faire:
offsetof(ProcessVars, ds0)
Le tableau + l'évaluateur représentera la même logique que l'une de vos fonctions, mais seul l'évaluateur sera le code. Le tableau est des "données" et peut être généré lors de l'exécution ou enregistré sur le disque et lu des morceaux i 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 de type vectoriel des constantes et des décalages vous devez ajouter aux adresses de base pour accéder à x14
, ds8
et csc[51370]
Vous devez créer une nouvelle forme de "données" qui décrira comment traiter les données réelles que vous transmettez à votre grand nombre de fonctions.
Le ABI x86-64 utilisé par Linux définit un "grand modèle" spécifiquement pour éviter de telles limitations de taille, qui inclut des types de relocalisation 64 bits pour le GOT et le PLT. (Voir le tableau de la section 4.4.2 et les séquences d'instructions du 3.5.5 qui montrent comment elles sont utilisées.)
Puisque 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 de réorganiser votre code d'une manière qui vous permettrait de le diviser en bibliothèques partagées que vous lieriez dynamiquement.
Si cela n'est pas possible, comme quelqu'un l'a suggéré, au lieu de mettre vos données en code (en les compilant et en les liant), car elles sont énormes, vous pouvez les charger au moment de l'exécution (soit en tant que fichier normal, soit en les cartographiant).
[~ # ~] modifier [~ # ~]
On dirait que le grand modèle est 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.
Avec un programme de ce côté, les échecs de cache pour le code sont très susceptibles de dépasser les coûts de boucle lors de l'exécution. Je vous recommande de revenir à votre générateur de code et de le faire générer une représentation compact pour ce qu'il veut évaluer (c.-à-d., Une susceptible de tenir dans le D-cache), puis d'exécuter cela avec un interprète dans votre programme. Vous pouvez également voir si vous pouvez éliminer les noyaux plus petits qui ont encore un nombre important d'opérations, puis les utiliser comme "instructions" dans le code interprété.
L'erreur se produit car vous avez trop de CODE, pas de données! Ceci est indiqué par exemple par __libc_csu_fini
(qui est une fonction) référencé à 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) tente d'appeler cette fonction via un décalage 32 bits SIGNÉ, qui n'a qu'une plage de 2 Go. Étant donné que le montant total de votre code objet est d'environ 2,8 Go, les faits sont vérifiés.
Si vous pouviez repenser vos structures de données, une grande partie de votre code pourrait être "compressé" en réécrivant les énormes expressions sous forme de boucles simples.
Vous pouvez également calculer csc[]
dans un autre programme, stockez les résultats dans un fichier et chargez-les au besoin.
Je pense que tout le monde convient qu'il devrait y avoir une façon différente de faire ce que vous voulez faire. Compiler des centaines de mégaoctets (gigaoctets?) De code, le relier à un exécutable de plusieurs gigaoctets et l'exécuter semble tout simplement 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 est de calculer csc1...M
Et d'exécuter une boucle de 1 000 000 de fois pour différentes entrées et à chaque fois de trouver s = func1 + func2 + ... + funcN
. Vous n'avez cependant pas précisé comment fucn1...N
Sont liés à csc1...M
.
Si tout cela est vrai, il semble que vous devriez être en mesure de renverser le problème de manière différente, ce qui peut potentiellement être beaucoup plus gérable et même éventuellement plus rapide (c'est-à-dire laisser 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é (trop de fonctions très longues).
Que diriez-vous de diviser votre programme en 3 phases: Phase 1 compilez csc1...M
Et stockez-les. La phase 2 génère un func
à la fois, l'exécute 1 000 000 de fois avec chaque entrée et stocke les résultats. La phase 3 trouve la somme des résultats des résultats stockés func1...N
Pour chaque exécution sur 1 000 000 de fois. La bonne partie de cette solution est qu'elle peut être facilement rendue parallèle sur plusieurs machines indépendantes.
Edit: @bbtrb, pourriez-vous 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 consistant chacune en 1 coefficient, 2 index des variables en s et 1 index en csc. Il peut donc être réduit à une boucle de Nice. Si vous fournissez des exemples complets, je suis sûr que des moyens peuvent être trouvés pour les compresser en boucles plutôt qu'en expressions longues.
Si je lis correctement vos erreurs, ce qui vous fait reporter la limite est la section des données initialisées (si c'était le code, vous auriez beaucoup plus d'erreurs à mon humble avis). 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 à partir d'un fichier de configuration.
BTW voyant ceci:
(.text + 0x20): référence non définie à `main '
Je pense que vous avez un autre problème.
Quelques suggestions: - Optimiser pour la taille (-Os). Faites vos appels de fonction en ligne, appels de fonction normaux. Activez le regroupement de 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 des objets à la demande et libérez-les lorsqu'ils ne sont pas nécessaires.
Sinon, divisez votre code en différents exécutables et utilisez quelque chose pour communiquer entre eux (canaux, sockets, même écriture/lecture dans un fichier). Maladroit, mais quelles options avez-vous?
Totalement alternatif: - Utilisez un langage dynamique avec JIT . Juste au-dessus de ma tête - utilisez LuaJIT - et réécrivez (régénérez?) Un grand nombre de ces expressions dans Lua , ou d'autres langages et exécutions de ce type qui permettent au code d'être des ordures collectés.
LuaJIT est assez efficace, battant parfois C/C++ pour certaines choses, mais souvent très proche (parfois peut être lent en raison d'une mauvaise collecte des ordures). Vérifiez par vous-même:
http://luajit.org/performance_x86.html
Téléchargez le scimark2.lua
fichier à partir de là, et comparez-le avec la version "C" (google it) - les résultats sont souvent très proches.
Il me semble que le code fait 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 qu'il génère une fonction par patch plutôt qu'une par type de patch. En tant que tel, il produit trop de code pour être compilé, et même s'il pouvait être compilé, son exécution serait pénible car rien n'est jamais partagé nulle part. (Pouvez-vous imaginer la douleur résultant du fait de devoir charger chaque page de code objet à partir du disque parce que rien n'est jamais partagé et donc c'est toujours un candidat à l'OS à expulser. 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 le surdébit d'instructions supplémentaires pour accéder aux données selon des modèles plus complexes sera absorbé par le coût du traitement avec le (probablement) grand ensemble de données sous-jacent de toute façon. Il est également possible que le générateur de code le fasse même par défaut, et que le scientifique ait vu certaines options de déroulement (avec la remarque que cela améliore parfois la vitesse) et les ait toutes activées en même temps et insiste maintenant pour que ce gâchis résultant soit accepté par l'ordinateur, plutôt que d'accepter les vraies 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, obtenez-en un qui le fera (ou pirater le code existant).
La ligne du bas: la compilation et la liaison de 2,8 Go de code ne fonctionne pas et ne devrait pas être forcée de fonctionner. Trouvez un autre moyen.
Ces expressions me ressemblent beaucoup à une série alternée. Je ne sais pas à quoi ressemble le reste du code, mais il ne semble pas que ce soit si difficile de dériver l'expression génératrice. Cela en vaudrait probablement la peine au moment de l'exécution, surtout si vous disposez de 2,8 Go de code déroulé de 2 Ko.
L'éditeur de liens tente de générer des décalages de relocalisation 32 bits dans un binaire qui a en quelque sorte dépassé ces limitations. Essayez de réduire les besoins en espace d'adressage du programme principal.
Pouvez-vous diviser une partie ou la plupart du code objet en une ou plusieurs bibliothèques (également compilées avec -fpic/-fPIC)? Générez ensuite un binaire non statique qui se lie à ces bibliothèques. Les bibliothèques vivront dans des blocs de mémoire discrets et vos décalages de relocalisation seront dynamiques/absolus (64 bits) plutôt que relatifs (32 bits).
Cela ressemble au résultat d'une génération de code qui a mal tourné, peut-être par algèbre symbolique et/ou déroulement manuel. Les manipulations symboliques sont bien connues pour croître de façon exponentielle dans la profondeur de l'arbre d'expression ou du graphe de calcul. Il est probable que la différenciation automatique puisse être utilisée ici, ce qui réduirait la taille du code et accélérerait considérablement l'exécution.