este é um pedaço de código de montagem
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov edx, len ;message length
mov ecx, msg ;message to write
mov ebx, 1 ;file descriptor (stdout)
mov eax, 4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax, 1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db 'Hello, world!',0xa ;our dear string
len equ $ - msg ;length of our dear string
Dado um sistema de computador específico, é possível prever com precisão o tempo real de execução de uma parte do código de montagem.
Respostas:
Só posso citar o manual de uma CPU bastante primitiva, um processador 68020, por volta de 1986: "É difícil calcular o tempo de execução exato de uma sequência de instruções, mesmo se você tiver conhecimento preciso da implementação do processador". O que não temos. E comparado a um processador moderno, essa CPU era primitiva .
Não posso prever o tempo de execução desse código, nem você. Mas você nem pode definir qual é o "tempo de execução" de um pedaço de código, quando um processador possui caches enormes e recursos massivos e fora de ordem. Um processador moderno típico pode ter 200 instruções "em voo", que estão em vários estágios de execução. Portanto, o tempo entre a tentativa de ler o primeiro byte da instrução e a retirada da última instrução pode ser bastante longo. Mas o atraso real em todo o restante trabalho que o processador precisa fazer pode ser (e normalmente é) muito menor.
É claro que fazer duas chamadas para o sistema operacional torna isso totalmente imprevisível. Você não sabe o que "escrever para stdout" realmente faz, portanto não pode prever o tempo.
E você não pode saber a velocidade do relógio do computador no momento exato em que executa o código. Pode estar em algum modo de economia de energia, o computador pode ter a velocidade do relógio reduzida porque ficou quente, portanto, mesmo o mesmo número de ciclos de relógio pode levar diferentes períodos de tempo.
Em suma: Totalmente imprevisível.
fonte
Você não pode fazer isso em geral, mas, em alguns sentidos, pode muito bem, e houve alguns casos históricos em que você realmente precisou .
O Atari 2600 (ou Atari Video Computer System) foi um dos primeiros sistemas de videogame doméstico e foi lançado pela primeira vez em 1978. Ao contrário dos sistemas posteriores da época, a Atari não podia dar ao luxo de fornecer ao dispositivo um buffer de quadro, o que significa que a CPU tinha para executar o código em cada linha de verificação para determinar o que produzir - se esse código levasse 17,08 microssegundos para ser executado (o intervalo HBlank), os gráficos não seriam definidos corretamente antes que a linha de verificação começasse a desenhá-los. Pior, se o programador quisesse desenhar conteúdo mais complexo do que o Atari normalmente permitia, eles precisariam medir os horários exatos para obter instruções e alterar os registros gráficos à medida que o feixe estava sendo desenhado, com um intervalo de 57,29 microssegundos para toda a linha de varredura.
No entanto, o Atari 2600, como muitos outros sistemas baseados no 6502, tinha um recurso muito importante que possibilitou o gerenciamento cuidadoso do tempo necessário para este cenário: a CPU, a RAM e o sinal de TV funcionavam com o mesmo mestre. relógio. O sinal da TV disparou em um relógio de 3,98 MHz, dividindo os tempos acima em um número inteiro de "relógios coloridos" que gerenciavam o sinal da TV, e um ciclo dos relógios da CPU e da RAM eram exatamente três relógios coloridos, permitindo que o relógio da CPU fosse uma medida precisa do tempo em relação ao sinal atual da TV em andamento. (Para obter mais informações, consulte o Stella Programmer's Guide , escrito para o emulador Stella Atari 2600 ).
Além disso, esse ambiente operacional significava que todas as instruções de CPU tinham uma quantidade definida de ciclos necessários em todos os casos, e muitos 6502 desenvolvedores publicaram essas informações em tabelas de referência. Por exemplo, considere esta entrada para a
CMP
instrução (Comparar memória com acumulador), obtida desta tabela :Usando todas essas informações, o Atari 2600 (e outros desenvolvedores do 6502) conseguiu determinar exatamente quanto tempo o código estava demorando para executar e construir rotinas que fizessem o que precisavam e que ainda cumprissem os requisitos de tempo de sinal de TV da Atari. E como esse tempo era tão exato (especialmente para instruções de perda de tempo como o NOP), eles foram capazes de usá-lo para modificar os gráficos à medida que eram desenhados.
Obviamente, o 6502 do Atari é um caso muito específico, e tudo isso é possível apenas porque o sistema tinha o seguinte:
Todas essas coisas se uniram para criar um sistema onde era possível criar conjuntos de instruções que levavam um tempo exato - e para esse aplicativo, era exatamente isso que era exigido. A maioria dos sistemas não possui esse grau de precisão simplesmente porque não há necessidade - os cálculos são feitos quando são feitos ou, se for necessária uma quantidade exata de tempo, um relógio independente pode ser consultado. Mas se a necessidade for adequada (como em alguns sistemas incorporados), ela ainda poderá aparecer e você poderá determinar com precisão quanto tempo seu código leva para ser executado nesses ambientes.
E também devo acrescentar o grande aviso de isenção de responsabilidade de que tudo isso se aplica apenas à construção de um conjunto de instruções de montagem que levarão uma quantidade exata de tempo. Se o que você quer fazer é pegar uma peça de montagem arbitrária, mesmo nesses ambientes, e perguntar "Quanto tempo isso leva para ser executado?", Você categoricamente não pode fazer isso - esse é o Problema da Parada , que foi provado insolúvel.
EDIT 1: Em uma versão anterior desta resposta, afirmei que o Atari 2600 não tinha como informar ao processador onde estava o sinal de TV, o que o forçou a manter o programa inteiro contado e sincronizado desde o início. Conforme apontado nos comentários, isso é válido para alguns sistemas como o ZX Spectrum, mas o Atari 2600, pois contém um registro de hardware que interrompe a CPU até que ocorra o próximo intervalo de apagamento horizontal, bem como uma função para iniciar o intervalo de apagamento vertical à vontade. Portanto, o problema da contagem de ciclos é limitado a cada linha de verificação e só se torna exato se o desenvolvedor desejar alterar o conteúdo à medida que a linha de verificação estiver sendo desenhada.
fonte
Há dois aspectos em jogo aqui
Como @ gnasher729 aponta, se sabemos as instruções exatas a serem executadas, ainda é difícil estimar o tempo de execução exato por causa de coisas como cache, previsão de ramificação, dimensionamento etc.
No entanto, a situação é ainda pior. Dado um pedaço de montagem, é impossível saber quais instruções serão executadas ou mesmo quantas instruções serão executadas. Isso se deve ao teorema de Rice: se pudéssemos determinar isso com precisão, poderíamos usar essas informações para resolver o problema da parada, o que é impossível.
O código de montagem pode conter saltos e ramificações, o que é suficiente para tornar o rastreamento completo de um programa possivelmente infinito. Houve um trabalho sobre aproximações conservadoras do tempo de execução, que fornece limites superiores à execução, por meio de coisas como semântica de custos ou sistemas de tipos anotados. Não estou familiarizado com nada específico para montagem, mas não ficaria surpreso se algo assim existisse.
fonte
mov
é Turing-Completesys_exit
e, assim, paramos o cronômetro. Se nos restringirmos ao encerramento de programas, o que é razoável para uma pergunta tão prática, então a resposta será realmente sim (desde que você tenha um instantâneo perfeito do estado, hw e sw, do sistema imediatamente antes de iniciar o programa).int
s podem executar código arbitrário, aguardar operações arbitrárias de E / S, etc.A escolha do "sistema de computador" incluiria microcontroladores? Alguns microcontroladores têm tempos de execução muito previsíveis, por exemplo, a série PIC de 8 bits possui quatro ciclos de clock por instrução, a menos que a instrução se ramifique para um endereço diferente, leia do flash ou seja uma instrução especial de duas palavras.
As interrupções interromperão obviamente esse tipo de timimg, mas é possível fazer muito sem um manipulador de interrupções em uma configuração "bare metal".
Usando assembly e um estilo de codificação especial, é possível escrever um código que sempre levará o mesmo tempo para ser executado. Não é tão comum agora que a maioria das variantes PIC possui vários temporizadores, mas é possível.
fonte
Na era dos computadores de 8 bits, alguns jogos faziam algo assim. Os programadores usariam a quantidade exata de tempo necessária para executar as instruções, com base na quantidade de tempo que levavam e na velocidade de clock conhecida da CPU, para sincronizar com os horários exatos do hardware de vídeo e áudio. Naquela época, a tela era um monitor de tubo de raios catódicos que percorria cada linha da tela a uma taxa fixa e pintava essa linha de pixels ativando e desativando o raio catódico para ativar ou desativar os fósforos. Como os programadores precisavam dizer ao hardware de vídeo o que exibir logo antes que o feixe chegasse àquela parte da tela e encaixar o restante do código no tempo que restasse, eles chamavam de "corrida no feixe".
Absolutamente não funcionaria em nenhum computador moderno ou em códigos como o seu exemplo.
Por que não? Aqui estão algumas coisas que atrapalhariam o tempo simples e previsível:
A velocidade da CPU e as buscas de memória são gargalos no tempo de execução. É um desperdício de dinheiro executar uma CPU mais rapidamente do que pode buscar instruções para executar ou instalar memória que pode fornecer bytes mais rapidamente do que a CPU pode aceitá-los. Por esse motivo, os computadores antigos rodavam no mesmo relógio. As CPUs modernas funcionam muito mais rápido que a memória principal. Eles gerenciam isso com caches de instruções e dados. A CPU continuará paralisada se precisar esperar por bytes que não estão no cache. As mesmas instruções serão executadas muito mais rapidamente se elas já estiverem no cache do que se não estiverem.
Além disso, as CPUs modernas têm pipelines longos. Eles mantêm seu alto rendimento, fazendo com que outra parte do chip faça um trabalho preliminar nas próximas instruções no pipeline. Isso falhará se a CPU não souber qual será a próxima instrução, o que pode acontecer se houver uma ramificação. Portanto, as CPUs tentam prever saltos condicionais. (Você não possui nenhum trecho de código, mas talvez tenha havido um salto condicional imprevisto que entupiu o pipeline. Além disso, boa desculpa para vincular essa resposta lendária.) Da mesma forma, os sistemas que chamam
int 80
para interceptar o modo kernel realmente estão usando um recurso complicado da CPU, um portão de interrupção, que introduz um atraso imprevisível.Se o seu sistema operacional usar multitarefa preemptiva, o encadeamento executando esse código poderá perder seu tempo a qualquer momento.
A corrida na trave também funcionava porque o programa estava sendo executado apenas no metal e batia diretamente no hardware. Aqui, você está ligando
int 80
para fazer uma chamada do sistema. Isso passa o controle para o sistema operacional, o que não oferece garantia de tempo. Você então diz para fazer E / S em um fluxo arbitrário, que pode ter sido redirecionado para qualquer dispositivo. É muito abstrato para você dizer quanto tempo a E / S leva, mas certamente dominará o tempo gasto na execução das instruções.Se você deseja um tempo exato em um sistema moderno, é necessário introduzir um loop de atraso. Você precisa executar as iterações mais rápidas na velocidade mais lenta, não sendo possível o inverso. Uma das razões pelas quais as pessoas fazem isso no mundo real é impedir o vazamento de informações criptográficas para um invasor que pode determinar quais solicitações demoram mais do que outras.
fonte
Isso é um pouco tangencial, mas o ônibus espacial tinha 4 computadores redundantes que dependiam de uma sincronização precisa, ou seja, exatamente o tempo de execução correspondente.
A primeira tentativa de inicialização do ônibus espacial foi eliminada quando o computador do Backup Flight Software (BFS) se recusou a sincronizar com os quatro computadores PASS (Primary Avionics Software System). Detalhes em "The Bug Heard Round the World" aqui . Leitura fascinante sobre como o software foi desenvolvido para combinar ciclo por ciclo e pode fornecer informações interessantes.
fonte
Acho que estamos misturando duas questões diferentes aqui. (E sim, eu sei que isso foi dito por outras pessoas, mas espero poder expressá-lo mais claramente.)
Primeiro, precisamos ir do código-fonte para a sequência de instruções que é realmente executada (que precisa de conhecimento dos dados de entrada e do código - quantas vezes você percorre um loop? Qual ramo é executado após um teste? ) Por causa do problema de parada, a sequência de instruções pode ser infinita (sem terminação) e nem sempre é possível determinar isso estaticamente, mesmo com o conhecimento dos dados de entrada.
Depois de estabelecer a sequência de instruções a serem executadas, você deseja determinar o tempo de execução. Isso certamente pode ser estimado com algum conhecimento da arquitetura do sistema. Mas o problema é que, em muitas máquinas modernas, o tempo de execução depende muito do armazenamento em cache de buscas de memória, o que significa que depende tanto dos dados de entrada quanto das instruções executadas. Também depende da estimativa correta dos destinos de ramificação condicional, que dependem novamente dos dados. Portanto, será apenas uma estimativa, não será exato.
fonte