Quais são os efeitos das exceções no desempenho em Java?

496

Pergunta: O tratamento de exceções em Java é realmente lento?

A sabedoria convencional, assim como muitos resultados do Google, diz que uma lógica excepcional não deve ser usada para o fluxo normal de programas em Java. Duas razões são geralmente apresentadas,

  1. é realmente lento - até uma ordem de magnitude mais lenta que o código normal (os motivos apresentados variam),

e

  1. é confuso porque as pessoas esperam que apenas erros sejam tratados em código excepcional.

Esta pergunta é sobre # 1.

Como exemplo, esta página descreve o tratamento de exceções Java como "muito lento" e relaciona a lentidão à criação da sequência de mensagens de exceção - "essa sequência é usada na criação do objeto de exceção que é lançado. Isso não é rápido". O artigo Tratamento efetivo de exceções em Java diz que "o motivo disso é devido ao aspecto de criação de objeto do tratamento de exceções, o que torna o lançamento de exceções inerentemente lento". Outro motivo é que a geração de rastreamento de pilha é o que a torna mais lenta.

Meu teste (usando Java 1.6.0_07, Java HotSpot 10.0, no Linux de 32 bits) indica que o tratamento de exceções não é mais lento que o código normal. Tentei executar um método em um loop que executa algum código. No final do método, eu uso um booleano para indicar se deve retornar ou lançar . Dessa forma, o processamento real é o mesmo. Tentei executar os métodos em diferentes ordens e calcular a média dos meus tempos de teste, pensando que poderia ter sido o aquecimento da JVM. Em todos os meus testes, o arremesso foi pelo menos tão rápido quanto o retorno, se não mais rápido (até 3,1% mais rápido). Estou completamente aberto à possibilidade de meus testes estarem errados, mas não vi nada por aí no caminho do exemplo de código, comparações de testes ou resultados nos últimos dois anos que mostram que o tratamento de exceções em Java é realmente lento.

O que me levou a seguir esse caminho foi uma API que eu precisava usar que gerou exceções como parte da lógica de controle normal. Eu queria corrigi-los em seu uso, mas agora talvez não consiga. Em vez disso, terei que elogiá-los por sua visão de futuro?

No artigo Manuseio eficiente de exceções Java na compilação just-in-time , os autores sugerem que a presença de manipuladores de exceção, mesmo que nenhuma exceção seja lançada, é suficiente para impedir que o compilador JIT otimize o código corretamente, diminuindo a velocidade dele . Ainda não testei essa teoria.

John Ellinwood
fonte
8
Eu sei que você não estava perguntando sobre 2), mas você realmente deve reconhecer que usar uma exceção para o fluxo do programa não é melhor do que usar GOTOs. Algumas pessoas defendem gotos, outras defendem o que você está falando, mas se você perguntar a alguém que implementou e manteve por um período de tempo, elas dirão que as duas são muito difíceis de manter as práticas de design (e provavelmente amaldiçoarão o nome da pessoa que achou que era inteligente o suficiente para tomar a decisão de usá-las).
Bill K
80
Bill, alegando que o uso de exceções para o fluxo do programa não é melhor do que o uso de GOTOs não é melhor do que afirmar que o uso de condicionais e loops para o fluxo do programa não é melhor do que o uso de GOTOs. É um arenque vermelho. Explique-se. Exceções podem e são usadas efetivamente para o fluxo do programa em outros idiomas. O código Python da Idiomatic usa exceções regularmente, por exemplo. Posso e mantive o código que usa exceções dessa maneira (embora não seja Java), e não acho que exista algo inerentemente errado.
Mmalone 01/09/10
14
@mmalone usando Exceções para o fluxo de controle normal é uma má idéia em Java porque a escolha do paradigma foi feita dessa maneira . Leia Bloch EJ2 - ele afirma claramente isso, citação (Item 57) exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow- dando uma explicação completa e extensa sobre o porquê. E ele foi o cara que escreveu a Java lib. Portanto, ele é quem define o contrato de API das classes. Eu concordo com Bill K neste.
8
@ OndraŽižka Se alguma estrutura faz isso (use Exceções em condições não excepcionais), ela é falha e quebrada por design, quebrando o contrato de classe Exception da linguagem. Só porque algumas pessoas escrevem código ruim não o torna menos ruim.
8
Ninguém, a não ser o criador do stackoverflow.com, está errado sobre exceções. A regra de ouro do desenvolvimento de software nunca é tornar o simples complexo e pesado. Ele escreve: "É verdade que o que deveria ser um programa simples de 3 linhas geralmente floresce para 48 linhas quando você faz uma boa verificação de erros, mas isso é vida ..." Esta é uma busca por pureza, não simplicidade.
Sf_jeff 02/04

Respostas:

345

Depende de como as exceções são implementadas. A maneira mais simples é usar setjmp e longjmp. Isso significa que todos os registros da CPU são gravados na pilha (o que já leva algum tempo) e possivelmente outros dados precisam ser criados ... tudo isso já acontece na instrução try. A instrução throw precisa desenrolar a pilha e restaurar os valores de todos os registros (e possíveis outros valores na VM). Portanto, try e throw são igualmente lentos, e isso é bastante lento, no entanto, se nenhuma exceção é lançada, sair do bloco try não leva tempo na maioria dos casos (como tudo é colocado na pilha que limpa automaticamente se o método existir).

