Eu tenho uma pergunta para todos os hackers de baixo nível hardcore por aí. Eu encontrei esta frase em um blog. Eu realmente não acho que a fonte importe (é Haack, se você realmente se importa) porque parece ser uma afirmação comum.
Por exemplo, muitos jogos 3D modernos têm seu mecanismo principal de alto desempenho escrito em C ++ e Assembly.
No que diz respeito ao assembly - o código é escrito em assembly porque você não quer um compilador emitindo instruções extras ou usando bytes excessivos, ou você está usando algoritmos melhores que você não pode expressar em C (ou não pode expressar sem o compilador bagunçando-os)?
Eu entendo completamente que é importante entender as coisas de baixo nível. Eu só quero entender o porquê do programa em assembly depois que você o entender.
fonte
Respostas:
Acho que você está interpretando mal esta declaração:
Os jogos (e a maioria dos programas hoje em dia) não são "escritos em conjunto" da mesma forma que são "escritos em C ++". Esse blog não está dizendo que uma fração significativa do jogo é projetada em assembly, ou que uma equipe de programadores senta e desenvolve em assembly como sua linguagem principal.
O que isso realmente significa é que os desenvolvedores primeiro escrevem o jogo e o fazem funcionar em C ++. Em seguida, eles criam o perfil, descobrem quais são os gargalos e, se vale a pena, eles os otimizam totalmente na montagem. Ou, se já tiverem experiência, sabem quais partes serão gargalos e têm peças otimizadas de outros jogos que criaram.
O ponto de programação em montagem é o mesmo de sempre: velocidade . Seria ridículo escrever muito código em assembler, mas existem algumas otimizações das quais o compilador não está ciente, e para uma janela de código pequena o suficiente, um ser humano fará melhor.
Por exemplo, para ponto flutuante, os compiladores tendem a ser bastante conservadores e podem não estar cientes de alguns dos recursos mais avançados de sua arquitetura. Se você está disposto a aceitar algum erro, geralmente pode fazer melhor do que o compilador, e vale a pena escrever aquele pequeno código em assembly se você descobrir que gasta muito tempo nisso.
Aqui estão alguns exemplos mais relevantes:
Exemplos de jogos
Artigo da Intel sobre a otimização de um mecanismo de jogo usando intrínsecos SSE. O código final usa intrínsecos (não assembler embutido), então a quantidade de assembly puro é muito pequena. Mas eles olham para a saída do montador pelo compilador para descobrir exatamente o que otimizar.
Raiz quadrada inversa rápida de Quake . Novamente, a rotina não possui assembler, mas você precisa saber algo sobre arquitetura para fazer esse tipo de otimização. Os autores sabem quais operações são rápidas (multiplicação, deslocamento) e quais são lentas (divisão, sqrt). Portanto, eles apresentam uma implementação muito complicada de raiz quadrada que evita totalmente as operações lentas.
Computação de alto desempenho
Fora do domínio dos jogos, as pessoas da computação científica freqüentemente otimizam o lixo para fazer com que funcionem rapidamente no hardware mais recente. Pense nisso como jogos em que você não pode trapacear na física.
Um grande exemplo recente disso é Lattice Quantum Chromodynamics (Lattice QCD) . Este artigo descreve como o problema praticamente se resume a uma muito pequena do kernel computacional, que foi otimizado fortemente para PowerPC 440 da em um IBM Blue Gene / L . Cada 440 possui duas FPUs e suportam algumas operações ternárias especiais que são difíceis de explorar pelos compiladores. Sem essas otimizações, Lattice QCD teria executado muito mais devagar, o que é caro quando seu problema requer milhões de horas de CPU em máquinas caras.
Se você está se perguntando por que isso é importante, verifique o artigo na Science que saiu deste trabalho. Usando Lattice QCD, esses caras calcularam a massa de um próton a partir dos primeiros princípios e mostraram no ano passado que 90% da massa vem de uma forte energia de ligação, e o resto, de quarks. Isso é E = mc 2 em ação. Aqui está um resumo .
Por tudo isso, os aplicativos não são projetados ou escritos 100% em montagem - nem mesmo perto. Mas quando as pessoas realmente precisam de velocidade, elas se concentram em escrever as partes principais de seu código para voar em um hardware específico.
fonte
Não codifico em linguagem assembly há muitos anos, mas posso citar vários motivos que vi com frequência:
Nem todos os compiladores podem fazer uso de certas otimizações de CPU e conjunto de instruções (por exemplo, os novos conjuntos de instruções que a Intel adiciona de vez em quando). Esperar que os criadores do compilador se atualizem significa perder uma vantagem competitiva.
Mais fácil de combinar o código real com a arquitetura e otimização de CPU conhecidas. Por exemplo, coisas que você sabe sobre o mecanismo de busca, cache, etc. Isso deveria ser transparente para o desenvolvedor, mas o fato é que não é, é por isso que os escritores do compilador podem otimizar.
Certos acessos de nível de hardware só são possíveis / práticos via linguagem assembly (por exemplo, ao escrever um driver de dispositivo).
O raciocínio formal às vezes é realmente mais fácil para a linguagem assembly do que para a linguagem de alto nível, uma vez que você já sabe qual é o layout final ou quase final do código.
Programar certas placas gráficas 3D (por volta do final dos anos 1990) na ausência de APIs era freqüentemente mais prático e eficiente em linguagem assembly e às vezes não era possível em outras linguagens. Mas, novamente, isso envolveu jogos de nível realmente especializado baseados na arquitetura do acelerador, como mover dados manualmente para dentro e para fora em determinada ordem.
Duvido que muitas pessoas usem a linguagem assembly quando uma linguagem de nível superior serviria, especialmente quando essa linguagem é C. A otimização manual de grandes quantidades de código de uso geral é impraticável.
fonte
Há um aspecto da programação em assembler que outros não mencionaram - a sensação de satisfação que você obtém ao saber que cada byte em um aplicativo é o resultado de seu próprio esforço, não do compilador. Nem por um segundo eu gostaria de voltar a escrever aplicativos inteiros em assembler, como costumava fazer no início dos anos 80, mas às vezes sinto falta desse sentimento ...
fonte
Normalmente, a montagem de um leigo é mais lenta do que C (devido à otimização do C), mas muitos jogos (lembro-me distintamente de Doom ) tinham que ter seções específicas do jogo em Montagem para que funcionasse sem problemas em máquinas normais.
Aqui está o exemplo a que me refiro.
fonte
Comecei a programar profissionalmente em linguagem assembly no meu primeiro emprego (anos 80). Para sistemas embarcados, as demandas de memória - RAM e EPROM - eram baixas. Você poderia escrever um código restrito com recursos fáceis.
No final dos anos 80, mudei para C. O código era mais fácil de escrever, depurar e manter. Trechos de código muito pequenos foram escritos em assembler - para mim, foi quando eu estava escrevendo a alternância de contexto em um RTOS roll-your-own. (Algo que você não deve fazer mais a menos que seja um "projeto de ciências".)
Você verá fragmentos de assembler em alguns códigos do kernel do Linux. Mais recentemente, eu naveguei em spinlocks e outro código de sincronização. Essas partes do código precisam obter acesso a operações atômicas de teste e configuração, manipulação de caches etc.
Eu acho que você teria dificuldade em otimizar os compiladores C modernos para a maioria da programação geral.
Eu concordo com @altCognito que seu tempo provavelmente será melhor gasto pensando mais sobre o problema e fazendo as coisas melhor. Por alguma razão, os programadores geralmente se concentram em microeficiências e negligenciam as macroeficiências. A linguagem assembly para melhorar o desempenho é uma microeficiência. Recuar para uma visão mais ampla do sistema pode expor os problemas macro em um sistema. Resolver os problemas macro muitas vezes pode render melhores ganhos de desempenho. Uma vez que os problemas macro são resolvidos, eles caem para o nível micro.
Acho que os microproblemas estão sob o controle de um único programador e em um domínio menor. Alterar o comportamento no nível macro requer comunicação com mais pessoas - algo que alguns programadores evitam. Toda aquela coisa de cowboy vs time.
fonte
"Sim". Mas, entenda que na maioria das vezes os benefícios de escrever código em assembler não valem o esforço. O retorno recebido por escrevê-lo em assembléia tende a ser menor do que simplesmente focar em pensar mais sobre o problema e gastar seu tempo pensando em uma maneira melhor de fazer os trabalhos.
John Carmack e Michael Abrash, que foram os grandes responsáveis por escrever Quake e todo o código de alto desempenho que foi usado nos motores de jogos de IDs, abordam isso em detalhes neste livro .
Eu também concordaria com Ólafur Waage que hoje, compiladores são muito inteligentes e muitas vezes empregam muitas técnicas que tiram proveito de impulsos arquitetônicos ocultos.
fonte
Hoje em dia, pelo menos para códigos sequenciais, um compilador decente quase sempre bate até mesmo um programador de linguagem assembly altamente experiente. Mas, para os códigos vetoriais, é outra história. Compiladores amplamente implantados não fazem um trabalho tão bom explorando os recursos de vetor paralelo da unidade x86 SSE, por exemplo. Sou um escritor de compiladores, e explorar o SSE está no topo da minha lista de motivos para ir por conta própria em vez de confiar no compilador.
fonte
O código SSE funciona melhor em assembly do que intrínsecos do compilador, pelo menos em MSVC. (ou seja, não cria cópias extras de dados)
fonte
Tenho três ou quatro rotinas de assembler (em fonte de cerca de 20 MB) em minhas fontes no trabalho. Todos eles são SSE (2) e estão relacionados a operações em imagens (bastante grandes - pense em 2400x2048 e maiores).
Por hobby, eu trabalho em um compilador, e aí você tem mais assembler. Bibliotecas de tempo de execução estão frequentemente cheias deles, a maioria deles tem a ver com coisas que desafiam o regime de procedimento normal (como ajudantes para exceções, etc.)
Não tenho nenhum montador para meu microcontrolador. A maioria dos microcontroladores modernos tem tanto hardware periférico (contadores controlados por interrupção, até mesmo codificadores inteiros de quadratura e blocos de construção seriais) que o uso do assembler para otimizar os loops geralmente não é mais necessário. Com os preços atuais do flash, o mesmo vale para a memória de código. Além disso, muitas vezes há intervalos de dispositivos compatíveis com pinos, portanto, aumentar a escala se você sistematicamente ficar sem energia da CPU ou espaço flash muitas vezes não é um problema
A menos que você realmente envie 100.000 dispositivos e o montador de programação torne possível realmente fazer uma grande economia apenas inserindo em um chip flash uma categoria menor. Mas não estou nessa categoria.
Muitas pessoas pensam que embarcado é uma desculpa para montador, mas seus controladores têm mais potência de CPU do que as máquinas em que o Unix foi desenvolvido. (Microchip vem com microcontroladores de 40 e 60 MIPS por menos de US $ 10).
No entanto, muitas pessoas estão presas ao legado, já que mudar a arquitetura do microchip não é fácil. Além disso, o código HLL é muito dependente da arquitetura (porque ele usa a periferia de hardware, registra para controlar E / S, etc). Portanto, às vezes há boas razões para manter um projeto em assembler (tive a sorte de poder configurar assuntos em uma nova arquitetura do zero). Mas muitas vezes as pessoas se enganam achando que realmente precisam do montador.
Ainda gosto da resposta que um professor deu quando perguntamos se poderíamos usar GOTO (mas você também pode ler isso como ASSEMBLER): "se você acha que vale a pena escrever um ensaio de 3 páginas sobre por que você precisa do recurso, você pode usá-lo . Envie o ensaio com seus resultados. "
Usei isso como princípio orientador para recursos de nível inferior. Não fique muito apertado para usá-lo, mas certifique-se de motivá-lo adequadamente. Até mesmo coloque uma ou duas barreiras artificiais (como o ensaio) para evitar o raciocínio complicado como justificativa.
fonte
Algumas instruções / sinalizadores / controle simplesmente não existem no nível C.
Por exemplo, a verificação de estouro em x86 é o sinalizador de estouro simples. Esta opção não está disponível em C.
fonte
Os defeitos tendem a ser executados por linha (instrução, ponto de código, etc.); embora seja verdade que para a maioria dos problemas, o assembly usaria muito mais linhas do que as linguagens de nível superior, ocasionalmente há casos em que é o melhor (mais conciso, menos linhas) mapeado para o problema em questão. A maioria desses casos envolve os suspeitos usuais, como drivers e bits em sistemas embarcados.
fonte
Outra razão pode ser quando o compilador disponível simplesmente não é bom o suficiente para uma arquitetura e a quantidade de código necessária no programa não é tão longa ou complexa a ponto de o programador se perder nela. Tente programar um microcontrolador para um sistema embarcado, geralmente a montagem será muito mais fácil.
fonte
Além de outras coisas mencionadas, todas as línguas superiores têm certas limitações. É por isso que algumas pessoas optam por programar em ASM, para ter controle total sobre seu código.
Outros desfrutam de executáveis muito pequenos, na faixa de 20-60 KB, por exemplo, verifique o HiEditor , que é implementado pelo autor do controle HiEdit, excelente controle de edição poderoso para Windows com destaque de sintaxe e guias em apenas ~ 50kb). Em minha coleção, tenho mais de 20 controles dourados da Excell, como ssheets para renderizações html.
fonte
Acho que muitos desenvolvedores de jogos ficariam surpresos com esta informação.
A maioria dos jogos que conheço usa o mínimo de montagem possível. Em alguns casos, nenhum e, na pior das hipóteses, um ou dois loops ou funções.
Essa citação é generalizada demais e nem de longe tão verdadeira quanto era há uma década.
Mas ei, meros fatos não devem impedir a cruzada de um verdadeiro hacker em favor da montagem. ;)
fonte
Se você está programando um microcontrolador de 8 bits de baixo custo com 128 bytes de RAM e 4K de memória de programa, você não tem muita escolha sobre o uso de assembly. Porém, às vezes, ao usar um microcontrolador mais poderoso, você precisa que uma determinada ação ocorra no momento exato. A linguagem assembly é útil, pois você pode contar as instruções e assim medir os ciclos de clock usados pelo seu código.
fonte
Se você estivesse presente em todos os esforços de remediação do Y2K, poderia ter ganho muito dinheiro se conhecesse a Assembly. Ainda há muito código legado escrito nele, e esse código ocasionalmente precisa de manutenção.
fonte
Além de projetos muito pequenos em CPUs muito pequenas, eu nunca planejaria programar um projeto inteiro em montagem. No entanto, é comum descobrir que um gargalo de desempenho pode ser aliviado com a codificação manual estratégica de alguns loops internos.
Em alguns casos, tudo o que é realmente necessário é substituir alguma construção de linguagem por uma instrução que o otimizador não deve descobrir como usar. Um exemplo típico é em aplicativos DSP em que operações de vetor e operações de multiplicação-acumulação são difíceis para um otimizador descobrir, mas fáceis de codificar manualmente.
Por exemplo, certos modelos do SH4 contêm uma matriz 4x4 e 4 instruções vetoriais. Eu vi uma grande melhoria de desempenho em um algoritmo de correção de cores ao substituir operações C equivalentes em uma matriz 3x3 pelas instruções apropriadas, ao pequeno custo de ampliar a matriz de correção para 4x4 para corresponder à suposição de hardware. Isso foi conseguido escrevendo não mais do que uma dúzia de linhas de montagem e levando os ajustes correspondentes aos tipos de dados e armazenamento relacionados em um punhado de lugares no código C circundante.
fonte
Não parece ser mencionado, então pensei em adicioná-lo: no desenvolvimento de jogos modernos, acho que pelo menos parte da montagem que está sendo escrita não é para a CPU. É para a GPU, na forma de programas de shader .
Isso pode ser necessário por todos os tipos de razões, às vezes simplesmente porque qualquer linguagem de sombreamento de nível superior usada não permite que a operação exata seja expressa no número exato de instruções desejadas, para se ajustar a alguma restrição de tamanho, velocidade ou qualquer combinação . Como de costume com a programação em linguagem assembly, eu acho.
fonte
Quase todo mecanismo de jogo de médio a grande porte ou biblioteca que vi até agora tem algumas versões de assembly otimizadas à mão disponíveis para operações de matriz, como concatenação de matriz 4x4. Parece que os compiladores inevitavelmente perdem algumas das otimizações inteligentes (reutilização de registradores, desenrolamento de loops de uma forma mais eficiente, aproveitando as instruções específicas da máquina, etc.) ao trabalhar com grandes matrizes. Essas funções de manipulação de matriz quase sempre são "pontos de acesso" no perfil também.
Também vi assembly codificado manualmente muito usado para despacho personalizado - coisas como FastDelegate, mas específico do compilador e da máquina.
Finalmente, se você tiver rotinas de serviço de interrupção, o asm pode fazer toda a diferença no mundo - há certas operações que você simplesmente não quer que ocorram durante a interrupção, e você deseja que seus manipuladores de interrupção "entrem e saiam rápido". .. você sabe quase exatamente o que vai acontecer em seu ISR se estiver em conjunto, e isso o incentiva a manter as coisas curtas (o que é uma boa prática de qualquer maneira).
fonte
Os jogos exigem muito desempenho e, embora os otimizadores sejam muito bons, um "programador mestre" ainda é capaz de extrair um pouco mais de desempenho codificando manualmente as peças corretas na montagem.
Nunca, jamais, comece a otimizar seu programa sem antes criar o perfil dele. Após a criação de perfil, você deve ser capaz de identificar gargalos e, se encontrar algoritmos melhores e similares não resolver mais, você pode tentar codificar manualmente algumas coisas em assembly.
fonte
Eu só conversei pessoalmente com um desenvolvedor sobre seu uso de assembly. Ele estava trabalhando no firmware que lidava com os controles de um mp3 player portátil. Fazer o trabalho em montagem tinha 2 propósitos:
fonte
A única codificação em assembler que continuo a fazer é para hardware embarcado com recursos escassos. Como Leander menciona, a montagem ainda é adequada para ISRs, onde o código precisa ser rápido e bem compreendido.
Uma razão secundária para mim é manter meu conhecimento de montagem funcional. Ser capaz de examinar e entender as etapas que a CPU está realizando para cumprir minhas ordens é muito bom.
fonte
A última vez que escrevi em assembler foi quando não consegui convencer o compilador a gerar código livre de libc e independente de posição.
A próxima vez provavelmente será pelo mesmo motivo.
Claro, eu costumava ter outros motivos .
fonte
Muitas pessoas adoram denegrir a linguagem assembly porque nunca aprenderam a codificar com ela e a encontraram apenas vagamente, e isso os deixou horrorizados ou um tanto intimidados. Os verdadeiros programadores talentosos entenderão que não faz sentido atacar C ou Assembly porque eles são complementares. na verdade, a vantagem de um é a desvantagem do outro. As regras de sintaxe organizadas de C aumentam a clareza, mas ao mesmo tempo desistem de todo o poder de montagem de estar livre de quaisquer regras estruturais! As instruções do código C são feitas para criar um código não bloqueador que pode ser argumentado que força a clareza da intenção de programação, mas isso é uma perda de potência. Em C, o compilador não permitirá um salto dentro de um if / elseif / else / end. Ou você não tem permissão para escrever dois loops for / end em variáveis diferentes que se sobrepõem, você não pode escrever código que se modifica automaticamente (ou não pode de uma maneira fácil e contínua), etc. os programadores convencionais estão assustados com o que foi dito acima, e não teriam ideia de como usar o poder dessas abordagens, já que foram criadas para seguir as regras convencionais . Aqui está a verdade: Hoje temos máquinas com o poder de computação para fazer muito mais do que o aplicativo para o qual as usamos, mas o cérebro humano é muito incapaz de codificá-las em um ambiente de codificação livre de regras (= montagem) e precisa de regras restritivas que muito reduz o espectro e simplifica a codificação. Eu mesmo escrevi um código que não pode ser escrito em código C sem se tornar extremamente ineficiente por causa das limitações mencionadas acima. E ainda não falei sobre a velocidade que a maioria das pessoas pensa ser a principal razão para escrever em assembléia, bem, se sua mente está limitada a pensar em C, então você é escravo de seu compilador para sempre. Sempre pensei que os mestres dos jogadores de xadrez seriam os programadores de montagem ideais, enquanto os programadores C apenas jogam "Dames".
fonte
goto
permite saltos não estruturados dentro de uma função, no entanto. Incluindo em um bloco dentro de umif()
loop ou na mesma função. por exemplo, godbolt.org/z/IINHTg . Veja também o Dispositivo de Duff, usando switch / case em umdo{}while()
loop para expressar um salto em um loop desenrolado. Mas em algum ponto pode ficar mais claro escrever como se você estiver chegando a esse nível de confusão.Não mais velocidade, mas controle . A velocidade às vezes virá do controle, mas é a única razão para codificar em assembly. Todas as outras razões se resumem ao controle (ou seja, SSE e outras otimizações, drivers de dispositivo e código dependente de dispositivo, etc.).
fonte
Se eu conseguir superar o GCC e o Visual C ++ 2008 (também conhecido como Visual C ++ 9.0), as pessoas se interessarão em me entrevistar sobre como isso é possível.
É por isso que no momento eu apenas leio coisas em assembly e apenas escrevo __asm int 3 quando necessário.
Espero que ajude ...
fonte
Faz alguns anos que não escrevo em assembleia, mas os dois motivos que costumava fazer eram:
Eu fico olhando para codificação de montagem novamente, e nada mais é do que o desafio e a alegria da coisa. Não tenho outro motivo para fazer isso :-)
fonte
Certa vez, assumi um projeto DSP que o programador anterior havia escrito principalmente em código assembly, exceto para a lógica de detecção de tom que foi escrita em C, usando ponto flutuante (em um DSP de ponto fixo!). A lógica de detecção de tom funcionou a cerca de 1/20 do tempo real.
Acabei reescrevendo quase tudo do zero. Quase tudo estava em C, exceto por alguns pequenos manipuladores de interrupção e algumas dezenas de linhas de código relacionadas ao tratamento de interrupções e detecção de frequência de baixo nível, que é executado mais de 100 vezes mais rápido que o código antigo.
Uma coisa importante a se ter em mente, eu acho, é que em muitos casos, haverá muito mais oportunidades de aumento de velocidade com rotinas pequenas do que grandes, especialmente se o assembler escrito à mão puder encaixar tudo nos registradores, mas um compilador não bastante gerenciar. Se um loop for grande o suficiente para não conseguir manter tudo nos registros de qualquer maneira, haverá muito menos oportunidade de melhoria.
fonte
O Dalvik VM que interpreta o bytecode para aplicativos Java em telefones Android usa assembler para o despachante. Este filme (cerca de 31 minutos, mas vale a pena assistir o filme inteiro!) Explica como
fonte
Eu não, mas fiz questão de pelo menos tentar, e tentar com afinco em algum ponto do futuro (esperançosamente em breve). Não pode ser ruim saber mais sobre as coisas de baixo nível e como as coisas funcionam nos bastidores quando estou programando em uma linguagem de alto nível. Infelizmente, é difícil conseguir um emprego em tempo integral como desenvolvedor / consultor e pai / mãe. Mas vou desistir no devido tempo, isso é certo.
fonte