Otimizar método Java utilizando o conceito de Escopos – java android desempenho

Pergunta:


Bem a algum tempo atrás, quando tive algumas aulas de J2ME para Mobile (praticamente falecido ✞), onde fui apresentado a um conceito de escopo até então desconhecido por mim, que seria esse:

{
    // cria um novo escopo
}

Onde em qualquer lugar de um método é possível forçar novos escopos. Pesquisando encontrei algumas fontes e referências que podem ser o artigo Thinking in Java no site Linuxtopia e esta outra questão do StackOverflow.

Então buscando otimizar alguns recursos em meus aplicativos Android, pensei em refatorar meus códigos para tirar proveito dessa forma de gerenciamento de recursos, só que não tenho conhecimento se isso realmente pode fazer alguma diferença.

Então apresento o seguinte exemplo, para contextualizar minha dúvida:

Exemplo:

Suponhamos que eu tenha uma class grande que aloque muitos recursos ao ser inicializada:

public class ClasseGrande{

    // ...

    public void alocaMuitosRecursos(){
        // Aqui inicia varios atributos da ClasseGrande()
        // ...
    }

    public String obtemResultado(){
        return "Resultado da classe grande";
    }    
}

E as 3 seguintes abordagens de utilização:

Abordagem 1 (A minha atual):

// tudo no mesmo scope
public void testScopeMethod1(){
    ClasseGrande classeGrande = new ClasseGrande();
    classeGrande.alocaMuitosRecursos();

    // ... usar a classe grande

    String resultado = classeGrande.obtemResultado();

    // agora não se utiliza mais a classe grande só se manipula seu resultado

    // faz vários processos com o atributo resultado

    // ...

    // termina método, e só aqui todos os recursos são liberados para GC. Certo?
}

Abordagem 2 (utilizando métodos para dividir escopos):

// utiliza método para trocar de scope
public void testScopeMethod2(){
    String resultado = processaClasseGrande();

    // faz vários processos com o atributo resultado

    // ...

    // termina método, e libera os recursos do método testScopeMethod2
}

private String processaClasseGrande(){
    ClasseGrande classeGrande = new ClasseGrande();
    classeGrande.alocaMuitosRecursos();

    // ... usar a classe grande

    return classeGrande.obtemResultado();

    // aqui a classe grande já é liberada, pois finalizou-se seu escopo de método. Certo?
}

Abordagem 3 (utilizando sub escopos dentro do próprio método, logo após utilizar o recurso):

// com sub scope no método
public void testScopeMethod3(){
    String resultado;
    {
        ClasseGrande classeGrande = new ClasseGrande();
        classeGrande.alocaMuitosRecursos();

        // ... usar a classe grande

        resultado = classeGrande.obtemResultado();
        // aqui a classe grande já é liberada, pois finalizou-se seu escopo. Certo?
    }

    // agora não se utiliza mais a classe grande só se manipula seu resultado

    // faz vários processos com o atributo resultado

    // ...

    // termina método, e libera os recursos do método testScopeMethod, mas a classe grande já foi libera logo após ser utilizada. Certo?
}

Perguntas:

  • Isso faz realmente diferença de performance? Por que?
  • Se faz diferença, as abordagens 2 e 3 apresentam diferenças de
    performance? Onde e por que?
  • Se isso faz algum sentido, seria uma boa prática ter isso em mente
    para projetos robustos, principalmente pensando em dispositivos
    moveis?
Autor da pergunta Fernando Leal

Edgar Muniz Berlinck

Algo muito importante sobre escopo é o tempo que seus objetos permanecerão na heap (Área da memória onde a JVM aloca os objetos).

Não sendo interessante que um objeto permaneça em memória além do necessário, então é importante SIM definir bem os escopos pois facilita o trabalho do Garbage Colector.

Então, respondendo suas perguntas:

Isso faz realmente diferença de performance? Por que?

Talvez. É importantíssimo fazer bom uso dos recursos computacionais, principalmente quando falamos de celulares e gatgets do tipo.

Utilizar bem o conceito de escopo é apenas um fator positivo em seu código, mas não é tudo. Definir bem o escopo talvez não deixe seu programa mais rápido, mas o manterá robusto e consistente, diferente daqueles programas que vão ficando mais e mais lentos no fim do dia, obrigando-nos a dar reboots no mesmo.

Se faz diferença, as abordagens 2 e 3 apresentam diferenças de
performance? Onde e por que?

Considerando o que eu disse na primeira resposta e analisando as abordagens, não. Olhando rapidamente as duas eu não vejo muita diferença entre elas.

Particularmente eu prefiro a abordagem dois, pois cada objeto vai existir durante a execução de seu respectivo método.

A abordagem 3 pode ser interessante em métodos muito grandes que alocam uma boa quantidade de objetos, mas mesmo assim eu consideraria um refactoring.

Se isso faz algum sentido, seria uma boa prática ter isso em mente
para projetos robustos, principalmente pensando em dispositivos
moveis?

Sim. Se for comparar com um servidor, dispositivos móveis são muito limitados. Precisamos sempre ter em mente que um app que consome muitos recursos consome muita bateria, ninguém vai usar um app destes.

Não programe de um jeito amigável ao JIT, deixe ele fazer o trabalho

Em uma HotSpot comum, digamos, uma versão muita usada na Indústria, como a JVM 6, diversos recursos existem para permitir que o código que você escreveu seja otimizado.

Você escreve um arquivo .java, ele é compilado para .class e então é executado dentro de uma JVM que compila em tempo de execução para código nativo.

