Uma comparação de 1 <10 é mais barata que 1 <1000000?


65

Eu apenas usei ~ 1 bilhão como contagem para z-indexCSS, e estava pensando nas comparações que deveriam continuar. Existe uma diferença no desempenho no nível da ALU nas comparações entre números muito grandes e números muito pequenos?

Por exemplo, um desses dois trechos seria mais caro que o outro?

snippet 1

for (int i = 0; i < 10000000; i++){
    if (i < 10000000000000) {
        //do nothing
    }
}

snippet 2

for (int i = 0; i < 10000000; i++){
    if (i < 1000) {
        //do nothing
    }
}


12
O OP não está perguntando quanto tempo levará a ramificação. Claramente, o exemplo tem como objetivo garantir que o tempo seja exatamente o mesmo nos dois trechos. A questão é se a CMPinstrução individual da máquina será mais lenta se ifor maior.
Kilian Foth 02/02

18
Como isso é feito no CSS, a conversão de uma string em um número inteiro provavelmente dominará a operação de comparação em termos de tempo gasto na execução.

58
Se você precisava usar 1000000000 como um z-index em um arquivo CSS, você fez algo errado.
Bergi 02/02

6
Para CSS, a sobrecarga da conversão de texto em um número inteiro dependerá do número de dígitos a serem convertidos (onde um número de 6 dígitos como 1000000 pode ser aproximadamente 6 vezes mais caro que um número de 1 dígito como 1); e essa sobrecarga pode ter ordens de magnitude maiores que a sobrecarga das comparações inteiras.
Brendan

Respostas:


82

Todo processador em que trabalhei faz comparação, subtraindo um dos operandos do outro, descartando o resultado e deixando os sinalizadores do processador (zero, negativo etc.) sozinhos. Como a subtração é feita como uma única operação, o conteúdo dos operandos não importa.

A melhor maneira de responder à pergunta com certeza é compilar seu código no assembly e consultar a documentação do processador de destino para obter as instruções geradas. Para CPUs Intel atuais, esse seria o Manual do desenvolvedor de software das arquiteturas Intel 64 e IA-32 .

A descrição da CMPinstrução ("compare") está no volume 2A, página 3-126 ou página 618 do PDF e descreve sua operação como:

temp ← SRC1 − SignExtend(SRC2);
ModifyStatusFlags; (* Modify status flags in the same manner as the SUB instruction*)

Isso significa que o segundo operando é estendido por sinal, se necessário, subtraído do primeiro operando e o resultado colocado em uma área temporária no processador. Em seguida, os sinalizadores de status são definidos da mesma maneira que seriam para a SUBinstrução ("subtrair") (página 1492 do PDF).

Não há menção na documentação CMPou SUBque os valores dos operandos tenham alguma influência na latência; portanto, qualquer valor usado é seguro.


5
E se o número ficar muito grande para aritmética de 32 bits? Não seria então dividido em computação mais lenta?
Falco

3
@Falco Não em uma CPU com um 64-bit ALU (que é praticamente todos eles, exceto no espaço incorporado nos dias de hoje.)
reirab

8
@Falco: Sim, mas como a pergunta é sobre o desempenho da ALU, a implicação é que os valores se encaixam no tamanho da palavra da CPU ou nas habilidades de qualquer instrução SIMD que ela possa ter. Operar em números maiores do que isso teria que ser implementado com várias instruções fora da CPU. Isso era muito comum 30 anos atrás, quando você tinha apenas registros de 8 ou 16 bits para trabalhar.
Blrfl

6
@Falco Como isso exigiria depuração? Não é um bug; é um pouco mais lento fazer operações de 64 bits em uma CPU que não suporta operações de 64 bits. Sugerir que nunca se deve usar um número acima de 2 ^ 31-1 parece um pouco ridículo.
reirab

2
@Falco Dito isso, os mecanismos de renderização nos navegadores usam números inteiros para representar índices z? A maioria dos mecanismos de renderização que eu estou familiarizado usa flutuadores de precisão única para tudo (até o estágio final de rasterização), mas eu realmente não estudei os mecanismos de renderização de navegador.
reirab

25

Existe uma diferença no desempenho no nível da ALU nas comparações entre números muito grandes e números muito pequenos?

É muito improvável, a menos que passar de um número pequeno para um número grande altere seu tipo numérico, digamos de um intpara um long. Mesmo assim, a diferença pode não ser significativa. É mais provável que você veja uma diferença se sua linguagem de programação mudar silenciosamente para uma aritmética de precisão arbitrária sob as cobertas.

No entanto, seu compilador específico pode estar executando algumas otimizações inteligentes que você não conhece. A maneira que você descobre é medir. Execute um criador de perfil no seu código; veja quais comparações demoram mais. Ou simplesmente inicie e pare um cronômetro.


