É finalmente caro

14

No caso de código em que você precisa fazer uma limpeza de recursos antes de sair de uma função, há uma grande diferença de desempenho entre essas duas maneiras de fazê-lo.

  1. Limpando o Recurso Antes de Cada Declaração de Retorno

    void func()
    {
        login();
    
        bool ret = dosomething();
        if(ret == false)
        {
            logout();
            return;
        }
    
        ret = dosomethingelse();
        if(ret == false)
        {
            logout();
            return;
        }
    
        dootherstuff();
    
        logout();
    }
  2. Limpando o Recurso em um Bloco Finalmente

    void func()
    {
        login();
    
        try
        {
            bool ret = dosomething();
            if(ret == false)
                return;
    
            ret = dosomethingelse();
            if(ret == false)
                return;
    
            dootherstuff();
    
        }
        finally
        {
            logout();
        }
    }

Fiz alguns testes básicos em programas de amostra e não parece haver muita diferença. Eu prefiro muito a finallymaneira de fazer isso - mas estava pensando se isso causaria algum impacto no desempenho de um grande projeto.

user93353
fonte
3
Não vale a pena postar como resposta, mas não. Se você estiver usando Java, que espera que as exceções sejam a estratégia padrão de tratamento de erros, use try / catch / finalmente - a linguagem é otimizada para o padrão que você está usando.
Jonathan Rico
1
@ JonathanRich: como assim?
precisa saber é
2
Escreva o código de forma limpa, de maneira que expresse o que você está tentando realizar. Otimize mais tarde quando souber que tem um problema - não evite os recursos de idioma porque acha que pode economizar alguns milissegundos de algo que não é lento em primeiro lugar.
Sean McSomething
@mikeTheLiar - Se você quer dizer que eu deveria escrever if(!cond), é o Java que me fez fazer isso. Em C ++, é assim que escrevo códigos tanto para booleanos quanto para outros tipos - ie int x; if(!x). Como o java me permite usar isso apenas para booleans, parei totalmente de usar o if(cond)& if(!cond)in java.
user93353
1
@ user93353 que me deixa triste. Eu preferiria ver do (someIntValue != 0)que comparar e não avaliar booleanos. Isso cheira a mim, e refatoro-o imediatamente quando o vejo na natureza.
MikeTheLiar # 7/13

Respostas:

23

Conforme indicado em Quão lenta são as exceções de Java? pode-se ver que a lentidão de try {} catch {}está dentro da instatiação da própria exceção.

Criar uma exceção buscará toda a pilha de chamadas do tempo de execução e é aí que está a despesa. Se você não estiver criando uma exceção, isso representa apenas um aumento muito pequeno de tempo.

No exemplo dado nesta pergunta, não há exceções, não seria de esperar que as lentilhas fossem criadas - elas não foram criadas. Em vez disso, o que está aqui é try {} finally {}lidar com a desalocação de recursos dentro do bloco final.

Portanto, para responder à pergunta, não, não há uma despesa real de tempo de execução dentro de uma try {} finally {}estrutura que não usa exceções (não é algo inédito, como visto). O que é possivelmente caro é o tempo de manutenção, quando alguém lê o código e vê esse estilo de código não típico, e o codificador precisa entender que algo mais acontece nesse método após o returnretorno à chamada anterior.


Como foi mencionado, a manutenção é um argumento para ambas as maneiras de fazer isso. Para constar, após consideração, minha preferência seria a abordagem finalmente.

Considere o tempo de manutenção de ensinar a alguém uma nova estrutura de linguagem. Ver try {} finally {}não é algo que se vê frequentemente no código Java e, portanto, pode ser confuso para as pessoas. Há um certo tempo de manutenção para aprender estruturas um pouco mais avançadas em Java do que o que as pessoas estão familiarizadas em ver.

O finally {}bloco sempre corre. E é por isso que você deve usá-lo. Considere também o tempo de manutenção da depuração da abordagem não definitiva, quando alguém se esquece de incluir um logout no momento apropriado, ou o chama no momento inadequado, ou se esquece de retornar / sair depois de chamá-lo para que seja chamado duas vezes. Existem tantos erros possíveis com isso que o uso de try {} finally {}torna impossível de ter.

Ao pesar esses dois custos, é caro em tempo de manutenção não usar a try {} finally {}abordagem. Embora as pessoas possam ponderar quantos milissegundos fracionários ou instruções jvm adicionais o try {} finally {}bloco é comparado à outra versão, também é necessário considerar as horas gastas na depuração da maneira menos ideal de lidar com a desalocação de recursos.

Escreva primeiro o código de manutenção e, de preferência, de forma a impedir que erros sejam gravados posteriormente.

Comunidade
fonte
1
Uma edição sugerida disse assim: "try / finalmente oferece garantias que você não teria em relação às exceções de tempo de execução. Goste ou não, qualquer linha é praticamente capaz de gerar uma exceção" - Eu: o que não é exatamente verdade . A estrutura fornecida na pergunta não possui nenhuma exceção adicional que atrasaria o desempenho do código. Não há impactos adicionais no desempenho envolvendo uma tentativa / finalmente ao redor do bloco em comparação com o bloco sem tentativa / finalmente.
2
O que poderia ser ainda mais caro em manutenção do que entender esse uso do try-finalmente, é ter que manter vários blocos de limpeza. Agora, pelo menos, você sabe que, se for necessária uma limpeza adicional, existe apenas um lugar para colocá-lo.
Bart van Ingen Schenau 04/04
@BartvanIngenSchenau Indeed. Uma tentativa-finalmente é provavelmente a melhor maneira de lidar com isso, simplesmente não é tão comum em uma estrutura que eu já vi no código - as pessoas têm problemas suficientes para entender a tentativa de pegar finalmente e o tratamento de exceções em geral .. mas tente -finalmente sem pegar? O que? As pessoas realmente não entendem isso . Como você sugeriu, é melhor entender a estrutura da linguagem do que tentar evitá-la e fazer mal as coisas.
Eu queria muito saber que a alternativa para tentar finalmente também não é gratuita. Eu gostaria de poder dar outro voto positivo por sua adição sobre os custos.
Bart van Ingen Schenau
5