A Sun e outros reconheceram que isso é possivelmente subótimo e, é claro, as VMs ficam cada vez mais rápidas ao longo do tempo. Existe outra maneira de implementar exceções, o que faz com que a tentativa se torne muito rápida (na verdade, nada acontece para tentar em geral - tudo o que precisa acontecer já está feito quando a classe é carregada pela VM) e torna o lançamento não tão lento . Não sei qual JVM usa essa técnica nova e melhor ...

... mas você está escrevendo em Java para que seu código posteriormente seja executado apenas em uma JVM em um sistema específico? Desde que, se alguma vez for executado em qualquer outra plataforma ou qualquer outra versão da JVM (possivelmente de qualquer outro fornecedor), quem disse que eles também usam a implementação rápida? O mais rápido é mais complicado que o mais lento e não é facilmente possível em todos os sistemas. Você quer ficar portátil? Então não confie nas exceções que são rápidas.

Também faz uma grande diferença o que você faz dentro de um bloco try. Se você abrir um bloco try e nunca chamar nenhum método dentro desse bloco try, o bloco try será ultra rápido, pois o JIT pode realmente tratar um arremesso como um simples goto. Ele não precisa salvar o estado da pilha nem precisa desenrolar a pilha se uma exceção for lançada (ela só precisará pular para os manipuladores de captura). No entanto, isso não é o que você costuma fazer. Normalmente, você abre um bloco try e, em seguida, chama um método que pode gerar uma exceção, certo? E mesmo se você apenas usar o bloco try dentro do seu método, que tipo de método será esse que não chama outro método? Apenas calculará um número? Então, para que você precisa de exceções? Existem maneiras muito mais elegantes de regular o fluxo do programa. Para praticamente qualquer outra coisa, exceto matemática simples,

Veja o seguinte código de teste:

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
        // an AND operation between two integers. The size of the number plays
        // no role. AND on 32 BIT always ANDs all 32 bits
        if ((i & 0x1) == 1) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method3(i);
            } catch (Exception e) {
                // Do nothing here, as we will get here
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method3 took " + l + " ms, result was " + t.getValue()
        );
    }
}

Resultado:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

A desaceleração do bloco try é muito pequena para excluir fatores de confusão, como processos em segundo plano. Mas o bloco de captura matou tudo e tornou 66 vezes mais lento!

Como eu disse, o resultado não será tão ruim se você colocar try / catch e throw tudo dentro do mesmo método (método3), mas esta é uma otimização JIT especial em que eu não confiaria. E mesmo ao usar essa otimização, o arremesso ainda é bem lento. Portanto, não sei o que você está tentando fazer aqui, mas há definitivamente uma maneira melhor de fazê-lo do que usar try / catch / throw.

Mecki
fonte
7
Ótima resposta, mas eu gostaria de acrescentar que, tanto quanto sei, System.nanoTime () deve ser usado para medir o desempenho, não System.currentTimeMillis ().
Simon Forsberg
10
@ SimonAndréForsberg nanoTime()requer o Java 1.5 e eu só tinha o Java 1.4 disponível no sistema que usei para escrever o código acima. Também não desempenha um papel importante na prática. A única diferença entre os dois é que um é nanossegundo e outro milissegundos e nanoTimenão é influenciado por manipulações de relógio (que são irrelevantes, a menos que você ou o processo do sistema modifique o relógio do sistema exatamente no momento em que o código de teste está sendo executado). Geralmente você está certo, porém, nanoTimeé obviamente a melhor escolha.
Mecki # 22/13
2
Realmente, deve-se notar que seu teste é um caso extremo. Você mostra um desempenho muito pequeno no código com um trybloco, mas não throw. Seu throwteste está lançando exceções 50% das vezes que passa pelo try. Essa é claramente uma situação em que a falha não é excepcional . Reduzir isso para apenas 10% reduz massivamente o impacto no desempenho. O problema com esse tipo de teste é que ele encoraja as pessoas a parar de usar as exceções completamente. O uso de exceções, para tratamento excepcional de casos, tem um desempenho muito melhor do que o que mostra seu teste.
Nate
1
@Nate Primeiro de tudo, eu disse muito claramente que tudo isso depende de como as exceções são implementadas. Eu estava apenas testando UMA implementação específica, mas há muitas e a Oracle pode escolher uma totalmente diferente a cada versão. Segundo, se as exceções são apenas excepcionais, o que geralmente são, é claro que o impacto é menor, isso é tão óbvio, que eu realmente não acho que alguém precise apontar isso explicitamente e, portanto, não consigo entender o seu ponto aqui. E terceiro, com exceção do uso excessivo, todos concordam com isso, portanto, usá-los com muito cuidado é uma coisa muito boa.
Mecki #
4
@ Gllide Um arremesso não é como uma limpeza return. Ele deixa um método em algum lugar no meio do corpo, talvez até no meio de uma operação (que até agora foi concluída em apenas 50%) e o catchbloco pode ter 20 quadros de pilha para cima (um método tem um trybloco, chamando method1, que chama method2, que chama mehtod3, ... e no method20, no meio de uma operação, uma exceção é lançada). A pilha deve girar 20 quadros para cima, todas as operações inacabadas devem ser desfeitas (as operações não devem ter sido concluídas pela metade) e os registros da CPU precisam estar em um estado limpo. Tudo isso consome tempo.
Mecki
255

Para sua informação, estendi o experimento que Mecki fez:

method1 took 1733 ms, result was 2
method2 took 1248 ms, result was 2
method3 took 83997 ms, result was 2
method4 took 1692 ms, result was 2
method5 took 60946 ms, result was 2
method6 took 25746 ms, result was 2

Os três primeiros são iguais aos do Mecki (meu laptop é obviamente mais lento).

method4 é idêntico ao method3, exceto pelo fato de criar um em new Integer(1)vez de fazer throw new Exception().

method5 é como method3, exceto que ele cria o new Exception()sem lançá-lo.

method6 é como method3, exceto que lança uma exceção pré-criada (uma variável de instância) em vez de criar uma nova.

Em Java, grande parte da despesa de lançar uma exceção é o tempo gasto na coleta do rastreamento de pilha, que ocorre quando o objeto de exceção é criado. O custo real de lançar a exceção, embora grande, é consideravelmente menor que o custo de criar a exceção.

Hot Licks
fonte
48
+1 Sua resposta aborda a questão principal - o tempo necessário para desanuviar e rastrear a pilha e, secundariamente, o lançamento do erro. Eu teria selecionado isso como a resposta final.
Engineer
8
agradável. ~ 70% criando a exceção, ~ 30% lançando-a. boa informação.
Chaqke
1
@ Basil - Você deve conseguir descobrir isso a partir dos números acima.
Hot Licks
1
Isso pode ser específico da implementação. Qual versão do Java foi usada para esses benchmarks?
Thorbjørn Ravn Andersen
3
Podemos observar que no código padrão, criar e lançar exceções ocorre em casos raros (em tempo de execução, quero dizer), se não for o caso, as condições de tempo de execução são muito ruins ou o design é o problema; em ambos os casos performances não são uma preocupação ...
Jean-Baptiste Yunes
70

Aleksey Shipilëv fez uma análise muito minuciosa na qual ele avalia as exceções de Java sob várias combinações de condições:

  • Exceções recém-criadas vs exceções pré-criadas
  • Rastreamento de pilha ativado ou desativado
  • Rastreamento de pilha solicitado versus nunca solicitado
  • Preso no nível superior vs retrocedido em todos os níveis vs encadeado / envolvido em todos os níveis
  • Vários níveis de profundidade da pilha de chamadas Java
  • Não há otimizações de inlining versus configurações extremas de inlining vs. padrão
  • Campos definidos pelo usuário ler vs não ler

Ele também os compara ao desempenho de verificar um código de erro em vários níveis de frequência de erro.

As conclusões (citadas literalmente em seu post) foram:

  1. Exceções verdadeiramente excepcionais têm um ótimo desempenho. Se você usá-los como projetado, e apenas comunicar casos verdadeiramente excepcionais entre o número esmagadoramente grande de casos não excepcionais tratados por código regular, o uso de exceções é a conquista do desempenho.

  2. Os custos de desempenho das exceções têm dois componentes principais: construção do rastreio da pilha quando a exceção é instanciada e desenrolamento da pilha durante o lançamento da exceção.

  3. Os custos de construção do rastreamento de pilha são proporcionais à profundidade da pilha no momento da instanciação da exceção. Isso já é ruim, porque quem sabe a profundidade da pilha na qual esse método de lançamento seria chamado? Mesmo se você desativar a geração de rastreamento de pilha e / ou armazenar em cache as exceções, você poderá se livrar dessa parte do custo de desempenho.

  4. Os custos de desenrolamento da pilha dependem da sorte que temos em aproximar o manipulador de exceções no código compilado. Estruturar cuidadosamente o código para evitar a pesquisa profunda de manipuladores de exceção provavelmente está nos ajudando a ter mais sorte.

  5. Se eliminarmos ambos os efeitos, o custo de desempenho das exceções é o da filial local. Não importa o quão bonito seja, isso não significa que você deve usar Exceções como o fluxo de controle usual, porque nesse caso você está à mercê de otimizar o compilador! Você deve usá-los apenas em casos verdadeiramente excepcionais, nos quais a frequência da exceção amortiza o possível custo infeliz de aumentar a exceção real.

  6. A regra geral otimista parece ter uma frequência de 10 ^ -4, pois as exceções são suficientemente excepcionais. Isso, é claro, depende dos pesos pesados ​​das próprias exceções, das ações exatas tomadas nos manipuladores de exceções, etc.

O resultado é que, quando uma exceção não é lançada, você não paga um custo; portanto, quando a condição excepcional é suficientemente rara, o tratamento de exceções é mais rápido do que o uso ifsempre. O post completo vale muito a pena ser lido.

Doval
fonte
41

Infelizmente, minha resposta é muito longa para postar aqui. Então, deixe-me resumir aqui e encaminhá-lo para http://www.fuwjax.com/how-slow-are-java-exceptions/ para obter os detalhes mais detalhados.

A verdadeira questão aqui não é "Quão lenta são as 'falhas relatadas como exceções' em comparação com o 'código que nunca falha'?" como a resposta aceita pode fazer você acreditar. Em vez disso, a pergunta deve ser "Quão lenta são as 'falhas relatadas como exceções' em comparação com as falhas relatadas de outras maneiras?" Geralmente, as duas outras formas de relatar falhas são com valores de sentinela ou com wrappers de resultados.