Deve-se mencionar que os números propostos na pergunta são de tipo numérico diferente em um tipo inteiro típico de 32 bits ...
Febco

19

Muitos processadores possuem instruções "pequenas" que podem executar operações aritméticas, incluindo comparações, em certos operandos imediatamente especificados. Operandos que não sejam esses valores especiais devem usar um formato de instrução maior ou, em alguns casos, devem usar uma instrução "carregar valor da memória". No conjunto de instruções do ARM Cortex-M3, por exemplo, há pelo menos cinco maneiras pelas quais um valor pode ser comparado a uma constante:

    cmp r0,#1      ; One-word instruction, limited to values 0-255

    cmp r0,#1000   ; Two-word instruction, limited to values 0-255 times a power of 2

    cmn r0,#1000   ; Equivalent to comparing value with -1000
                   ; Two-word instruction, limited to values 0-255 times a power of 2

    mov r1,#30000  ; Two words; can handle any value 0-65535
    cmp r0,r1      ; Could use cmn to compare to values -1 to -65535

    ldr r1,[constant1000000] ; One or two words, based upon how nearby the constant is
    cmp r0,r1
    ...

constant1000000:
    dd  1000000

A primeira forma é a menor; a segunda e terceira forma podem ou não ser executadas tão rapidamente, dependendo da velocidade da memória da qual o código é buscado. A quarta forma de formulário quase certamente será mais lenta que as três primeiras e a quinta forma ainda mais lenta, mas a última pode ser usada com qualquer valor de 32 bits.

Nos processadores x86 mais antigos, as instruções de comparação de formato curto seriam executadas mais rapidamente do que as de formato longo, mas muitos processadores mais recentes convertem os formatos longo e curto na mesma representação quando são buscados pela primeira vez e armazenam essa representação uniforme no cache. Assim, enquanto os controladores embarcados (como os encontrados em muitas plataformas móveis) terão uma diferença de velocidade, muitos computadores baseados em x86 não terão.

Observe também que, em muitos casos em que uma constante é muito usada em um loop, um compilador precisará carregar a constante em um registro apenas uma vez - antes do início do loop - tornando as diferenças de tempo irrelevantes. Por outro lado, existem algumas situações, mesmo em pequenos loops, onde isso nem sempre acontece; se um loop é pequeno, mas com execução pesada, ocasionalmente pode haver um desempenho importante entre as comparações que envolvem valores imediatos curtos e as que envolvem valores mais longos.


No MIPS, você pode ter apenas imediatos de 16 bits; portanto, a comparação com 1 será mais curta e (provavelmente) mais rápida que 1000000. Talvez o mesmo para Sparc e PowerPC. E eu acho que eu li de algumas fontes que a Intel também otimiza operações em pequenas imediatos em vários casos, mas não tenho certeza de comparação ou não
phuclv

@ LưuVĩnhPhúc: Um registo pode ser carregado antes do loop. Nesse ponto, a comparação real será o mesmo número de instruções em ambos os casos.
cHao 03/02

Como o Loop era apenas um exemplo do op e a pergunta era, por exemplo, um índice z, se você tiver 1000 objetos, cada um com seu próprio índice z, e você os definir para 100000000 ... 1000000999 ou 10000 ... 10999 e você faz um loop sobre eles para classificação antes da renderização, há muitas comparações e muitas instruções de carregamento. Lá poderia fazer a diferença!
Falco

@ Falco: Nesse caso, os imediatos nem levariam em consideração; carregar e comparar com um registro parece praticamente inevitável.
cHao 04/02

@cHao: Se alguém estiver comparando índices Z entre si, eles estarão em registros. Se alguém estiver lidando com determinados intervalos de índices de maneira diferente, isso pode implicar comparações imediatas. Normalmente, as constantes seriam carregadas antes do início de um loop, mas se, por exemplo, um tivesse um loop que precisasse ler pares de valores da memória e comparar o primeiro valor de cada par com cinco constantes diferentes (com espaçamento não uniforme) no intervalo 100000 para 100.499, e o outro valor com cinco outras constantes, pode ser muito mais rápido para subtrair 100250 (inscritas num registo) e, em seguida, comparar com os valores -250 a 250 ...
supercat

5

A resposta curta para essa pergunta é: não , não há diferença horária para comparar dois números com base na magnitude desses números, supondo que eles sejam armazenados no mesmo tipo de dados (por exemplo, entradas de 32 bits ou comprimentos de 64 bits).

Além disso, até o tamanho da palavra da ALU , é incrivelmente improvável que a comparação de dois números inteiros entre si demore mais de 1 ciclo de clock, pois essa é uma operação trivial equivalente a uma subtração. Eu acho que todas as arquiteturas com as quais eu lidei tiveram comparação de números inteiros de ciclo único.

Os únicos casos em que consigo encontrar que uma comparação de dois números não era uma operação de ciclo único são os seguintes:

  • Instruções em que há realmente uma latência de memória na busca de operandos, mas isso não tem nada a ver com o modo como a comparação funciona (e geralmente não é possível em arquiteturas RISC, embora geralmente seja possível em projetos CISC, como x86 / x64).
  • As comparações de ponto flutuante podem ser de vários ciclos, dependendo da arquitetura.
  • Os números em questão não se encaixam no tamanho da palavra da ULA e, portanto, a comparação deve ser dividida em várias instruções.

4

@ A resposta de RobertHarvey é boa; considere esta resposta um complemento à dele.


Você também deve considerar a Predição de ramificação :

Na arquitetura de computadores, um preditor de ramificação é um circuito digital que tenta adivinhar o caminho que uma ramificação (por exemplo, uma estrutura if-then-else) seguirá antes que se saiba com certeza. O objetivo do preditor de ramificação é melhorar o fluxo no pipeline de instruções. Os preditores de ramificação desempenham um papel crítico na obtenção de alto desempenho efetivo em muitas arquiteturas modernas de microprocessadores em pipeline, como o x86.

Basicamente, no seu exemplo, se a ifdeclaração dentro do loop sempre retornar a mesma resposta, o sistema poderá otimizá-la, adivinhando corretamente de que maneira será ramificada. No seu exemplo, como a ifinstrução no primeiro caso sempre retorna o mesmo resultado, ela será executada um pouco mais rápido que o segundo caso.

Excelente pergunta sobre estouro de pilha sobre o assunto


A previsão de ramificação afeta o tempo de ramificação, mas não o próprio tempo de comparação.
reirab

3

Depende da implementação, mas seria muito, muito improvável .

Admito que não li os detalhes de implementação dos vários mecanismos do navegador, e o CSS não especifica nenhum tipo específico de armazenamento para números. Mas acredito que é seguro assumir que todos os principais navegadores estão usando números de ponto flutuante de precisão dupla de 64 bits ("dobra", para emprestar um termo do C / C ++) para lidar com a maioria de suas necessidades numéricas em CSS , porque é isso que JavaScript usa para números e, portanto, usar o mesmo tipo facilita a integração.

Do ponto de vista do computador, todas as duplas carregam a mesma quantidade de dados: 64 bits, se o valor é 1 ou -3,14 ou 1000000 ou 1e100 . A quantidade de tempo que leva para realizar uma operação nesses números não depende do valor real desses números, porque está sempre trabalhando na mesma quantidade de dados. Existe uma desvantagem em fazer as coisas dessa maneira, pois as duplas não podem representar com precisão todos os números (ou mesmo todos os números dentro de seu intervalo), mas elas podem se aproximar o suficiente para a maioria dos assuntos, e os tipos de coisas que o CSS faz não são numericamente exigente o suficiente para precisar de mais precisão do que isso. Combine isso com os benefícios da compatibilidade direta com o JavaScript e você terá uma forte justificativa para duplas.

Não é impossível que alguém possa implementar CSS usando uma codificação de tamanho variável para números. Se alguém utilizada uma codificação de comprimento variável, em seguida, comparando contra pequenos números seria menos dispendioso do que comparando contra grandes números, porque um grande número têm mais dados para trituração . Esses tipos de codificação podem ser mais precisos que binários, mas também são muito mais lentos, e para CSS em particular, os ganhos de precisão provavelmente não são suficientes para valer a pena o desempenho atingido. Eu ficaria muito surpreso ao saber que qualquer navegador fez as coisas dessa maneira.

Agora, em teoria, há uma exceção possível a tudo o que eu disse acima: comparar com zero é geralmente mais rápido do que comparar com outros números . Isso não ocorre porque o zero é curto (se esse foi o motivo, então 1 deve ser tão rápido, mas não é). É porque zero permite trapacear. É o único número em que todos os bits estão desativados; portanto, se você souber que um dos valores é zero, nem precisa olhar para o outro valor como um número: se algum dos bits estiver ativado, não será igual a zero e, em seguida, você só precisa olhar um bit para ver se é maior ou menor que zero.


0

Se esse código estivesse sendo interpretado toda vez que fosse executado, haveria uma diferença, pois levaria mais tempo para tokenizar e interpretar em 10000000000000comparação com 1000. No entanto, esta é a primeira otimização óbvia dos intérpretes nesse caso: tokenize uma vez e interprete os tokens.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.