Tentei comparar o desempenho da linguagem assembly embutida e do código C ++, então escrevi uma função que adiciona duas matrizes de tamanho 2000 por 100000 vezes. Aqui está o código:
#define TIMES 100000
void calcuC(int *x,int *y,int length)
{
for(int i = 0; i < TIMES; i++)
{
for(int j = 0; j < length; j++)
x[j] += y[j];
}
}
void calcuAsm(int *x,int *y,int lengthOfArray)
{
__asm
{
mov edi,TIMES
start:
mov esi,0
mov ecx,lengthOfArray
label:
mov edx,x
push edx
mov eax,DWORD PTR [edx + esi*4]
mov edx,y
mov ebx,DWORD PTR [edx + esi*4]
add eax,ebx
pop edx
mov [edx + esi*4],eax
inc esi
loop label
dec edi
cmp edi,0
jnz start
};
}
Aqui está main()
:
int main() {
bool errorOccured = false;
setbuf(stdout,NULL);
int *xC,*xAsm,*yC,*yAsm;
xC = new int[2000];
xAsm = new int[2000];
yC = new int[2000];
yAsm = new int[2000];
for(int i = 0; i < 2000; i++)
{
xC[i] = 0;
xAsm[i] = 0;
yC[i] = i;
yAsm[i] = i;
}
time_t start = clock();
calcuC(xC,yC,2000);
// calcuAsm(xAsm,yAsm,2000);
// for(int i = 0; i < 2000; i++)
// {
// if(xC[i] != xAsm[i])
// {
// cout<<"xC["<<i<<"]="<<xC[i]<<" "<<"xAsm["<<i<<"]="<<xAsm[i]<<endl;
// errorOccured = true;
// break;
// }
// }
// if(errorOccured)
// cout<<"Error occurs!"<<endl;
// else
// cout<<"Works fine!"<<endl;
time_t end = clock();
// cout<<"time = "<<(float)(end - start) / CLOCKS_PER_SEC<<"\n";
cout<<"time = "<<end - start<<endl;
return 0;
}
Então eu executo o programa cinco vezes para obter os ciclos do processador, que podem ser vistos como tempo. Cada vez que chamo apenas uma das funções mencionadas acima.
E aí vem o resultado.
Função da versão de montagem:
Debug Release
---------------
732 668
733 680
659 672
667 675
684 694
Average: 677
Função da versão C ++:
Debug Release
-----------------
1068 168
999 166
1072 231
1002 166
1114 183
Average: 182
O código C ++ no modo de liberação é quase 3,7 vezes mais rápido que o código do assembly. Por quê?
Acho que o código do assembly que escrevi não é tão eficaz quanto os gerados pelo GCC. É difícil para um programador comum como eu escrever código mais rápido que seu oponente gerado por um compilador. Isso significa que eu não devo confiar no desempenho da linguagem assembly escrita por minhas mãos, focar em C ++ e esquecer a linguagem assembly?
fonte
Respostas:
Sim, na maioria das vezes.
Antes de tudo, você começa com a suposição errada de que uma linguagem de baixo nível (assembly neste caso) sempre produzirá código mais rápido que a linguagem de alto nível (C ++ e C neste caso). Não é verdade. O código C é sempre mais rápido que o código Java? Não, porque existe outra variável: programador. A maneira como você escreve código e o conhecimento dos detalhes da arquitetura influenciam muito o desempenho (como você viu neste caso).
Você sempre pode produzir um exemplo em que o código de montagem artesanal é melhor que o código compilado, mas geralmente é um exemplo fictício ou uma única rotina, não um verdadeiro programa de mais de 500.000 linhas de código C ++. Eu acho que compiladores irá produzir melhor montagem código de 95% vezes e , por vezes, apenas algumas raras vezes, você pode precisar de escrever montagem código para alguns curta,, muito utilizado , desempenho crítico rotinas ou quando você tem para acessar os recursos sua linguagem favorita de alto nível não expõe. Você quer um toque dessa complexidade? Leia esta resposta incrível aqui no SO.
Porque isso?
Antes de tudo, porque os compiladores podem fazer otimizações que nem imaginamos (veja esta lista curta ) e elas farão em segundos (quando precisarmos de dias ).
Ao codificar na montagem, é necessário criar funções bem definidas com uma interface de chamada bem definida. No entanto, eles podem levar em consideração a otimização de todo o programa e a otimização entre procedimentos , como alocação de registros , propagação constante , eliminação de subexpressão comum , programação de instruções e outras otimizações complexas e não óbvias ( modelo Polytope , por exemplo). Na arquitetura RISC , os caras pararam de se preocupar com isso há muitos anos (o agendamento de instruções, por exemplo, é muito difícil de ajustar manualmente ) e as CPUs CISC modernas têm pipelines muito longos também.
Para alguns microcontroladores complexos, até as bibliotecas do sistema são escritas em C, em vez de assembly, porque seus compiladores produzem um código final melhor (e fácil de manter).
Às vezes, os compiladores podem usar automaticamente algumas instruções do MMX / SIMDx e, se você não as usar, simplesmente não pode comparar (outras respostas já revisaram muito bem o seu código de montagem). Apenas para loops, esta é uma pequena lista de otimizações de loop do que é comumente verificado por um compilador (você acha que poderia fazê-lo sozinho quando sua agenda foi decidida para um programa C #?) Se você escrever algo em assembly, eu pense que você deve considerar pelo menos algumas otimizações simples . O exemplo de livro escolar para matrizes é desenrolar o ciclo (seu tamanho é conhecido no momento da compilação). Faça isso e execute seu teste novamente.
Hoje em dia também é realmente incomum precisar usar a linguagem assembly por outro motivo: a infinidade de CPUs diferentes . Deseja apoiar todos eles? Cada um possui uma microarquitetura específica e alguns conjuntos de instruções específicos . Eles têm um número diferente de unidades funcionais e as instruções de montagem devem ser organizadas para mantê-las todas ocupadas . Se você escreve em C, pode usar o PGO, mas no assembly precisará de um grande conhecimento dessa arquitetura específica (e repensar e refazer tudo para outra arquitetura ). Para tarefas pequenas, o compilador geralmente melhora e, para tarefas complexas, geralmente o trabalho não é reembolsado (ecompilador pode fazer melhor de qualquer maneira).
Se você se sentar e der uma olhada no seu código, provavelmente verá que ganhará mais para redesenhar seu algoritmo do que traduzir para assembly (leia este ótimo post aqui no SO ), há otimizações de alto nível (e dicas para o compilador), você pode aplicar efetivamente antes de precisar recorrer à linguagem assembly. Provavelmente vale a pena mencionar que muitas vezes usando intrínsecos você terá um ganho de desempenho que está procurando e o compilador ainda poderá executar a maioria de suas otimizações.
Tudo isso dito, mesmo quando você pode produzir um código de montagem 5 a 10 vezes mais rápido, pergunte aos seus clientes se eles preferem pagar uma semana do seu tempo ou comprar uma CPU 50 $ mais rápida . A otimização extrema mais frequentemente do que não (e especialmente em aplicativos LOB) simplesmente não é necessária para a maioria de nós.
fonte
Seu código de montagem é abaixo do ideal e pode ser aprimorado:
loop
instrução, que é conhecida como lenta nas CPUs mais modernas (possivelmente um resultado do uso de um livro de montagem antigo *)Portanto, a menos que você melhore bastante seu conjunto de habilidades em relação ao assembler, não faz sentido escrever código do assembler para desempenho.
* É claro que não sei se você realmente recebeu as
loop
instruções de um livro antigo de montagem. Mas você quase nunca o vê no código do mundo real, como todo compilador por aí é inteligente o suficiente para não emitirloop
, você o vê apenas em livros ruins e desatualizados da IMHO.fonte
loop
(e muitos "obsoleta" instruções) se você otimizar para tamanhoMesmo antes de se aprofundar na montagem, existem transformações de código que existem em um nível superior.
pode ser transformado em rotação de loop :
o que é muito melhor no que diz respeito à localização da memória.
Isso pode ser otimizado ainda mais, fazer
a += b
X vezes é equivalente a fazê-a += X * b
lo, obtemos:no entanto, parece que meu otimizador favorito (LLVM) não realiza essa transformação.
[editar] Eu descobri que a transformação é executada se tivéssemos o
restrict
qualificador parax
ey
. Na verdade, sem essa restrição,x[j]
ey[j]
poderia alias para o mesmo local que torna esta transformação errônea. [fim de edição]De qualquer forma, acho que essa é a versão C otimizada. Já é muito mais simples. Com base nisso, aqui está o meu crack no ASM (deixei Clang gerá-lo, sou inútil):
Receio não entender de onde vêm todas essas instruções, mas você sempre pode se divertir e tentar ver como elas se comparam ... mas ainda assim usaria a versão C otimizada em vez da montagem, no código, muito mais portável.
fonte
x
ey
. Ou seja, o compilador não pode ter certeza que para todosi,j
no[0, length)
que temosx + i != y + j
. Se houver sobreposição, a otimização é impossível. A linguagem C introduziu arestrict
palavra-chave para informar ao compilador que dois ponteiros não podem usar o pseudônimo, no entanto, ele não funciona para matrizes, porque ainda podem se sobrepor, mesmo que não façam exatamente o pseudônimo.__restrict
). O SSE2 é a linha de base para x86-64 e, com o embaralhamento, o SSE2 pode fazer multiplicações 2x de 32 bits de uma só vez (produzindo produtos de 64 bits, daí o embaralhamento para reunir os resultados). godbolt.org/z/r7F_uo . (SSE4.1 é necessário parapmulld
: 32x32 compactado => multiplicação de 32 bits). O GCC tem um truque para transformar multiplicadores inteiros constantes em shift / add (e / ou subtrair), o que é bom para multiplicadores com poucos bits definidos. O código de embaralhamento pesado de Clang vai prejudicar o rendimento de embaralhamento nas CPUs Intel.Resposta curta: sim.
Resposta longa: sim, a menos que você realmente saiba o que está fazendo e tenha um motivo para fazê-lo.
fonte
Eu corrigi meu código asm:
Resultados para a versão Release:
O código de montagem no modo de liberação é quase duas vezes mais rápido que o C ++.
fonte
xmm0
em vez demm0
), você vai ter outra aceleração por um fator de dois ;-)paddd xmm
(após verificar a sobreposição entrex
ey
, porque você não usouint *__restrict x
). Por exemplo, o gcc faz isso: godbolt.org/z/c2JG0- . Ou depois de fazer a inclusãomain
, não é necessário verificar a sobreposição, pois pode ver a alocação e provar que não há sobreposição. (E também assumirá o alinhamento de 16 bytes em algumas implementações x86-64, o que não é o caso da definição autônoma.) E se você compilargcc -O3 -march=native
, poderá obter 256 ou 512 bits vetorização.Sim, é exatamente isso que significa, e é verdade para todos idiomas. Se você não sabe escrever código eficiente no idioma X, não deve confiar na sua capacidade de escrever código eficiente no X. Portanto, se você deseja código eficiente, deve usar outro idioma.
A montagem é particularmente sensível a isso, porque, bem, o que você vê é o que obtém. Você escreve as instruções específicas que deseja que a CPU execute. Com linguagens de alto nível, existe um compilador no betweeen, que pode transformar seu código e remover muitas ineficiências. Com a montagem, você está por sua conta.
fonte
Atualmente, o único motivo para usar a linguagem assembly é usar alguns recursos não acessíveis pelo idioma.
Isso se aplica á:
Mas os compiladores atuais são bastante inteligentes; eles podem até substituir duas instruções separadas, como
d = a / b; r = a % b;
por uma única instrução que calcula a divisão e o restante de uma só vez, se estiver disponível, mesmo que C não possua esse operador.fonte
É verdade que um compilador moderno faz um trabalho incrível na otimização de código, mas eu ainda o encorajo a continuar aprendendo assembly.
Antes de tudo, você claramente não se sente intimidado por isso ; isso é uma ótima, ótima vantagem, a seguir - você está no caminho certo ao criar um perfil para validar ou descartar suas suposições de velocidade , você está pedindo informações de pessoas experientes e você tem a maior ferramenta de otimização conhecida pela humanidade: um cérebro .
À medida que sua experiência aumenta, você aprenderá quando e onde usá-lo (geralmente os loops mais estreitos e internos do seu código, depois de otimizar profundamente em nível algorítmico).
Para inspiração, recomendo que você procure os artigos de Michael Abrash (se você não ouviu falar dele, ele é um guru da otimização; ele até colaborou com John Carmack na otimização do renderizador do software Quake!)
fonte
Eu mudei o código asm:
Resultados para a versão Release:
O código de montagem no modo de liberação é quase 4 vezes mais rápido que o C ++. IMHo, a velocidade do código de montagem depende do programador
fonte
shr ecx,2
é supérfluo, porque o comprimento da matriz já é fornecido emint
e não em byte. Então você basicamente alcança a mesma velocidade. Você pode tentar apaddd
resposta de harolds, isso será realmente mais rápido.é um tópico muito interessante!
Mudei o MMX por SSE no código de Sasha
Aqui estão meus resultados:
O código de montagem com SSE é 5 vezes mais rápido que o C ++
fonte
A maioria dos compiladores de idiomas de alto nível é muito otimizada e sabe o que está fazendo. Você pode tentar despejar o código de desmontagem e compará-lo com seu assembly nativo. Eu acredito que você verá alguns truques legais que seu compilador está usando.
Apenas por exemplo, mesmo que eu não tenha mais certeza :):
Fazendo:
custam mais ciclos do que
que faz a mesma coisa.
O compilador conhece todos esses truques e os usa.
fonte
O compilador venceu você. Vou tentar, mas não darei garantias. Vou assumir que a "multiplicação" por TIMES destina-se a fazer-lhe um teste de desempenho mais relevante, que
y
ex
são 16-alinhados, e quelength
é um diferente de zero múltiplo de 4. Isso é provavelmente tudo verdade de qualquer maneira.Como eu disse, não dou garantias. Mas ficarei surpreso se isso puder ser feito muito mais rapidamente - o gargalo aqui é a taxa de transferência de memória, mesmo que tudo seja um sucesso L1.
fonte
mov ecx, length, lea ecx,[ecx*4], mov eax,16... add ecx,eax
e depois usar [esi + ecx] em todos os lugares, evitará 1 travamento de ciclo por instrução, acelerando os lotes do loop. (Se você possui o Skylake mais recente, isso não se aplica). O add reg, reg apenas torna o loop mais apertado, o que pode ou não ajudar.Apenas implementar cegamente o mesmo algoritmo, instrução por instrução, em assembly é garantido que é mais lento do que o compilador pode fazer.
É porque mesmo a menor otimização que o compilador faz é melhor que o seu código rígido, sem nenhuma otimização.
Obviamente, é possível vencer o compilador, especialmente se for uma parte pequena e localizada do código, eu mesmo precisei fazer isso sozinho para obter um aprox. Acelere 4x, mas, neste caso, precisamos confiar muito no bom conhecimento do hardware e em vários truques aparentemente contra-intuitivos.
fonte
Como compilador, eu substituiria um loop com um tamanho fixo para muitas tarefas de execução.
vai produzir
e eventualmente saberá que "a = a + 0;" é inútil, portanto ele removerá essa linha. Espero que algo em sua cabeça esteja agora disposto a anexar algumas opções de otimização como um comentário. Todas essas otimizações muito eficazes tornarão a linguagem compilada mais rápida.
fonte
a
seja volátil, há uma boa chance de que o compilador faça issoint a = 13;
desde o início.É exatamente o que isso significa. Deixe as micro otimizações para o compilador.
fonte
Adoro este exemplo porque demonstra uma lição importante sobre código de baixo nível. Sim, você pode escrever uma montagem tão rápida quanto o seu código C. Isso é tautologicamente verdadeiro, mas não significa necessariamente nada. Claramente alguém pode, caso contrário, o montador não conhecerá as otimizações apropriadas.
Da mesma forma, o mesmo princípio se aplica à medida que você sobe na hierarquia de abstração da linguagem. Sim, você pode escrever um analisador em C tão rápido quanto um script perl rápido e sujo, e muitas pessoas o fazem. Mas isso não significa que, como você usou C, seu código será rápido. Em muitos casos, os idiomas de nível superior fazem otimizações que você talvez nunca tenha considerado.
fonte
Em muitos casos, a maneira ideal de executar alguma tarefa pode depender do contexto em que a tarefa é executada. Se uma rotina é escrita em linguagem assembly, geralmente não será possível que a sequência de instruções varie com base no contexto. Como um exemplo simples, considere o seguinte método simples:
Um compilador para código ARM de 32 bits, dado o exposto acima, provavelmente o renderizará como algo como:
ou talvez
Isso pode ser otimizado levemente no código montado à mão, como:
ou
Ambas as abordagens montadas manualmente exigiriam 12 bytes de espaço de código em vez de 16; o último substituiria uma "carga" por uma "adição", que executaria em um ARM7-TDMI dois ciclos mais rápidos. Se o código fosse executado em um contexto em que r0 não sabia / não se importa, as versões da linguagem assembly seriam assim um pouco melhores que a versão compilada. Por outro lado, suponha que o compilador sabia que algum registro [por exemplo, r5] manteria um valor dentro de 2047 bytes do endereço desejado 0x40001204 [por exemplo 0x40001000] e ainda sabia que outro registro [por exemplo, r7] para manter um valor cujos bits baixos fossem 0xFF. Nesse caso, um compilador pode otimizar a versão C do código para simplesmente:
Muito mais curto e rápido do que o código de montagem otimizado manualmente. Além disso, suponha que set_port_high ocorreu no contexto:
Não é de todo implausível ao codificar para um sistema incorporado. Se
set_port_high
estiver escrito no código do assembly, o compilador precisaria mover r0 (que retém o valor de retornofunction1
) para outro lugar antes de chamar o código do assembly e, em seguida, mover esse valor de volta para r0 posteriormente (poisfunction2
esperará seu primeiro parâmetro em r0), portanto, o código de montagem "otimizado" precisaria de cinco instruções. Mesmo que o compilador não soubesse de nenhum registro contendo o endereço ou o valor a ser armazenado, sua versão de quatro instruções (que poderia ser adaptada para usar os registros disponíveis - não necessariamente r0 e r1) venceria o assembly "otimizado" versão em vários idiomas. Se o compilador tiver o endereço e os dados necessários em r5 e r7, conforme descrito anteriormente, com uma única instrução -function1
não alteraria esses registros e, portanto, poderia substituirset_port_high
strb
quatro instruções menores e mais rápidas que o código de montagem "otimizado manualmente".Observe que o código de montagem otimizado manualmente pode superar um compilador nos casos em que o programador conhece o fluxo preciso do programa, mas os compiladores brilham nos casos em que um trecho de código é escrito antes de seu contexto ser conhecido ou onde um trecho de código-fonte pode ser invocado de vários contextos [se
set_port_high
for usado em cinquenta lugares diferentes no código, o compilador poderá decidir independentemente para cada um qual a melhor forma de expandi-lo].Em geral, eu sugeriria que a linguagem assembly é capaz de fornecer as maiores melhorias de desempenho nos casos em que cada parte do código pode ser abordada a partir de um número muito limitado de contextos e é prejudicial ao desempenho em locais onde uma parte da código pode ser abordado de muitos contextos diferentes. Curiosamente (e convenientemente) os casos em que a montagem é mais benéfica para o desempenho são geralmente aqueles em que o código é mais direto e fácil de ler. Os locais em que o código da linguagem assembly se tornaria uma bagunça pegajosa são geralmente aqueles em que a escrita em assembly ofereceria o menor benefício de desempenho.
[Nota secundária: existem alguns lugares onde o código de montagem pode ser usado para gerar uma bagunça pegajosa hiper otimizada; por exemplo, um pedaço de código que fiz para o ARM precisava buscar uma palavra da RAM e executar uma das cerca de doze rotinas com base nos seis bits superiores do valor (muitos valores mapeados para a mesma rotina). Eu acho que otimizei esse código para algo como:
O registrador r8 sempre mantinha o endereço da tabela principal de despacho (dentro do loop em que o código gasta 98% de seu tempo, nada o utilizava para qualquer outro propósito); todas as 64 entradas se referiam a endereços nos 256 bytes anteriores a ela. Como o loop primário tinha, na maioria dos casos, um limite de tempo de execução rígido de cerca de 60 ciclos, a busca e o despacho de nove ciclos foram muito úteis para atingir esse objetivo. O uso de uma tabela de 256 endereços de 32 bits seria um ciclo mais rápido, mas consumiria 1 KB de RAM muito preciosa [o flash teria adicionado mais de um estado de espera]. Usar 64 endereços de 32 bits exigiria adicionar uma instrução para mascarar alguns bits da palavra buscada e ainda assim devoraria 192 bytes a mais do que a tabela que realmente usei. O uso da tabela de compensações de 8 bits produziu código muito compacto e rápido, mas não é algo que eu esperaria que um compilador pudesse inventar; Eu também não esperaria que um compilador dedicasse um registro "em tempo integral" para manter o endereço da tabela.
O código acima foi projetado para funcionar como um sistema independente; poderia chamar periodicamente o código C, mas somente em determinados momentos em que o hardware com o qual estava se comunicando podia ser colocado com segurança em um estado "inativo" por dois intervalos de aproximadamente um milissegundo a cada 16ms.
fonte
Nos últimos tempos, todas as otimizações de velocidade que eu fiz foram substituir o código lento com dano cerebral por apenas um código razoável. Mas, como as coisas eram rápidas, a velocidade era realmente crítica e eu esforçava seriamente para tornar algo rápido, o resultado sempre foi um processo iterativo, em que cada iteração dava mais informações sobre o problema, encontrando maneiras de resolvê-lo com menos operações. A velocidade final sempre dependia de quanto insight eu chegava ao problema. Se em qualquer estágio eu usasse código de montagem ou código C otimizado demais, o processo de encontrar uma solução melhor teria sofrido e o resultado final seria mais lento.
fonte
Quando codifico no ASM, reorganizo as instruções manualmente para que a CPU possa executar mais delas em paralelo quando logicamente possível. Eu mal uso a RAM quando codifico no ASM, por exemplo: Pode haver mais de 20.000 linhas de código no ASM e nunca usei push / pop.
Você pode potencialmente pular no meio do código de operação para modificar o código e o comportamento sem a possível penalidade do código de modificação automática. O acesso a registros leva 1 tick (às vezes leva 0,25 ticks) da CPU. O acesso à RAM pode levar centenas.
Para minha última aventura no ASM, nunca usei a RAM para armazenar uma variável (para milhares de linhas de ASM). O ASM pode ser potencialmente inimaginavelmente mais rápido que o C ++. Mas isso depende de muitos fatores variáveis, como:
Agora estou aprendendo C # e C ++ porque percebi que a produtividade importa! Você pode tentar executar os programas mais rápidos que se possa imaginar usando o ASM puro sozinho no tempo livre. Mas, para produzir algo, use alguma linguagem de alto nível.
Por exemplo, o último programa que eu codifiquei estava usando JS e GLSL e nunca notei nenhum problema de desempenho, mesmo falando sobre JS, que é lento. Isso ocorre porque o mero conceito de programação da GPU para 3D torna a velocidade da linguagem que envia os comandos para a GPU quase irrelevante.
A velocidade da montadora sozinha no metal puro é irrefutável. Poderia ser ainda mais lento dentro do C ++? - Pode ser porque você está escrevendo código de montagem com um compilador que não usa um montador para começar.
Meu conselho pessoal é nunca escrever código de montagem se você puder evitá-lo, mesmo que eu goste de montagem.
fonte
Todas as respostas aqui parecem excluir um aspecto: às vezes não escrevemos código para atingir um objetivo específico, mas apenas por diversão . Pode não ser econômico investir tempo para fazê-lo, mas é possível que não exista maior satisfação do que vencer o snippet de código otimizado do compilador mais rápido em velocidade com uma alternativa ASM rolada manualmente.
fonte
Um compilador c ++, após a otimização no nível organizacional, produz código que utilizaria as funções internas da CPU de destino. A HLL nunca superará ou superará o montador por várias razões; 1.) A HLL será compilada e gerada com o código do acessador, verificação de limites e, possivelmente, coleta de lixo embutida (anteriormente abordando o escopo no maneirismo OOP), todos exigindo ciclos (inversões e falhas). Atualmente, o HLL faz um excelente trabalho (incluindo C ++ mais recente e outros como GO), mas se eles superam o assembler (ou seja, o seu código), você precisa consultar a documentação da CPU - as comparações com código superficial são certamente inconclusivas e os compilados como o assembler resolvem até o código operacional, a HLL abstrai os detalhes e não os elimina, caso contrário o aplicativo não será executado se for reconhecido pelo sistema operacional host.
A maioria dos códigos de assembler (principalmente objetos) são exibidos como "decapitados" para inclusão em outros formatos executáveis com muito menos processamento necessário, portanto, será muito mais rápido, mas muito mais inseguro; se um executável for emitido pelo assembler (NAsm, YAsm; etc.), ele ainda será executado mais rapidamente até corresponder completamente ao código HLL na funcionalidade, e os resultados poderão ser pesados com precisão.
Chamar um objeto de código baseado em assembler da HLL em qualquer formato adicionará inerentemente sobrecarga de processamento, além de chamadas de espaço de memória usando memória alocada globalmente para tipos de dados variáveis / constantes (isso se aplica a LLL e HLL). Lembre-se de que a saída final está usando a CPU como api e abi em relação ao hardware (opcode) e, tanto os montadores quanto os "compiladores HLL" são essencialmente / fundamentalmente idênticos, com a única exceção verdadeira sendo a legibilidade (gramatical).
A aplicação Hello World Console em assembler usando FAsm é de 1,5 KB (e isso é no Windows ainda menor no FreeBSD e Linux) e supera tudo o que o GCC pode jogar no seu melhor dia; razões são preenchimento implícito com nops, validação de acesso e verificação de limites para citar alguns. O objetivo real são as bibliotecas HLL limpas e um compilador otimizável que tem como alvo um processador de maneira "hardcore" e a maioria deles atualmente (finalmente). O GCC não é melhor que o YAsm - são as práticas de codificação e o entendimento do desenvolvedor que estão em questão e a "otimização" ocorre após a exploração iniciante, o treinamento e a experiência interinos.
Os compiladores precisam vincular e montar a saída no mesmo código de operação que um assembler, porque esses códigos são tudo o que uma CPU exclui (CISC ou RISC [PIC também]). O YAsm otimizou e limpou bastante o NAsm inicial, acelerando finalmente toda a saída desse montador, mas mesmo assim o YAsm ainda, como o NAsm, produz executáveis com dependências externas visando as bibliotecas de SO em nome do desenvolvedor, para que a milhagem possa variar. No fechamento, o C ++ está em um ponto incrível e muito mais seguro do que o assembler para mais de 80%, especialmente no setor comercial ...
fonte
ld
, mas isso não faz diferença, a menos que você esteja tentando otimizar o tamanho do arquivo (não apenas o tamanho do arquivo). segmento de texto). Veja um tutorial turbilhão sobre a criação de executáveis ELF realmente adolescentes para Linux .std::vector
compilado no modo de depuração. Matrizes C ++ não são assim. Os compiladores podem verificar as coisas em tempo de compilação, mas, a menos que você habilite opções adicionais de proteção, não há verificação em tempo de execução. Veja, por exemplo, uma função que incrementa os primeiros 1024 elementos de umint array[]
arg. A saída asm não possui verificações de tempo de execução: godbolt.org/g/w1HF5t . Tudo o que obtém é um ponteirordi
, sem informações de tamanho. Cabe ao programador para evitar um comportamento indefinido por não chamá-lo com um leque menor do que 1024.new
, exclua manualmente comdelete
, sem verificação de limites). Você pode usar o C ++ para produzir código asm / máquina cheio de merda (como a maioria dos softwares), mas isso é culpa do programador, não do C ++. Você pode até usaralloca
para alocar o espaço da pilha como uma matriz.g++ -O3
gerar código de verificação de limites para uma matriz simples ou fazer o que mais estiver falando. O C ++ facilita muito a geração de binários inchados (e, de fato, você precisa ter cuidado para não ter o objetivo de obter desempenho), mas isso não é literalmente inevitável. Se você entender como o C ++ é compilado para asm, é possível obter um código apenas um pouco pior do que você poderia escrever à mão, mas com inline e propagação constante em uma escala maior do que você poderia gerenciar manualmente.A montagem pode ser mais rápida se o seu compilador gerar muito código de suporte OO .
Editar:
Para os que rejeitam: o OP escreveu "devo ... focar em C ++ e esquecer a linguagem assembly?" e eu mantenho minha resposta. Você sempre precisa ficar de olho no código que o OO gera, principalmente ao usar métodos. Não esquecer a linguagem assembly significa que você revisará periodicamente o assembly que seu código OO gera, que eu acredito ser uma obrigação para escrever um software com bom desempenho.
Na verdade, isso pertence a todo código compilável, não apenas ao OO.
fonte