Os valores do Sentinel são uma tentativa de retornar uma classe no caso de sucesso e outra no caso de falha. Você pode pensar nisso quase como retornar uma exceção em vez de lançar uma. Isso requer uma classe pai compartilhada com o objeto de sucesso e, em seguida, faz uma verificação de "instanceof" e um par lança para obter as informações de sucesso ou falha.

Acontece que, com o risco de segurança do tipo, os valores do Sentinel são mais rápidos que as exceções, mas apenas por um fator de aproximadamente 2x. Agora, isso pode parecer muito, mas 2x apenas cobre o custo da diferença de implementação. Na prática, o fator é muito menor, pois nossos métodos que podem falhar são muito mais interessantes do que alguns operadores aritméticos, como no código de exemplo em outras partes desta página.

Os Wrappers de resultado, por outro lado, não sacrificam a segurança do tipo. Eles agrupam as informações de sucesso e falha em uma única classe. Portanto, em vez de "instanceof", eles fornecem um "isSuccess ()" e getters para os objetos de sucesso e falha. No entanto, os objetos de resultado são aproximadamente duas vezes mais lentos do que usando exceções. Acontece que criar um novo objeto wrapper toda vez é muito mais caro do que lançar uma exceção às vezes.

Além disso, as exceções são o idioma fornecido para indicar que um método pode falhar. Não há outra maneira de dizer apenas pela API quais métodos devem sempre (principalmente) funcionar e quais devem reportar falhas.

As exceções são mais seguras que os sentinelas, mais rápidas que os objetos resultantes e menos surpreendentes que qualquer uma delas. Não estou sugerindo que try / catch substitua if / else, mas as exceções são a maneira correta de relatar falhas, mesmo na lógica comercial.

Dito isso, gostaria de salientar que as duas formas mais freqüentes de impactar substancialmente o desempenho que encontrei estão criando objetos desnecessários e loops aninhados. Se você tiver uma escolha entre criar uma exceção ou não, não crie a exceção. Se você tiver a opção entre criar uma exceção às vezes ou criar outro objeto o tempo todo, crie a exceção.

Fuwjax
fonte
5
Decidi testar o desempenho de longo prazo das três implementações em comparação com uma implementação de controle que verifica falhas sem relatar. O processo tem uma taxa de falhas de cerca de 4%. Uma iteração de um teste chama o processo 10000 vezes contra uma das estratégias. Cada estratégia é testada 1000 vezes e as últimas 900 vezes são usadas para gerar as estatísticas. Aqui estão os horários médios em nanos: Controle 338 Exceção 429 Resultado 348 Sentinel 345
Fuwjax 10/10/11
2
Apenas por diversão, desativei o fillInStackTrace no teste de exceção. Aqui estão os horários agora: Controle 347 Exceção 351 Resultado 364 Sentinel 355
Fuwjax 10/04
Fuwjax, a menos que esteja faltando alguma coisa (e admito que só li sua postagem no SO, não a postagem no seu blog), parece que seus dois comentários acima contradizem a sua postagem. Eu presumo que números mais baixos são melhores no seu benchmark, certo? Nesse caso, gerar exceções com fillInStackTrace ativado (que é o comportamento padrão e usual) resulta em desempenho mais lento do que as outras duas técnicas que você descreve. Estou faltando alguma coisa ou você realmente comentou para refutar sua postagem?
Felix GV
@ Fuwjax - a maneira de evitar a opção "rock and hard place" que você apresenta aqui é pré-alocar um objeto que represente "sucesso". Geralmente, também é possível pré-alocar objetos para os casos de falha comuns. Então, apenas no raro caso de retornar detalhes adicionais, é criado um novo objeto. (Este é o equivalente OO de inteiros "Códigos de erro", além de uma chamada separada para obter os detalhes do último erro -. Uma técnica que já existe há décadas)
ToolmakerSteve
@ Fuujax Então, lançar uma exceção não cria um objeto em sua conta? Não tenho certeza se entendi esse raciocínio. Se você lança uma exceção ou retorna um objeto de resultado, está criando objetos. Nesse sentido, os objetos resultantes não são mais lentos do que lançando uma exceção.
Matthias
20

Estendi as respostas dadas por @Mecki e @incarnate , sem o preenchimento de stacktrace para Java.

Com o Java 7+, podemos usar Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace). Mas para Java6, veja minha resposta para esta pergunta

// This one will regularly throw one
public void method4(int i) throws NoStackTraceThrowable {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceThrowable();
    }
}

// This one will regularly throw one
public void method5(int i) throws NoStackTraceRuntimeException {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceRuntimeException();
    }
}

public static void main(String[] args) {
    int i;
    long l;
    Test t = new Test();

    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method4(i);
        } catch (NoStackTraceThrowable e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method4 took " + l + " ms, result was " + t.getValue() );


    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method5(i);
        } catch (RuntimeException e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
}

Saída com Java 1.6.0_45, no Core i7, 8 GB de RAM:

method1 took 883 ms, result was 2
method2 took 882 ms, result was 2
method3 took 32270 ms, result was 2 // throws Exception
method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable
method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException

Portanto, os métodos still que retornam valores são mais rápidos, em comparação com os métodos que lançam exceções. IMHO, não podemos projetar uma API clara usando apenas tipos de retorno para fluxos de sucesso e erro. Os métodos que lançam exceções sem rastreamento de pilha são 4-5 vezes mais rápidos que as exceções normais.

Edit: NoStackTraceThrowable.java Obrigado @Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}
manikanta
fonte
interessante, obrigado. Aqui está a declaração de classe que está faltando:public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
Greg
no começo Você escreveu, With Java 7+, we can usemas depois você escreveu, Output with Java 1.6.0_45,então esse é o resultado do Java 6 ou 7?
WBAR
1
@WBAR do Java 7, só precisamos usar o Throwableconstrutor que possui boolean writableStackTracearg. Mas isso não está presente no Java 6 e abaixo. É por isso que dei implementação personalizada para Java 6 e abaixo. Portanto, o código acima é para Java 6 e abaixo. Por favor, leia a primeira linha do segundo parágrafo com cuidado.
manikanta
@manikanta "IMHO, não podemos projetar uma API clara usando apenas tipos de retorno para fluxos de sucesso e erro." - podemos, se usarmos Opcionais / Resultados / Talvez o mesmo número de idiomas.
precisa saber é o seguinte
@Hejazzman eu concordo. Mas Optionalou similar chegou um pouco tarde para o Java. Antes disso, também usamos objetos wrapper com sinalizadores de sucesso / erro. Mas parece ser um pouco hacks e não me parece natural.
manikanta
8

Há um tempo atrás, escrevi uma classe para testar o desempenho relativo da conversão de strings em ints usando duas abordagens: (1) chame Integer.parseInt () e capture a exceção, ou (2) combine a string com um regex e chame parseInt () somente se a partida for bem-sucedida. Usei a regex da maneira mais eficiente possível (por exemplo, criando os objetos Pattern e Matcher antes de inserir o loop) e não imprimi nem salvei os traços de pilha das exceções.

Para uma lista de dez mil strings, se todos fossem números válidos, a abordagem parseInt () era quatro vezes mais rápida que a abordagem regex. Mas se apenas 80% das seqüências de caracteres fossem válidas, o regex seria duas vezes mais rápido que parseInt (). E se 20% eram válidos, significando que a exceção era lançada e capturada 80% das vezes, o regex era cerca de vinte vezes mais rápido que parseInt ().

Fiquei surpreso com o resultado, considerando que a abordagem regex processa seqüências de caracteres válidas duas vezes: uma para a correspondência e outra para parseInt (). Mas lançar e capturar exceções mais do que compensava isso. É provável que esse tipo de situação não ocorra com muita frequência no mundo real, mas, se ocorrer, você definitivamente não deve usar a técnica de captura de exceções. Mas se você estiver validando apenas a entrada do usuário ou algo assim, use a abordagem parseInt ().

Alan Moore
fonte
qual JVM você usou? ainda é tão lento com o sun-jdk 6?
Benedikt Waldvogel
Eu desenterrei e executei novamente no JDK 1.6u10 antes de enviar essa resposta, e esses são os resultados que eu publiquei.
277 Alan Moore
Isso é muito, muito útil! Obrigado. Para meus casos de uso habituais, eu preciso analisar as entradas do usuário (usando algo como Integer.ParseInt()) e espero que, na maioria das vezes, a entrada do usuário esteja correta, portanto, para o meu caso de uso, parece que tomar o hit de exceção ocasional é o caminho a percorrer .
markvgti
8

Acho que o primeiro artigo se refere ao ato de percorrer a pilha de chamadas e criar um rastreamento de pilha como sendo a parte mais cara, e enquanto o segundo artigo não diz isso, acho que é a parte mais cara da criação de objetos. John Rose tem um artigo em que descreve diferentes técnicas para acelerar exceções . (Pré-alocando e reutilizando uma exceção, exceções sem rastreios de pilha, etc.)

Mas ainda assim - acho que isso deve ser considerado apenas um mal necessário, um último recurso. A razão de John fazer isso é emular recursos em outros idiomas que ainda não estão disponíveis na JVM. Você NÃO deve adquirir o hábito de usar exceções para o fluxo de controle. Especialmente por razões de desempenho! Como você mencionou no item 2, você corre o risco de mascarar erros graves no seu código dessa maneira, e será mais difícil manter os novos programadores.

Microbenchmarks em Java são surpreendentemente difíceis de acertar (já me disseram), especialmente quando você entra no território JIT, então duvido muito que o uso de exceções seja mais rápido que o "retorno" na vida real. Por exemplo, suspeito que você tenha algo entre 2 e 5 quadros de pilha em seu teste? Agora imagine que seu código será chamado por um componente JSF implantado pelo JBoss. Agora você pode ter um rastreamento de pilha com várias páginas.

Talvez você possa postar seu código de teste?

Lars Westergren
fonte
7

Não sei se esses tópicos estão relacionados, mas uma vez eu quis implementar um truque baseado no rastreamento de pilha do encadeamento atual: queria descobrir o nome do método, que acionava a instanciação dentro da classe instanciada (sim, a ideia é louca, Eu desisti totalmente). Então, descobri que a chamada Thread.currentThread().getStackTrace()é extremamente lenta (devido ao dumpThreadsmétodo nativo usado internamente).

Portanto Throwable, Java , correspondentemente, possui um método nativo fillInStackTrace. Eu acho que o killer- catchblock descrito anteriormente de alguma forma desencadeia a execução desse método.

Mas deixe-me contar outra história ...

No Scala, alguns recursos funcionais são compilados na JVM usando ControlThrowable, o que estende Throwablee substitui os fillInStackTraceda seguinte maneira:

override def fillInStackTrace(): Throwable = this

Então, eu adaptei o teste acima (a quantidade de ciclos diminui em dez, minha máquina é um pouco mais lenta :):

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

Então, os resultados são:

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

Veja, a única diferença entre method3e method4é que eles lançam diferentes tipos de exceções. Sim, method4ainda é mais lento que method1e method2, mas a diferença é muito mais aceitável.

encarnar
fonte
6

Fiz alguns testes de desempenho com a JVM 1.5 e o uso de exceções foi pelo menos duas vezes mais lento. Em média: o tempo de execução em um método trivialmente pequeno mais que triplicou (3x), com exceções. Um loop trivialmente pequeno que teve que capturar a exceção viu um aumento de 2x no tempo próprio.

Eu já vi números semelhantes no código de produção e nos micro benchmarks.

As exceções definitivamente NÃO devem ser usadas para qualquer coisa chamada com freqüência. Lançar milhares de exceções por segundo causaria um gargalo enorme.

Por exemplo, usando "Integer.ParseInt (...)" para encontrar todos os valores incorretos em um arquivo de texto muito grande - péssima idéia. (Eu vi esse método utilitário diminuir o desempenho no código de produção)

Usando uma exceção para relatar um valor ruim em um formulário da GUI do usuário, provavelmente não tão ruim do ponto de vista de desempenho.

Seja ou não uma boa prática de design, eu seguiria a regra: se o erro for normal / esperado, use um valor de retorno. Se estiver anormal, use uma exceção. Por exemplo: ao ler entradas do usuário, valores ruins são normais - use um código de erro. Ao passar um valor para uma função de utilitário interno, valores ruins devem ser filtrados chamando código - use uma exceção.

James Schek
fonte
Deixe-me sugerir algumas coisas que são boas para fazer: Se você precisar de um número em um formulário, em vez de usar Integer.valueOf (String), considere usar um correspondente de expressão regular. Você pode pré-compilar e reutilizar o padrão, fazendo com que os fósforos sejam baratos. No entanto, em um formulário da GUI, ter um isValid / validate / checkField ou o que você tem provavelmente é mais claro. Além disso, no Java 8, temos mônadas opcionais, portanto, considere usá-las. (a resposta é 9 anos de idade, mas ainda: p!)
Haakon Løtveit
4

O desempenho de exceção em Java e C # deixa muito a desejar.

Como programadores, isso nos força a seguir a regra "exceções devem ser causadas com pouca frequência", simplesmente por razões práticas de desempenho.

No entanto, como cientistas da computação, devemos nos rebelar contra esse estado problemático. A pessoa que cria uma função geralmente não tem idéia de com que frequência ela será chamada ou se o sucesso ou o fracasso é mais provável. Somente o chamador tem essas informações. Tentar evitar exceções leva a códigos de API pouco claros, onde, em alguns casos, temos apenas versões de exceção limpas, mas lentas, e em outros casos, temos erros de valor de retorno rápidos, mas desajeitados e, em outros casos, acabamos com os dois . O implementador da biblioteca pode precisar escrever e manter duas versões de APIs, e o responsável pela chamada deve decidir qual das duas versões usar em cada situação.

Isso é uma bagunça. Se as exceções tivessem melhor desempenho, poderíamos evitar esses idiomas desajeitados e usar as exceções como elas deveriam ser usadas ... como um recurso estruturado de retorno de erro.

Eu realmente gostaria de ver os mecanismos de exceção implementados usando técnicas mais próximas dos valores retornados, para que pudéssemos ter um desempenho mais próximo dos valores retornados.

Aqui está um exemplo de código que compara o desempenho da exceção ao desempenho do valor de retorno do erro.

classe pública TestIt {

int value;


public int getValue() {
    return value;
}

public void reset() {
    value = 0;
}

public boolean baseline_null(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        return shouldfail;
    } else {
        return baseline_null(shouldfail,recurse_depth-1);
    }
}

public boolean retval_error(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            return false;
        } else {
            return true;
        }
    } else {
        boolean nested_error = retval_error(shouldfail,recurse_depth-1);
        if (nested_error) {
            return true;
        } else {
            return false;
        }
    }
}

public void exception_error(boolean shouldfail, int recurse_depth) throws Exception {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            throw new Exception();
        }
    } else {
        exception_error(shouldfail,recurse_depth-1);
    }

}

public static void main(String[] args) {
    int i;
    long l;
    TestIt t = new TestIt();
    int failures;

    int ITERATION_COUNT = 100000000;


    // (0) baseline null workload
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                t.baseline_null(shoulderror,recurse_depth);
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }


    // (1) retval_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                if (!t.retval_error(shoulderror,recurse_depth)) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }

    // (2) exception_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                try {
                    t.exception_error(shoulderror,recurse_depth);
                } catch (Exception e) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);              
        }
    }
}

}

E aqui estão os resultados:

baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms
baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms
baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms
baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms
baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms
baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms
baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms
baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms
baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms
baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms
baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms
baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms
baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms
baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms
baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms
retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121   ms
retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141   ms
retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334  ms
retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms
retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367   ms
exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775  ms
exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms
exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116   ms
exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms
exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms

Verificar e propagar valores de retorno adiciona algum custo em relação à chamada nula da linha de base, e esse custo é proporcional à profundidade da chamada. Com uma profundidade de 8 na cadeia de chamadas, a versão de verificação de erro-retorno-valor era cerca de 27% mais lenta que a versão de linha de base, que não verificava os valores de retorno.

O desempenho de exceção, em comparação, não é uma função da profundidade da chamada, mas da frequência da exceção. No entanto, a degradação à medida que a frequência da exceção aumenta é muito mais dramática. Com apenas uma frequência de erro de 25%, o código funcionava 24 vezes mais devagar. Com uma frequência de erro de 100%, a versão da exceção é quase 100 vezes mais lenta.

Isso sugere para mim que talvez estejam fazendo as trocas incorretas em nossas implementações de exceção. As exceções podem ser mais rápidas, evitando caminhadas caras ou transformando-as em verificação de valor de retorno suportada pelo compilador. Até que o façam, estamos impedidos de evitá-los quando queremos que nosso código seja executado rapidamente.

David Jeske
fonte
3

O HotSpot é capaz de remover o código de exceção para exceções geradas pelo sistema, desde que tudo esteja embutido. No entanto, a exceção criada explicitamente e as que não foram removidas gastam muito tempo criando o rastreamento de pilha. Substitua fillInStackTracepara ver como isso pode afetar o desempenho.

Tom Hawtin - linha de orientação
fonte
2

Mesmo que lançar uma exceção não seja lento, ainda é uma má idéia lançar exceções para o fluxo normal do programa. Usado desta maneira, é análogo a um GOTO ...

Eu acho que isso realmente não responde à pergunta. Eu imagino que a sabedoria "convencional" de lançar exceções sendo lentas seja verdadeira nas versões java anteriores (<1.4). Criar uma exceção requer que a VM crie o rastreamento de pilha inteiro. Muita coisa mudou desde então na VM para acelerar as coisas e essa é provavelmente uma área que foi aprimorada.

user38051
fonte
1
Seria bom definir "fluxo normal de programa". Muito foi escrito sobre o uso de exceções verificadas como uma falha no processo de negócios e uma exceção não verificada para falhas não recuperáveis; portanto, de certa forma, uma falha na lógica de negócios ainda pode ser considerada como fluxo normal.
Spencer Kormos
2
@ Spencer K: Uma exceção, como o nome indica, significa que uma situação excepcional foi descoberta (um arquivo foi embora, uma rede subitamente fechada, ...). Isso implica que a situação era INESPERADA. Se ESPERAR que a situação irá ocorrer, eu não usaria uma exceção para ela.
Mecki
2
@Mecki: certo. Recentemente, tive uma discussão com alguém sobre isso ... Eles estavam escrevendo uma estrutura de validação e lançando uma exceção em caso de falha na validação. Eu acho que é uma péssima idéia, pois isso seria bastante comum. Prefiro ver o método retornar um ValidationResult.
user38051
2
Em termos de fluxo de controle, uma exceção é análoga a breakou returnnão goto.
Hot Licks
3
Existem toneladas de paradigmas de programação. Não pode haver um único "fluxo normal", o que você quer dizer com isso. Basicamente, o mecanismo de exceção é apenas uma maneira de deixar rapidamente o quadro atual e desenrolar a pilha até um determinado ponto. A palavra "exceção" não implica nada em sua natureza "inesperada". Um exemplo rápido: é muito natural "lançar" 404s de aplicativos da Web quando certas circunstâncias ocorrem ao longo do caminho do roteamento. Por que essa lógica não seria implementada com exceções? Qual é o anti-padrão?
encarnada
2

Basta comparar, digamos, Integer.parseInt ao método a seguir, que retorna um valor padrão no caso de dados não analisáveis, em vez de gerar uma exceção:

  public static int parseUnsignedInt(String s, int defaultValue) {
    final int strLength = s.length();
    if (strLength == 0)
      return defaultValue;
    int value = 0;
    for (int i=strLength-1; i>=0; i--) {
      int c = s.charAt(i);
      if (c > 47 && c < 58) {
        c -= 48;
        for (int j=strLength-i; j!=1; j--)
          c *= 10;
        value += c;
      } else {
        return defaultValue;
      }
    }
    return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value;
  }

Contanto que você aplique os dois métodos aos dados "válidos", eles funcionarão aproximadamente na mesma taxa (mesmo que Integer.parseInt consiga manipular dados mais complexos). Mas assim que você tenta analisar dados inválidos (por exemplo, para analisar "abc" 1.000.000 de vezes), a diferença no desempenho deve ser essencial.

inflamer
fonte
2

Um ótimo post sobre o desempenho de exceções é:

https://shipilev.net/blog/2014/exceptional-performance/

Instanciando vs reutilizando existentes, com rastreamento de pilha e sem, etc:

Benchmark                            Mode   Samples         Mean   Mean error  Units

dynamicException                     avgt        25     1901.196       14.572  ns/op
dynamicException_NoStack             avgt        25       67.029        0.212  ns/op
dynamicException_NoStack_UsedData    avgt        25       68.952        0.441  ns/op
dynamicException_NoStack_UsedStack   avgt        25      137.329        1.039  ns/op
dynamicException_UsedData            avgt        25     1900.770        9.359  ns/op
dynamicException_UsedStack           avgt        25    20033.658      118.600  ns/op

plain                                avgt        25        1.259        0.002  ns/op
staticException                      avgt        25        1.510        0.001  ns/op
staticException_NoStack              avgt        25        1.514        0.003  ns/op
staticException_NoStack_UsedData     avgt        25        4.185        0.015  ns/op
staticException_NoStack_UsedStack    avgt        25       19.110        0.051  ns/op
staticException_UsedData             avgt        25        4.159        0.007  ns/op
staticException_UsedStack            avgt        25       25.144        0.186  ns/op

Dependendo da profundidade do rastreamento da pilha:

Benchmark        Mode   Samples         Mean   Mean error  Units

exception_0000   avgt        25     1959.068       30.783  ns/op
exception_0001   avgt        25     1945.958       12.104  ns/op
exception_0002   avgt        25     2063.575       47.708  ns/op
exception_0004   avgt        25     2211.882       29.417  ns/op
exception_0008   avgt        25     2472.729       57.336  ns/op
exception_0016   avgt        25     2950.847       29.863  ns/op
exception_0032   avgt        25     4416.548       50.340  ns/op
exception_0064   avgt        25     6845.140       40.114  ns/op
exception_0128   avgt        25    11774.758       54.299  ns/op
exception_0256   avgt        25    21617.526      101.379  ns/op
exception_0512   avgt        25    42780.434      144.594  ns/op
exception_1024   avgt        25    82839.358      291.434  ns/op

Para outros detalhes (incluindo o montador x64 do JIT), leia a postagem original do blog.

Isso significa que o Hibernate / Spring / etc-EE-shit está lento por causa das exceções (xD) e reescreve o fluxo de controle do aplicativo para longe das exceções (substitua-o por continure/ breake retornando booleansinalizadores como em C na chamada de método) para melhorar o desempenho do seu aplicativo 10x-100x , dependendo da frequência com que você os joga))

gavenkoa
fonte
0

Alterei a resposta do @Mecki acima para que o method1 retorne um booleano e um check no método de chamada, pois você não pode simplesmente substituir uma exceção por nada. Após duas execuções, o método1 ainda era o mais rápido ou o mais rápido que o método2.

Aqui está um instantâneo do código:

// Calculates without exception
public boolean method1(int i) {
    value = ((value + i) / i) << 1;
    // Will never be true
    return ((i & 0xFFFFFFF) == 1000000000);

}
....
   for (i = 1; i < 100000000; i++) {
            if (t.method1(i)) {
                System.out.println("Will never be true!");
            }
    }

e resultados:

Execução 1

method1 took 841 ms, result was 2
method2 took 841 ms, result was 2
method3 took 85058 ms, result was 2

Execução 2

method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2
inder
fonte
0

Uma exceção é destinada ao tratamento de condições inesperadas apenas no tempo de execução .

Usar uma exceção no lugar da validação simples que pode ser feita em tempo de compilação atrasará a validação até o tempo de execução. Por sua vez, isso reduzirá a eficiência do programa.

Lançar uma exceção em vez de usar uma validação simples if..else também tornará o código complexo para escrever e manter.

Gopinath
fonte
-3

Minha opinião sobre a velocidade de exceção versus a verificação de dados programaticamente.

Muitas classes tinham um conversor String to value (scanner / analisador), bibliotecas respeitadas e conhecidas também;)

geralmente tem forma

class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}

nome da exceção é apenas um exemplo, geralmente não está marcado (tempo de execução), então lança declaração é apenas minha imagem

às vezes existe uma segunda forma:

public static Example Parse(String input, Example defaultValue)

nunca jogando

Quando o segundo item não estiver disponível (ou o programador ler muito menos documentos e usar apenas primeiro), escreva esse código com expressão regular. Expressões regulares são legais, politicamente corretas etc:

Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
  Example v = Example.Parse(src);
}

com esse código, os programadores não têm custo de exceções. MAS TEM um custo MUITO ALTO comparável de expressões regulares SEMPRE versus um pequeno custo de exceção às vezes.

Eu uso quase sempre nesse contexto

try { parse } catch(ParsingException ) // concrete exception from javadoc
{
}

sem analisar o stacktrace, etc, acredito que após as palestras de vocês bastante velocidade.

Não tenha medo Exceções

Jacek Cz
fonte
-5

Por que as exceções devem ser mais lentas do que o retorno normal?

Contanto que você não imprima o rastreamento de pilha no terminal, salve-o em um arquivo ou algo semelhante, o bloco de captura não funciona mais do que outros blocos de código. Portanto, não consigo imaginar por que "lançar novo my_cool_error ()" deve ser tão lento.

Boa pergunta e estou ansioso para obter mais informações sobre este tópico!

qualbeen
fonte
17
A exceção deve capturar as informações sobre o rastreamento de pilha, mesmo que ele não seja realmente usado.
Jon Skeet