Durante esse processo, acontecem fenomenos no seu código, entre eles:

  • Inline Expasion – A chamada do método é substituida pelo corpo dele;
  • Reordering – Seu código é reordenado para executar da maneira mais eficaz pelo processador;
  • Escape Analyses – Cópias defensivas não são alocadas na memória se a JVM percebe que não são alteradas por código cliente (classes que fizeram chamada a métodos), Métodos sincronizados (synchronized) são chamados sem o overhead de tráfego no Shared Memory Bus da máquina, caso a JVM perceba que este método não é executado concorrentemente (recurso padrão a partir da versão 6u23 da HotSpot).

Esses fenômenos acontecem durante a execução do programa, que vai sendo otimizado baseado na sua utilização, é por isso que em muitos dispositivos Android, ao se reiniciar o sistema, ele está mais lento. O que não acontece mais a partir da versão 5.0 que utiliza ART, ou Android Runtime, que utiliza outra técnica, chamada de A Head-of-time compilation.

Não utilize escopos/blocos de códio em nome da performance

Otimização prematura vai te causar problemas de design e você terá códigos mais verbosos.

A fato de um objeto sair de escopo do método não quer dizer que ele será coletado.

O objeto sem referências fortes apenas será coletado no próximo ciclo do Garbage Collector, que geralmente ocorre em duas fases – Major Collections e Minor Collections, em suma ele pode permanecer no heap mesmo sem referência.

Recomendo fortemente que você veja este exemplo:
http://docs.oracle.com/cd/E15289_01/doc.40/e15058/underst_jit.htm#i1084566

Moral, dedique-se ao design primeiro, otimize depois.

A abordagem 2 e 3 não tem diferença, em ambos você dividiu o código em blocos separados para diminuir o escopo dos objetos.

Sobre a abordagem 1, se ainda tiver muito processamento a ser executado abaixo da linha:

String resultado = classeGrande.obtemResultado();

Você pode dividir ele em métodos diferentes tanto para facilitar a leitura, quando para poder liberar o objeto para o ‘garbage collector’ (não significa que ele será removido da memoria, apenas que ele está disponível).

Fonte

Related Posts:

Qual a diferença entre AppCompatActivity e Activity? – android android-activity
Pergunta: Qual a diferença da AppCompatActivity para Activity ? A partir de qual versão a AppCompatActivity foi adicionada ao Android? Autor da pergunta Luhhh A diferença reside ...
Como abreviar palavras em PHP? – php string
Pergunta: Possuo informações comuns como nome de pessoas e endereços, e preciso que elas contenham no máximo 30 caracteres sem cortar palavras. Exemplo: 'Avenida Natalino João Brescansin' ...
Qual é a finalidade de um parêntese vazio numa declaração Lambda? – c# expressões-lambda característica-linguagem
Pergunta: Criei um exemplo de uma declaração Lambda sem argumentos, entretanto, estou com duvidas referente a omissão do parêntese vazio () na declaração. Veja o exemplo: class ...
Boas práticas para URI em API RESTful – api rest restful
Pergunta: Estou com dúvida em relação às URIs de alguns recursos da api que estou desenvolvendo. Tenho os recursos projetos e atividades com relação 1-N, ...
Dúvidas sobre a integração do MySQL com Java – java mysql netbeans
Pergunta: Estou criando um sistema no NetBeans, utilizando a linguagem Java e o banco de dados MySQL. Escrevi o seguinte código para realizar a conexão ...
Qual é a finalidade da pasta Model do framework Inphinit? – php inphinit
Pergunta: No Inphinit micro-framework existe a pasta Model que fica dentro da pasta application, e nela é onde ficam as classes, mas eu estou muito ...
Uso do ‘@’ em variáveis – javascript typescript coffeescript
Pergunta: Vejo em algumas linguagens que compilam para javascript, como TypeScript e CoffeeScript, o uso do @ em variáveis, como também, casos em que o ...
Qual tamanho máximo um arquivo JSON pode ter? – json arquivo
Pergunta: Vou dar um exemplo para conseguir explicar minha duvida: Preciso recuperar informação de imagens vindas de uma API, esse banco de imagens me retorna JSON's ...
O que é Teste de Regressão? – terminologia engenharia-de-software testes
Pergunta: Na matéria de Teste de Software o professor abordou um termo chamado Teste de Regressão, isto dentro da disciplina de teste de software. Sendo ...
O que é um construtor da linguagem? – php característica-linguagem
Pergunta: Em PHP, já li e ouvi várias vezes a respeito dos Construtores da Linguagem. Os casos que sempre ouvi falar deles foi em casos ...
Função intrínseca para converter numérico para string – cobol
Pergunta: Estou a tentar saber se existe alguma função intrínseca do COBOL para converter um data numérico para string sem precisar usar a cláusula REDEFINES: ( ...
Porque usar implements? – java android
Pergunta: Qual a diferença entre usar btn.setOnClickListener(new OnClickListener() { e public class MainActivity extends Activity implements OnClickListener{ Estive fazendo um curso de Android e meu professor falou que ...
O que é XHTML e quando deve ser usado? – html xml xhtml
Pergunta: O que eu sei é que o XHTML precisa ser XML válido. Isso implica, por exemplo, que todas as tags precisam ser fechadas. Por ...
Uma placa aceleradora de vídeo pode melhorar o desempenho não-gráfico? [fechada] – desempenho
Pergunta: Para desenvolver em Ruby on Rails, eu utilizo aqui uma máquina virtual do VirtualBox com Ubuntu Server 14.04 sem interface gráfica instalada. Recentemente descobri uma ...
Concat() VS Union() – c# .net
Pergunta: Qual a diferença entre Concat() e Union() ? Quando usar Concat() e quando usar Union() ? Somente pode ser usado em list ? ...

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *