Tengo una gran cantidad de funciones que suman alrededor de 2.8 GB de código objeto (desafortunadamente no hay forma de evitarlo, computación científica ...)
Cuando trato de vincularlos, obtengo relocation truncated to fit: R_X86_64_32S
errores (esperados) , que esperaba evitar especificando el indicador del compilador -mcmodel=medium
. Todas las bibliotecas que están vinculadas además de las que tengo control se compilan con la -fpic
bandera.
Aún así, el error persiste y supongo que algunas bibliotecas con las que me vinculo no están compiladas con PIC.
Aquí está el error:
/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
Y las bibliotecas del sistema con las que me vinculo:
-lgfortran -lm -lrt -lpthread
¿Alguna pista de dónde buscar el problema?
EDITAR: En primer lugar, gracias por la discusión ... Para aclarar un poco, tengo cientos de funciones (cada una de aproximadamente 1 MB de tamaño en archivos de objetos separados) como esta:
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;
}
El objeto s
es relativamente pequeño y mantiene las constantes necesarias x14, x15, ..., ds0, ..., etc., mientras que ti
solo devuelve un doble de una biblioteca externa. Como puede ver, csc[]
es un mapa precalculado de valores que también se evalúa en archivos de objetos separados (nuevamente cientos con aproximadamente ~ 1 MB de tamaño cada uno) de la siguiente forma:
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));
}
// ...
}
Eso es todo. El paso final entonces consiste en llamar a todos esos func[i]
y resumir el resultado.
En cuanto al hecho de que este es un caso bastante especial e inusual: sí, lo es. Esto es lo que la gente tiene que afrontar cuando intenta hacer cálculos de alta precisión para la física de partículas.
EDIT2: También debería agregar que x12, x13, etc. no son realmente constantes. Se establecen en valores específicos, se ejecutan todas esas funciones y se devuelve el resultado, y luego se elige un nuevo conjunto de x12, x13, etc. para producir el siguiente valor. Y esto debe hacerse de 10 ^ 5 a 10 ^ 6 veces ...
EDIT3: Gracias por las sugerencias y la discusión hasta ahora ... Intentaré enrollar los bucles sobre la generación de código de alguna manera, no estoy seguro de cómo hacerlo exactamente, para ser honesto, pero esta es la mejor opción.
Por cierto, no traté de esconderme detrás de "esto es computación científica, no hay forma de optimizar". Es solo que la base de este código es algo que sale de una "caja negra" a la que no tengo acceso real y, además, todo funcionó muy bien con ejemplos simples, y principalmente me siento abrumado con lo que sucede en un aplicación mundial ...
EDIT4: Entonces, me las arreglé para reducir el tamaño del código de las csc
definiciones en aproximadamente un cuarto simplificando expresiones en un sistema de álgebra por computadora ( Mathematica ). Ahora veo también alguna forma de reducirlo en otro orden de magnitud aplicando algunos otros trucos antes de generar el código (lo que reduciría esta parte a unos 100 MB) y espero que esta idea funcione.
Ahora relacionado con sus respuestas: estoy tratando de volver a enrollar los bucles en el func
s, donde un CAS no ayudará mucho, pero ya tengo algunas ideas. Por ejemplo, ordenar las expresiones por variables como x12, x13,...
, analizar la csc
s con Python y generar tablas que las relacionen entre sí. Entonces puedo al menos generar estas partes como bucles. Como esta parece ser la mejor solución hasta ahora, la marco como la mejor respuesta.
Sin embargo, también me gustaría darle crédito a VJo. De hecho, GCC 4.6 funciona mucho mejor, produce un código más pequeño y es más rápido. El uso del modelo grande funciona en el código tal cual. Entonces, técnicamente, esta es la respuesta correcta, pero cambiar todo el concepto es un enfoque mucho mejor.
Gracias a todos por sus sugerencias y ayuda. Si alguien está interesado, voy a publicar el resultado final tan pronto como esté listo.
OBSERVACIONES: Solo algunos comentarios a algunas otras respuestas: El código que estoy tratando de ejecutar no se origina en una expansión de funciones / algoritmos simples y un desenrollado innecesario estúpido. Lo que realmente sucede es que las cosas con las que comenzamos son objetos matemáticos bastante complicados y llevarlos a una forma numéricamente computable genera estas expresiones. El problema radica en realidad en la teoría física subyacente. La complejidad de las expresiones intermedias escala factorialmente, lo cual es bien conocido, pero cuando se combinan todas estas cosas con algo físicamente medible, un observable, solo se reduce a un puñado de funciones muy pequeñas que forman la base de las expresiones. (Definitivamente hay algo "incorrecto" a este respecto con el general y solo disponibleansatz que se llama "teoría de la perturbación") Tratamos de llevar este ansatz a otro nivel, que ya no es factible analíticamente y donde se desconoce la base de las funciones necesarias. Así que tratamos de usar la fuerza bruta de esta manera. No es la mejor manera, pero es de esperar que al final ayude con nuestra comprensión de la física en cuestión ...
ÚLTIMA EDICIÓN:
Gracias a todas sus sugerencias, me las arreglé para reducir considerablemente el tamaño del código, usando Mathematica y una modificación del generador de código para la func
s algo similar a la respuesta principal :)
He simplificado las csc
funciones con Mathematica, reduciéndolas a 92 MB. Esta es la parte irreductible. Los primeros intentos tomaron una eternidad, pero después de algunas optimizaciones, esto ahora se ejecuta en aproximadamente 10 minutos en una sola CPU.
El efecto en los func
s fue dramático: el tamaño total del código para ellos se redujo a aproximadamente 9 MB, por lo que el código ahora totaliza en el rango de 100 MB. Ahora tiene sentido activar las optimizaciones y la ejecución es bastante rápida.
Nuevamente, gracias a todos por sus sugerencias, he aprendido mucho.
fuente
mmap
su lugar , sacarlos usted mismo de un binario externo en tiempo de ejecución.Respuestas:
Entonces, ya tiene un programa que produce este texto:
y
¿Derecha?
Si todas sus funciones tienen un "formato" similar (multiplique n números m veces y agregue los resultados, o algo similar), entonces creo que puede hacer esto:
offsetof(ProcessVars, ds0)
La matriz + evaluador representará la misma lógica que una de sus funciones, pero solo el evaluador será código. La matriz es "datos" y puede generarse en tiempo de ejecución o guardarse en el disco y leer fragmentos de i o con un archivo mapeado en memoria.
Para su ejemplo particular en func1 Imagínese cómo se re-escribir la función a través de un evaluador si tuviera acceso a la dirección base
s
ycsc
también un vector como representación de las constantes y las compensaciones que hay que añadir a las direcciones de base para llegar ax14
,ds8
ycsc[51370]
Necesita crear una nueva forma de "datos" que describa cómo procesar los datos reales que pasa a su gran cantidad de funciones.
fuente
La ABI x86-64 utilizada por Linux define un "modelo grande" específicamente para evitar tales limitaciones de tamaño, que incluye tipos de reubicación de 64 bits para GOT y PLT. (Consulte la tabla en la sección 4.4.2 y las secuencias de instrucciones en 3.5.5 que muestran cómo se utilizan).
Dado que sus funciones ocupan 2,8 GB, no tiene suerte, porque gcc no admite modelos grandes. Lo que puede hacer es reorganizar su código de tal manera que le permita dividirlo en bibliotecas compartidas que vincularía dinámicamente.
Si eso no es posible, como sugirió alguien, en lugar de poner sus datos en código (compilarlos y vincularlos), ya que es enorme, puede cargarlos en tiempo de ejecución (ya sea como un archivo normal o puede mmap).
EDITAR
Parece que el modelo grande es compatible con gcc 4.6 (consulte esta página ). Puede intentarlo, pero lo anterior aún se aplica a la reorganización de su código.
fuente
Con un programa de ese lado, es muy probable que los errores de caché para el código excedan los costos de bucle en tiempo de ejecución. Le recomendaría que regrese a su generador de código y haga que genere alguna representación compacta para lo que quiere evaluar (es decir, una que probablemente quepa en D-cache), luego ejecute eso con un intérprete en su programa. También puede ver si puede descartar kernels más pequeños que todavía tienen un número significativo de operaciones, luego usarlos como 'instrucciones' en el código interpretado.
fuente
El error se produce porque tiene demasiado CÓDIGO, ¡no datos! Esto se indica, por ejemplo,
__libc_csu_fini
(que es una función) desde el que se hace referencia_start
y la reubicación se trunca para ajustar. Esto significa que_start
(el verdadero punto de entrada del programa) está intentando llamar a esa función a través de un desplazamiento FIRMADO de 32 bits, que tiene solo un rango de 2 GB. Dado que la cantidad total de su código objeto es ~ 2.8 GB, los hechos verifican.Si pudiera rediseñar sus estructuras de datos, gran parte de su código podría "comprimirse" reescribiendo las enormes expresiones como simples bucles.
Además, puede calcular
csc[]
en un programa diferente, almacenar los resultados en un archivo y simplemente cargarlos cuando sea necesario.fuente
csc[]
debe calcularse con mucha frecuencia y me gustaría evitar la E / S de disco.func1
arriba, algo como:for (int i = 0; i < N; ++i) expr += constants[i].*s.x14*s.x15*csc[49300 + i];
.Creo que todo el mundo está de acuerdo en que debería haber una forma diferente de hacer lo que quieres hacer. Compilar cientos de megabytes (¿gigabytes?) De código, vincularlo en un ejecutable de varios gigabytes y ejecutarlo suena muy ineficiente.
Si entiendo tu problema correctamente, usas algún tipo de generador de código, G, para generar un montón de funciones
func1...N
que toman un montón de mapascsc1...M
como entrada. Lo que quiere hacer es calcularcsc1...M
y ejecutar un ciclo de 1,000,000 de veces para diferentes entradas y cada vez encontrars = func1 + func2 + ... + funcN
. Sin embargo, no especificó cómofucn1...N
están relacionadoscsc1...M
.Si todo eso es cierto, parece que debería poder darle la vuelta al problema de una manera diferente, lo que potencialmente puede ser mucho más manejable e incluso posiblemente más rápido (es decir, dejar que el caché de su máquina funcione realmente).
Además del problema práctico de los tamaños de los archivos de objetos, su programa actual no será eficiente ya que no localiza el acceso a los datos (demasiados mapas enormes) y no tiene ejecución de código localizado (demasiadas funciones muy largas).
¿Qué tal si divide su programa en 3 fases: Fase 1, compile
csc1...M
y almacene. Fase 2 compile unofunc
a la vez, ejecútelo 1,000,000 veces con cada entrada y almacene los resultados. La fase 3 encuentra la suma de los resultados de losfunc1...N
resultados almacenados para cada ejecución de 1.000.000 de veces. Lo bueno de esta solución es que se puede hacer fácilmente en paralelo en varias máquinas independientes.Editar: @bbtrb, ¿podrías hacer que una función y una csc estén disponibles en algún lugar? Parecen ser muy regulares y comprimibles. Por ejemplo, func1 parece ser solo una suma de expresiones, cada una de las cuales consta de 1 coeficiente, 2 índices para las variables en sy 1 índice en csc. Por lo que se puede reducir a un bonito bucle. Si ofrece ejemplos completos, estoy seguro de que se pueden encontrar formas de comprimirlos en bucles en lugar de expresiones largas.
fuente
func
depende de casi todoscsc
y esos números deben calcularse también 10 ^ 6 veces. 2. La entrada se obtendrá de un integrador de Monte Carlo adaptativo, lo que significa que el integrador debe conocer el resultado completo en cada punto para poder reducir el error resultante refinando la malla en las proximidades del punto si es necesario. 3. Las expresiones grandes paracsc
persistir ...csc
en cada iteración independientemente de los demás? Si fueran independientes, aún podría ejecutar cada uno 10 ^ 6 veces y almacenar los resultados. Sin embargo, si hay dependencias entre ellos, tal vez necesite averiguar cuál está relacionado con cuál, algo así como un gráfico de dependencia, y luego tratar de ver si puede dividirlo en múltiples subgráficos independientes. Considerándolo todo, creo que la clave es dividir el problema en múltiples subproblemas independientes.Si leo tus errores correctamente, lo que te hace traspasar el límite es la sección de datos inicializados (si fuera el código, tendrías muchos más errores en mi humilde opinión). ¿Tiene grandes conjuntos de datos globales? Si es el caso, reestructuraría el programa para que se asignen dinámicamente. Si se inicializan los datos, los leería de un archivo de configuración.
Por cierto, viendo esto:
Creo que tienes otro problema.
fuente
Me parece que el código está realizando una integración numérica utilizando algún tipo de método de profundidad adaptativo. Desafortunadamente, el generador de código (o más bien el autor del generador de código) es tan estúpido como para generar una función por parche en lugar de una por tipo de parche. Como tal, ha producido demasiado código para ser compilado, e incluso si pudiera compilarse, su ejecución sería dolorosa porque nunca se ha compartido nada en ningún lugar. (¿Puede imaginarse el dolor que resulta de tener que cargar cada página de código objeto desde el disco porque nunca se comparte nada y, por lo tanto, siempre es un candidato para que el sistema operativo lo desaloje? Por no hablar de los cachés de instrucciones, que serán inútiles).
La solución es dejar de desenrollar todo; para este tipo de código, desea maximizar el uso compartido, ya que la sobrecarga de instrucciones adicionales para acceder a datos en patrones más complejos será absorbida por el costo de lidiar con el (presumiblemente) gran conjunto de datos subyacente de todos modos. También es posible que el generador de código incluso haga esto de forma predeterminada, y que el científico haya visto algunas opciones para desenrollar (con la nota de que a veces mejoran la velocidad) y las haya activado todas a la vez y ahora insiste en que se acepte este desastre resultante. por la computadora, en lugar de aceptar las restricciones reales de la máquina y usar la versión numéricamente correcta que se genera por defecto. Pero si el generador de código no lo hace, obtenga uno que lo haga (o piratee el código existente).
La conclusión: compilar y vincular 2,8 GB de código no funciona y no debería obligarse a hacerlo. Encontrar otra manera.
fuente
Un par de sugerencias: - Optimice el tamaño (-Os). Realice sus llamadas de función en línea, llamadas de función normales. Habilite la agrupación de cadenas.
Intente dividir las cosas en diferentes DLL (objetos compartidos, .so para linux, .dylib para Mac OS X). Asegúrese de que se puedan descargar. Luego, implemente algo para cargar cosas bajo demanda y libérelas cuando no las necesite.
Si no es así, divida su código en diferentes ejecutables y use algo para comunicarse entre ellos (tuberías, sockets, incluso escritura / lectura en un archivo). Torpe, pero ¿qué opciones tienes?
Totalmente alternativo: - Utilizar un lenguaje dinámico con JIT . Justo encima de mi cabeza, use LuaJIT , y reescriba (¿regenerar?) Muchas de estas expresiones en Lua , u otros lenguajes y tiempos de ejecución similares que permiten que el código se recolecte basura.
LuaJIT es bastante eficiente, a veces superando a C / C ++ para ciertas cosas, pero a menudo muy cerca (a veces puede ser lento debido a la mala recolección de basura que aún existe). Compruébelo usted mismo:
http://luajit.org/performance_x86.html
Descargue el
scimark2.lua
archivo desde allí y compárelo con la versión "C" (busque en Google); a menudo, los resultados son muy parecidos.fuente
El enlazador está intentando generar desplazamientos de reubicación de 32 bits dentro de un binario que de alguna manera ha superado estas limitaciones. Intente reducir los requisitos de espacio de direcciones del programa principal.
¿Puede dividir parte o la mayor parte del código objeto en una o más bibliotecas (también compiladas con -fpic / -fPIC)? Luego genere un binario no estático que se vincule con estas bibliotecas. Las bibliotecas vivirán en bloques de memoria discretos y sus compensaciones de reubicación serán dinámicas / absolutas (64 bits) en lugar de relativas (32 bits).
fuente
Esas expresiones me parecen una serie alternante. No sé cómo se ve el resto del código, pero no parece que sea tan difícil derivar la expresión generadora. Probablemente también valdría la pena en el momento de la ejecución, especialmente si tiene 2.8 GB de código sin enrollar de 2 KB.
fuente
Esto parece el resultado de la generación de código que salió mal, quizás por álgebra simbólica y / o desenrollado manual. Es bien sabido que las manipulaciones simbólicas crecen exponencialmente en la profundidad del árbol de expresión o gráfico computacional. Es probable que aquí se pueda usar la diferenciación automática, lo que haría que el tamaño del código fuera bastante pequeño y también aceleraría la ejecución de manera espectacular.
fuente