/programming/299068/how-slow-are-java-exceptions

A resposta aceita nesta pergunta mostra que o empacotamento de uma chamada de função em um bloco catch try custa menos de 5% em uma chamada de função bare. Lançar e capturar realmente a exceção fez com que o tempo de execução aumentasse para mais de 66x a chamada de função simples. Portanto, se você espera que o design da exceção seja lançado regularmente, eu tentaria evitá-lo (no código crítico de desempenho com perfil adequado); no entanto, se a situação excepcional for rara, não será um grande problema.

pedregoso
fonte
3
"se a situação excepcional for rara" - bem, há uma tautologia ali. Excepcional significa raro e é exatamente assim que as exceções devem ser usadas. Nunca deve fazer parte do fluxo de projeto ou controle.
Jesse C. Slicer #
1
Exceções não são necessariamente raras na prática, é apenas efeito colateral acidental. Por exemplo, se você tem pessoas más UI pode causar o caso de uso exceção fluir mais frequentemente do que o fluxo normal
Esailija
2
Não há exceções sendo lançadas no código da pergunta.
2
@ JesseC.Slicer Existem idiomas em que não é incomum vê-los usados ​​como controle de fluxo. Como exemplo, ao iterar qualquer coisa em python, o iterador lança uma exceção de stopiteration quando terminar. Também no OCaml, sua biblioteca padrão parece ser muito "feliz", pois gera um grande número de situações que não são raras (se você tiver um mapa e tentar procurar algo que não existe no mapa que lança) )
Stonemetal
Como @stonemetal faz um dicionário Python, com um KeyError
Izkata
4

Em vez de otimizar - pense no que seu código está fazendo.

Adicionar o bloco final é uma implementação diferente - significa que você fará logout em todas as exceções.

Especificamente - se o login gerar uma exceção, o comportamento na sua segunda função será diferente do seu primeiro.

Se o login e o logout não fizerem parte do comportamento da função, sugiro que o método faça uma coisa e outra bem:

    void func()
    {
            bool ret = dosomething();
            if(ret == false)
                return;

            ret = dosomethingelse();
            if(ret == false)
                return;

            dootherstuff();

    }

e deve estar em uma função que já está encapsulada em um contexto que gerencia o logon / logout, pois estes parecem fundamentalmente diferentes do que seu código principal está fazendo.

Sua postagem está marcada como Java, que eu não conheço muito bem, mas no .net eu usaria um bloco usando para isso:

ou seja:

    using(var loginContext = CreateLoginContext()) 
    {
            // Do all your stuff
    }

E o contexto de login possui um método Dispose que fará logout e limpará os recursos.

Edit: Na verdade, eu não respondi a pergunta!

Não tenho evidências mensuráveis, mas não esperaria que isso fosse mais caro ou, certamente, não o suficiente para ser digno de otimização prematura.

Vou deixar o resto da minha resposta porque, embora não tenha respondido à pergunta, acho que é útil para o OP.

Michael
fonte
1
Para concluir sua resposta, o Java 7 introduziu uma instrução try-with-resources que seria semelhante à sua usingconstrução .net , assumindo que o recurso em questão implementa a AutoCloseableinterface.
precisa saber é
Eu até removeria essa retvariável, que é apenas atribuída duas vezes para ser verificada uma linha depois. Faça isso if (dosomething() == false) return;.
ott--
Concordo, mas eu queria manter o código OP da maneira que foi fornecido.
Michael
@ ott-- Presumo que o código do OP seja uma simplificação de seu caso de uso - por exemplo, eles podem ter algo mais parecido ret2 = doSomethingElse(ret1), mas isso não é relevante para a pergunta, por isso foi removido.
Izkata 04/04
if (dosomething() == false) ...é um código incorreto. Use o mais intuitivoif (!dosomething()) ...
Steffen Heil
1

Suposição: você está desenvolvendo no código C #.

A resposta rápida é que não há impacto significativo no desempenho ao usar os blocos try / finally. Há um impacto no desempenho quando você lança e captura exceções.

A resposta mais longa é ver por si mesmo. Se você observar o código CIL subjacente gerado ( por exemplo, refletor de porta vermelha) , poderá ver as instruções CIL subjacentes geradas e o impacto dessa maneira.

Michael Shaw
fonte
11
A pergunta está marcada com java .
Joachim Sauer #
Bem manchado! ;)
Michael Shaw '
3
Além disso, métodos não são capitalizados, embora isso possa ser apenas de bom gosto.
Erik Reppen
ainda uma boa resposta se a pergunta era c # :)
PhillyNJ
-2

Como outros já mencionado, o código Ttese terá resultados diferentes, se quer dosomethingelseou dootherstufflançar uma exceção.

E sim, qualquer chamada de função pode gerar uma exceção, pelo menos a StackOVerflowError! Embora seja raramente possível manipulá-los corretamente (como qualquer função de limpeza chamada provavelmente terá o mesmo problema, em alguns casos (como implementar bloqueios, por exemplo), é crucial manipular isso corretamente ...

Steffen Heil
fonte
2
isso parece não oferecer nada de substancial em relação às 6 respostas anteriores
gnat