Método X Closure: Melhorando o desempenho da aplicação – Parte 3

Salve Groovy Geeks de plantão!

Chegamos ao final dessa jornada sobre métodos e closures groovy.

Pois bem, reta final apenas para essa serie dividida em três episódios, lembrando que nada nos impede de fazer como os Simpson e dar continuidade em outras temporadas.

Nos últimos artigos vimos como as closures podem ser uteis no dia a dia do desenvolvedor groovy. Porém, o foco deste post em questão é trazer a tona detalhes não tão claros sobre o mundo das closures, nos levando a uma análise um pouco mais profunda antes de substituirmos métodos por closures.

Para começar temos um exemplo de um calculo de soma simples, feito no eclipse com o plugin do groovy, onde o JUnit forneceu as métricas.

Exemplo 1

Comparação Metodo X Closure em cálculos matemáticos:

public class PerfmTests {

	@Test
	public void testMetodos() {
		MetClos mc = new MetClos()
		1000.times{
		mc.m1()
		// depois com
		// mc.m2(10,20)
		}
		Assert.assertNotNull('')
	}

	@Test
	public void testClosures() {
		MetClos mc = new MetClos()
		1000.times{
		mc.c1()
		// depois com
		// mc.c2(10,20)
		}
		Assert.assertNotNull('')
	}

}

class MetClos {
	def c1 = {
		def s = 1 + 1
		println s
	}

	def c2 = { n1,n2 ->
		def s = n1 + n2
		println s
	}

	void m1() {
		def s = 1 + 1
		println s
	}

	void m2(n1,n2) {
		def s = n1 + n2
		println s
	}
}

Neste exemplo os testes com closures foram cerca de 10x mais rápidos que os métodos.

Cuidado

Um ponto negativo de trocar todos os métodos por closures é que cada closure vira um .class distinto e isso pode inchar bastante o perm space da JVM.

Exemplo 2

Comparação Método X Closure em comparações de elementos:

def numberCount = 10000
def random = new Random()
def unorderedList1 = (1..numberCount).collect{random.nextInt()}
def unorderedList2 = (1..numberCount).collect{random.nextInt()}

def timeit = {String message, Closure cl->
    def startTime = System.currentTimeMillis()
    cl()
    def deltaTime = System.currentTimeMillis() - startTime
    println "$message: \ttime: $deltaTime"
}

timeit("compare using closure") {
    def comparator= [ compare: { a,b -> return b <=> a }] as Comparator
    unorderedList1.sort(comparator)
}

timeit("compare using method") {
    Comparator comparator = new MyComparator()
    unorderedList2.sort(comparator)
}

class MyComparator implements Comparator {
    int compare(a, b) {return b <=> a}
}

Neste caso, temos exatamente o contrário. O desempenho da closure foi bem abaixo do esperado.

Para o algoritimo descrito as closures levaram 270ms diante dos métodos que levaram 50ms.

Exemplo 3

Melhorando o desempenho das closures em loops:

Pois bem… Um recurso muito interessante e que pode nos proporcionar o ganho considerável de desempenho, claro quando utilizado com sabedoria, é o groovy .memoize().

Este recurso está disponível a partir do groovy 1.8+.

Explicação do exemplo a seguir

Suponha uma função “distance” que calcula a distância entre dois pontos.

Closure distance tradicional:

def distance = {x1, y1, x2, y2 ->
    sleep(200)  //just to add a delay for demo purposes
    Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
}

Chamada 5 vezes utilizando uma closure tradicional:

5.times {
    def t1 = System.currentTimeMillis()
    println distance(100, 20, 400, 10)
    println "took: ${System.currentTimeMillis() - t1} milliseconds to execute"
 }
[/sourcecode ]
Após executarmos este trecho de código teremos a seguinte saída:


300.1666203960727
took: 201 milliseconds to execute
300.1666203960727
took: 200 milliseconds to execute
300.1666203960727
took: 201 milliseconds to execute
300.1666203960727
took: 201 milliseconds to execute
300.1666203960727
took: 201 milliseconds to execute
[/sourcecode ]

Sempre que chamamos a closure todos seus itens são chamados novamente, exigindo reprocessamento.

Vamos fazer o mesmo, porém utilizando o <em>memoize</em>

def distance = {x1, y1, x2, y2 ->
    sleep(200)  //just to add a delay for demo purposes
    Math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
}.memoize() //Note now closure is memoized

//To Call It 5 Times
5.times {
    def t1 = System.currentTimeMillis()
    println distance(100, 20, 400, 10)
    println "took: ${System.currentTimeMillis() - t1}"
}

Após executar o código utilizando o memoize temos o seguinte resultado:

300.1666203960727
took: 202 milliseconds to execute
300.1666203960727
took: 1 milliseconds to execute
300.1666203960727
took: 1 milliseconds to execute
300.1666203960727
took: 0 milliseconds to execute
300.1666203960727
took: 0 milliseconds to execute

Pronto! Vemos claramente o quanto podemos ganhar desempenho conhecendo os recursos oferecidos pela linguagem.

Gostaria de salientar que, devemos tomar cuidado para não julgar precipitadamente o que é melhor, levando em consideração que desempenho também tem haver com a forma implementada e recursos oferecidos pelas linguagens.

Como dito anteriormente, estes três posts foram baseados nas respostas postadas no fórum do grails Brasil. Logo, gostaria de agradecer aqueles que contribuíram diretamente para a finalização deste post:

  • José Yoshiriro: Exemplo 1;
  • Raphael Miranda: Observação 1;
  • Pedro Henrique: Exemplo 2,  Exemplo 3;
  • Henrique Lobo Weissmann e Raphael Miranda: Considerações.

Gostaria também de agradecer aqueles que contribuíram indiretamente, os responsáveis pelo conteúdo dos links utilizados no fórum:

  • Michael Pollmeier:  closures significantly slower than methods?
  • Kushal Likhi: memoize sample

Segue links utilizados:

Por: Jonatas Emidio

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: