Tenho um grande número de funções, totalizando cerca de 2,8 GB de código-objeto (infelizmente não há como contornar, computação científica ...)
Quando tento vinculá-los, obtenho relocation truncated to fit: R_X86_64_32S
erros (esperados) que esperava contornar especificando o sinalizador do compilador -mcmodel=medium
. Todas as bibliotecas vinculadas, além das quais eu tenho controle, são compiladas com o -fpic
sinalizador.
Mesmo assim, o erro persiste, e suponho que algumas bibliotecas que vinculo não sejam compiladas com PIC.
Aqui está o erro:
/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
E as bibliotecas do sistema com as quais vinculo:
-lgfortran -lm -lrt -lpthread
Alguma pista de onde procurar o problema?
EDIT: Em primeiro lugar, obrigado pela discussão ... Para esclarecer um pouco, eu tenho centenas de funções (cada uma com aproximadamente 1 MB de tamanho em arquivos 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;
}
O objeto s
é relativamente pequeno e mantém as constantes necessárias x14, x15, ..., ds0, ..., etc. enquanto ti
apenas retorna um duplo de uma biblioteca externa. Como você pode ver, csc[]
é um mapa pré-computado de valores que também é avaliado em arquivos de objetos separados (novamente centenas com cerca de 1 MB de tamanho cada) da seguinte 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));
}
// ...
}
É sobre isso. A etapa final então consiste apenas em chamar todos aqueles func[i]
e somar o resultado.
Quanto ao fato de que este é um caso bastante especial e incomum: Sim, é. É com isso que as pessoas precisam lidar ao tentar fazer cálculos de alta precisão para a física de partículas.
EDIT2: Devo também acrescentar que x12, x13, etc. não são realmente constantes. Eles são definidos com valores específicos, todas essas funções são executadas e o resultado retornado e, em seguida, um novo conjunto de x12, x13, etc. é escolhido para produzir o próximo valor. E isso deve ser feito 10 ^ 5 a 10 ^ 6 vezes ...
EDIT3: Obrigado pelas sugestões e pela discussão até agora ... Vou tentar rolar os loops na geração de código de alguma forma, não tenho certeza de como fazer isso exatamente, para ser honesto, mas esta é a melhor aposta.
BTW, eu não tentei me esconder atrás de "isso é computação científica - nenhuma maneira de otimizar". É que a base desse código é algo que sai de uma "caixa preta" à qual não tenho acesso real e, além disso, a coisa toda funcionou muito bem com exemplos simples, e principalmente me sinto sobrecarregado com o que acontece em uma realidade aplicação mundial ...
EDIT4: Então, consegui reduzir o tamanho do código das csc
definições em cerca de um quarto, simplificando as expressões em um sistema de álgebra computacional ( Mathematica ). Vejo agora também uma maneira de reduzi-lo em outra ordem de magnitude ou mais, aplicando alguns outros truques antes de gerar o código (o que reduziria essa parte para cerca de 100 MB) e espero que essa ideia funcione.
Agora relacionado às suas respostas: Estou tentando rolar os loops de novo no func
s, onde um CAS não vai ajudar muito, mas já tenho algumas idéias. Por exemplo, classificando as expressões por variáveis como x12, x13,...
, analise os csc
s com Python e gere tabelas que os relacionem entre si. Então, posso pelo menos gerar essas partes como loops. Como esta parece ser a melhor solução até agora, eu a considero a melhor resposta.
No entanto, gostaria de dar crédito também a VJo. O GCC 4.6 realmente funciona muito melhor, produz código menor e é mais rápido. Usar o modelo grande funciona no código como está. Então, tecnicamente, essa é a resposta correta, mas mudar todo o conceito é uma abordagem muito melhor.
Obrigado a todos por suas sugestões e ajuda. Se alguém tiver interesse, postarei o resultado final assim que estiver pronto.
OBSERVAÇÕES: Apenas algumas observações para algumas outras respostas: O código que estou tentando executar não se origina em uma expansão de funções / algoritmos simples e estúpidos desdobramentos desnecessários. O que realmente acontece é que começamos com objetos matemáticos bastante complicados e trazê-los para uma forma computável numericamente gera essas expressões. O problema está, na verdade, na teoria física subjacente. A complexidade das expressões intermediárias escala fatorialmente, o que é bem conhecido, mas quando combinamos tudo isso com algo fisicamente mensurável - um observável - ela se reduz a apenas um punhado de funções muito pequenas que formam a base das expressões. (Definitivamente, há algo "errado" a esse respeito com o geral e apenas disponívelansatz que é chamado de "teoria da perturbação". Tentamos trazer este ansatz para outro nível, que não é mais viável analiticamente e onde a base das funções necessárias não é conhecida. Então, tentamos usar a força bruta assim. Não é a melhor maneira, mas espero que ajude no nosso entendimento da física em questão no final ...
ÚLTIMA EDIÇÃO:
Obrigado a todas as suas sugestões, eu consegui reduzir o tamanho do código consideravelmente, usando o Mathematica e uma modificação do gerador de código para os func
s um pouco ao longo das linhas da resposta principal :)
Simplifiquei as csc
funções com o Mathematica, diminuindo para 92 MB. Esta é a parte irredutível. As primeiras tentativas demoraram uma eternidade, mas depois de algumas otimizações, isso agora é executado em cerca de 10 minutos em uma única CPU.
O efeito sobre os func
s foi dramático: o tamanho do código inteiro para eles caiu para aproximadamente 9 MB, então o código agora totaliza na faixa de 100 MB. Agora faz sentido ativar as otimizações e a execução é bastante rápida.
Mais uma vez, obrigado a todos por suas sugestões, aprendi muito.
fonte
mmap
, retirá- los de um binário externo no tempo de execução.Respostas:
Então, você já tem um programa que produz este texto:
e
certo?
Se todas as suas funções tiverem um "formato" semelhante (multiplique n números m vezes e adicione os resultados - ou algo semelhante), então acho que você pode fazer isso:
offsetof(ProcessVars, ds0)
O avaliador array + representará a mesma lógica de uma de suas funções, mas apenas o avaliador será código. A matriz é "dados" e pode ser gerada em tempo de execução ou salva em disco e ler pedaços i ou com um arquivo mapeado na memória.
Para o seu exemplo particular em func1 imaginar como você iria reescrever a função através de um avaliador se você tivesse acesso ao endereço de base
s
ecsc
e também um vector como representação das constantes e os deslocamentos que você precisa para adicionar aos endereços base para chegar ax14
,ds8
ecsc[51370]
Você precisa criar uma nova forma de "dados" que descreverá como processar os dados reais que você passa para seu grande número de funções.
fonte
O x86-64 ABI usado pelo Linux define um "modelo grande" especificamente para evitar tais limitações de tamanho, que inclui tipos de realocação de 64 bits para GOT e PLT. (Consulte a tabela na seção 4.4.2 e as sequências de instruções em 3.5.5 que mostram como são usadas.)
Como suas funções ocupam 2,8 GB, você está sem sorte, porque o gcc não oferece suporte a modelos grandes. O que você pode fazer é reorganizar seu código de forma que permita dividi-lo em bibliotecas compartilhadas que você vincularia dinamicamente.
Se isso não for possível, como alguém sugeriu, em vez de colocar seus dados em código (compilando e vinculando), por ser enorme, você pode carregá-lo em tempo de execução (tanto como um arquivo normal, ou você pode mmapá-lo).
EDITAR
Parece que o modelo grande é compatível com o gcc 4.6 (consulte esta página ). Você pode tentar isso, mas o acima ainda se aplica à reorganização de seu código.
fonte
Com um programa desse lado, as perdas de cache para o código muito provavelmente excederão os custos de loop em tempo de execução. Eu recomendo que você volte ao seu gerador de código e faça com que ele gere alguma representação compacta para o que deseja avaliar (ou seja, uma que provavelmente caiba no D-cache) e, em seguida, execute-a com um interpretador em seu programa. Você também pode ver se pode fatorar kernels menores que ainda têm um número significativo de operações e, em seguida, usá-los como 'instruções' no código interpretado.
fonte
O erro ocorre porque você tem muito CODE, não dados! Isso é indicado, por exemplo
__libc_csu_fini
(que é uma função) sendo referenciado de_start
e a realocação é truncada para caber. Isso significa que_start
(o verdadeiro ponto de entrada do programa) está tentando chamar essa função por meio de um deslocamento SIGNED de 32 bits, que possui apenas um intervalo de 2 GB. Como a quantidade total de seu código-objeto é de ~ 2,8 GB, verifique os fatos.Se você pudesse reprojetar suas estruturas de dados, muito do seu código poderia ser "compactado" reescrevendo as grandes expressões como loops simples.
Além disso, você pode calcular
csc[]
em um programa diferente, armazenar os resultados em um arquivo e apenas carregá-los quando necessário.fonte
csc[]
tem que ser calculado com muita frequência e eu gostaria de evitar E / S de disco.func1
cima, algo como:for (int i = 0; i < N; ++i) expr += constants[i].*s.x14*s.x15*csc[49300 + i];
.Acho que todos concordam que deve haver uma maneira diferente de fazer o que você deseja. Compilar centenas de megabytes (gigabytes?) De código, vinculá-lo a um executável de vários gigabytes e executá-lo parece muito ineficiente.
Se entendi seu problema corretamente, você usa algum tipo de gerador de código, G, para gerar um monte de funções
func1...N
que recebem um monte de mapascsc1...M
como entrada. O que você quer fazer é calcularcsc1...M
e executar um loop de 1.000.000 de vezes para diferentes entradas e cada vez que encontrars = func1 + func2 + ... + funcN
. Você não especificou comofucn1...N
estão relacionados aocsc1...M
embora.Se tudo isso for verdade, parece que você deve ser capaz de virar o problema de cabeça para baixo de uma maneira que pode ser potencialmente muito mais gerenciável e até possivelmente mais rápida (ou seja, permitindo que o cache de sua máquina realmente funcione).
Além do problema prático dos tamanhos dos arquivos de objeto, seu programa atual não será eficiente, uma vez que não localiza o acesso aos dados (muitos mapas enormes) e não tem execução de código localizado (muitas funções muito longas).
Que tal dividir seu programa em 3 fases: Fase 1, construir
csc1...M
e armazená-los. A fase 2 constrói um defunc
cada vez, executa 1.000.000 de vezes com cada entrada e armazena os resultados. A Fase 3 encontre a soma dos resultados dosfunc1...N
resultados armazenados para cada execução de 1.000.000 vezes. A parte boa dessa solução é que ela pode ser facilmente feita em paralelo em várias máquinas independentes.Edit: @bbtrb, você poderia disponibilizar uma função e um csc em algum lugar? Eles parecem ser altamente regulares e compressíveis. Por exemplo, func1 parece ser apenas uma soma de expressões, cada uma consistindo em 1 coeficiente, 2 índices para as variáveis em s e 1 índice em csc. Portanto, pode ser reduzido a um belo loop. Se você disponibilizar exemplos completos, tenho certeza de que podem ser encontradas maneiras de compactá-los em loops em vez de em expressões longas.
fonte
func
s dependem de quase todos oscsc
s e esses números também devem ser calculados 10 ^ 6 vezes. 2. A entrada será obtida de um integrador Monte Carlo adaptativo, o que significa que o integrador deve saber o resultado completo em cada ponto para poder reduzir o erro resultante refinando a malha nas proximidades do ponto, se necessário. 3. As expressões grandes paracsc
persist ...csc
em cada iteração independentemente das outras? Se eles fossem independentes, você ainda poderia executar cada um 10 ^ 6 vezes e armazenar os resultados. No entanto, se houver dependências entre eles, talvez você precise descobrir qual está relacionada a qual, algo como um gráfico de dependência, e então tentar ver se você pode dividi-lo em vários subgráficos independentes. Em suma, acho que a chave é dividir o problema em subproblemas múltiplos e independentes.Se eu ler seus erros corretamente, o que o faz ultrapassar o limite é a seção de dados inicializados (se fosse o código, você teria muito mais erros IMHO). Você tem grandes matrizes de dados globais? Se for o caso, reestruturarei o programa para que sejam alocados dinamicamente. Se os dados forem inicializados, eu os leria de um arquivo de configuração.
BTW vendo isso:
Acho que você tem outro problema.
fonte
Parece-me que o código está fazendo integração numérica usando algum tipo de método de profundidade adaptativo. Infelizmente, o gerador de código (ou melhor, o autor do gerador de código) é tão estúpido a ponto de gerar uma função por patch em vez de uma por tipo de patch. Como tal, ele produziu muito código para ser compilado e, mesmo que pudesse ser compilado, sua execução seria dolorosa porque nada é compartilhado em qualquer lugar. (Você pode imaginar a dor resultante de ter que carregar cada página de código-objeto do disco porque nada é compartilhado e, portanto, é sempre um candidato para o sistema operacional despejar. Para não falar dos caches de instrução, que serão inúteis.)
A solução é parar de desenrolar tudo; para este tipo de código, você deseja maximizar o compartilhamento já que a sobrecarga de instruções extras para acessar dados em padrões mais complexos será absorvida pelo custo de lidar com o (presumivelmente) grande conjunto de dados subjacente de qualquer maneira. Também é possível que o gerador de código até faça isso por padrão, e que o cientista tenha visto algumas opções para desenrolar (com a observação de que isso às vezes melhora a velocidade) e ligue-as todas de uma vez e agora está insistindo que essa bagunça resultante seja aceita pelo computador, em vez de aceitar as restrições reais da máquina e usar a versão numericamente correta que é gerada por padrão. Mas se o gerador de código não funcionar, obtenha um que o faça (ou hackear o código existente).
O resultado final:Resumindo compilar e vincular 2.8 GB de código não funciona e não deve ser forçado a funcionar. Encontre outra maneira.
fonte
Algumas sugestões: - Otimize para o tamanho (-Os). Faça suas chamadas de função inline, chamadas de função normais. Habilite o agrupamento de strings.
Tente dividir as coisas em diferentes DLLs (objetos compartilhados, .so para linux, .dylib para Mac OS X). Certifique-se de que eles podem ser descarregados. Em seguida, implemente algo para carregar coisas sob demanda e liberte-as quando não for necessário.
Se não, divida seu código em diferentes executáveis e use algo para se comunicar entre eles (pipes, sockets, até mesmo escrever / ler em arquivo). Desajeitado, mas quais opções você tem?
Totalmente alternativo: - Use uma linguagem dinâmica com JIT . Bem em cima da minha cabeça - usar LuaJIT - e reescrever (regenerar?) Muitas dessas expressões em Lua , ou outras linguagens e tempos de execução que permitem que o código seja coletado como lixo.
LuaJIT é bastante eficiente, às vezes superando C / C ++ em certas coisas, mas geralmente muito próximo (às vezes pode ser lento devido à coleta de lixo deficiente que ainda existe). Verifique você mesmo:
http://luajit.org/performance_x86.html
Baixe o
scimark2.lua
arquivo de lá e compare-o com a versão "C" (google it) - geralmente os resultados são muito próximos.fonte
O vinculador está tentando gerar deslocamentos de realocação de 32 bits em um binário que de alguma forma excedeu essas limitações. Tente reduzir os requisitos de espaço de endereço do programa principal.
Você pode dividir parte / a maioria do código-objeto em uma ou mais bibliotecas (também compiladas com -fpic / -fPIC)? Em seguida, gere um binário não estático vinculado a essas bibliotecas. As bibliotecas viverão em blocos de memória discretos e seus deslocamentos de realocação serão dinâmicos / absolutos (64 bits) em vez de relativos (32 bits).
fonte
Essas expressões parecem muito com uma série alternada para mim. Não sei como o resto do código se parece, mas não parece que seja tão difícil derivar a expressão geradora. Provavelmente valeria a pena em tempo de execução também, especialmente se você tiver 2,8 GB de código desenrolado de 2 KB.
fonte
Isso parece o resultado da geração de código que deu errado, talvez por álgebra simbólica e / ou desenrolamento manual. As manipulações simbólicas são bem conhecidas por crescer exponencialmente na profundidade da árvore de expressão ou gráfico computacional. É provável que a diferenciação automática possa ser usada aqui, o que tornaria o tamanho do código bastante pequeno e também aceleraria drasticamente a execução.
fonte