Anonymous Methods e Closures no Delphi 2009

Escrito em 29 de agosto de 2008 em Linguagem Delphi por Leonel Togniolli

Já conhecemos a sintaxe dos anonymous methods do Delphi 2009. A parte interessante deste novo recurso é que eles são closures.

Closure é a união do código com o seu escopo. Isso quer dizer que o novo método tem acesso às variáveis locais do método que o criou, mesmo depois que ele terminou. Vamos ver como isso funciona com um exemplo:

type
  TContador = reference to function: Integer;

function CriaContador(Inicial, Final: Integer): TContador;
var
  i: Integer;
begin
  i := Inicial;
  Result := function: Integer
  begin
    Result := i;
    Inc(i);
    if i > Final then
      i := Inicial;
  end;
end;

var
  Contador: TContador;
  i: Integer;
begin
  Contador := CriaContador(5, 12);
  for i := 0 to 20 do
    WriteLn(Contador);
end.

Antes de ver a listagem da saída do programa, Vamos entender o código. TContador é um tipo que representa uma referência a uma função que retorna um número. Neste caso, vai retornar uma sequência de números, um número novo cada vez que for chamada. CriaContador é uma função que retorna um método desse tipo, recebendo os valores inicial e final que a sequência vai ter. Repare que o corpo de CriaContador tem apenas duas linhas de código que serão executadas quando ela for chamada: ela inicializa o contador, e atribui um novo método para o resultado.

No corpo do programa criamos um novo contador na faixa 5 até 12, e chamamos essa função 21 vezes, escrevendo o resultado no console.  Em cada chamada da função, ela retorna o valor atual de i, o incrementa, e caso tenha passado do valor final, volta ao primeiro.

Quais serão os 21 números escritos no console?

5
6
7
8
9
10
11
12
5
6
7
8
9
10
11
12
5
6
7
8
9

Ou seja, o programa funciona exatamente como descrevi.

Cada instância do método captura o escopo daquele momento. Se criarmos dois contadores:

var
  Contador, Contador2: TContador;
  i: Integer;
begin
  Contador := CriaContador(5, 8);
  Contador2 := CriaContador(1, 3);
  for i := 0 to 5 do
    WriteLn(Contador,':',Contador2);
end.

podemos ver que eles funcionam de forma completamente independente:

5:1
6:2
7:3
8:1
5:2
6:3

Essa é uma nova técnica possível no Delphi 2009. Para implementar o equivalente sem este recurso, seria necessário criar classes para o contador, construir instâncias e liberá-las. Ou seja, muito mais código e complicação.

O Craig Stuntz escreveu um exemplo muito interessante utilizando esse método pra implementar o Crivo de Eratóstenes. Ele entrou no assunto da técnica de currying, que vai ficar para o próximo artigo.

Se você é novo por aqui, não deixe de assinar o feed RSS ou notificações por email. Não perca novos artigos!

Anonymous Methods no Delphi 2009

Escrito em 28 de agosto de 2008 em Linguagem Delphi por Leonel Togniolli

Um dos novos recursos no Delphi 2009 é anonymous methods. É também chamado de “referências a métodos”, pois a declaração de um tipo procedural é feita com a sintaxe “reference to function/procedure”:

type
  TComparaString = reference to function(const S1, S2: string): Integer;

Esse tipo pode ser usado como qualquer outro tipo procedural:

procedure TLista.Ordena(Compara: TComparaString);
var
  i, j: Integer;
begin
  for i := 0 to FItems.Count - 2 do
    for j := FItems.Count - 1 downto i + 1 do
      if Compara(FItems[j], FItems[i]) < 0 then
         Troca(i, j);
end;

(Perdoem-me o Bubble Sort)

Não precisamos mais declarar um método separadamente para cada forma diferente de ordenação que for necessária. O código abaixo ordena uma lista alfabeticamente, depois de forma inversa, e finalmente de acordo com o tamanho da string:

  Lista := TLista.Create;
  try
    Lista.Adiciona('Um');
    Lista.Adiciona('Dois');
    Lista.Adiciona('Tres');
    Lista.Adiciona('Quatro');
    Lista.Adiciona('Cinco');
    Lista.Ordena(function(const S1, S2: string): Integer
                 begin
                   Result := CompareStr(S1, S2);
                 end);
    WriteLn(Lista.Texto);
    Lista.Ordena(function(const S1, S2: string): Integer
                 begin
                   Result := CompareStr(S2, S1);
                 end);
    WriteLn(Lista.Texto);
    Lista.Ordena(function(const S1, S2: string): Integer
                 begin
                   Result := Length(S1) - Length(S2);
                   if Result = 0 then
                     Result := CompareStr(S1, S2);
                 end);
    WriteLn(Lista.Texto);
  finally
    Lista.Free;
  end;

A resultado do programa é:

Cinco
Dois
Quatro
Tres
Um

Um
Tres
Quatro
Dois
Cinco

Um
Dois
Tres
Cinco
Quatro

Até aí, o recurso não é nada demais – permite economizar algumas linhas em troca de uma sintaxe discutivelmente mais confusa. Ele fica realmente interessante quando se nota que é, de fato, uma Closure, que exploraremos no próximo artigo.

Beyond Compare v.3

Escrito em 27 de agosto de 2008 em Utilitários por Leonel Togniolli

Acho essencial revisar alterações feitas no código fonte antes de um check-in, além de fazer comparar revisões no histórico. Uma boa ferramenta de comparação de arquivos é fundamental.

Já experimentei diferentes ferramentas, inclusive mantendo algumas customizações no exemplo TextDiff, que acompanha o componente TDiff (que permite embutir esse tipo de comparação na sua aplicação feita em Delphi). Mas sempre gostei mesmo do Beyond Compare, e acabei comprando uma licença cerca de um ano e meio atrás, por um preço bastante razoável.

Semana passada vi que o Beyond Compare tinha lançado a versão 3.0:

BC3

A nova versão possui diversos novos recursos, incluindo suporte a Unicode e 3-way merge (que fazia falta no passado).

Fui para o site preparado para comprar a atualização – para minha surpresa eles oferecem atualização grátis para quem comprou uma licença a partir de 1º de janeiro de 2007, quase um ano e nove meses atrás. Preenchi um formulário no site deles e em alguns minutos tinha a chave da nova versão no meu email.

Definitivamente recomendo Beyond Compare para comparação e sincronização de arquivos e pastas, pelos recursos e preço acessível. Qual a ferramenta de comparação que você usa?

Otimização de Código, parte II: Conhecendo Gargalos e Profilers

Escrito em 26 de agosto de 2008 em Performance por Leonel Togniolli

Já vi acontecer inúmeras vezes: o sistema é escrito, testado, e funciona bem. É instalado em produção e funciona por uma semana ou duas. Então começa a ficar extremamente lento, a ponto de não ser mais usável. A sequência é também muito comum: o programador vai “otimizando” o código no escuro, usando a intuição, e perde muito tempo fazendo pequenas melhorias.

A solução para a primeira parte do problema é simples, mas geralmente ignorada: sempre teste utilizando um volume considerável de dados. É simples testar com dois ou três registros que você mesmo cadastrou, mas é importantíssimo garantir que a performance continue adequada com um volume de dados real. Utilize uma cópia de dados de produção caso existam e estejam disponíveis, ou utilize um gerador de dados (como o exemplo  que acompanha o DBX4) para popular uma quantidade de registros compatível com o que volume esperado para produção.

O segundo problema é mais interessante. A intuição geralmente está errada – o que torna o código lento pode ser o que você menos espera.

Gargalos

Geralmente a lentidão é causada por gargalos no código, trecho de uma ou duas linhas que levam uma grande porcentagem do tempo total de processamento. Muitas vezes é fácil de encontrar uma alternativa para evitar essa demora, como reordenar o código para tratar a condição mais comum primeiro. Ao resolver um gargalo geralmente outro toma seu lugar, passando a ser responsável pela maioria da demora restante. A partir daí, resolver gargalos é uma questão de um retorno decrescente: cada otimização vai diminuir cada vez menos o retorno obtido.

Como um exemplo, vamos imaginar um hipotético processamento de registros que leve 10 segundos. Utilizando ferramentas apropriadas, descobrimos que a abertura da query para trazer os registros leva 80% do tempo. Analisando esta consulta, descobrimos uma forma de alterá-la para abrir praticamente instantaneamente. Agora o processamento leva cerca de 2 segundos. Se conseguirmos encontrar um novo gargalo que leve 80% do novo tempo e uma forma de removê-lo, ganhamos mais 1.6 segundos. Se repetirmos o processo mais uma vez, não conseguiremos melhorar mais que meio segundo. Se a qualquer momento trabalharmos em em outro trecho do código que não ser um desses três gargalos, estaremos perdendo nosso tempo e não teremos melhorias maiores que alguns milisegundos. É necessário bom senso para determinar a hora de parar.

Se não existe um gargalo claro, e a performance em geral ainda não é adequada, provavelmente é hora de procurar um algoritmo completamente diferente para resolver o problema.

Na prática nem sempre é possível remover gargalos completamente. Mesmo assim, deve se ter em mente que otimizar o restante do código muitas vezes não trará resultados perceptíveis.

No exemplo acima, escrevi que descobrimos o tempo levado por cada trecho do código utilizando “ferramentas apropriadas”. O que seria isso?

Profilers

Profilers são ferramentas que permitem determinar o tempo gasto na execução do seu código. Existem várias formas de fazer essa medição, desde uma forma arcaica manual, salvando o horário atual em um arquivo de log por exemplo, ou bem elaborada utilizando uma ferramenta profissional como o AQTime. Utilizar uma ferramenta profissional é ótimo, é claro, mas é cara e algumas vezes até um exagero para um problema mais simples.

Existem duas categorias de profilers: Sampling Profilers e Intrumenting Profilers. A primeira faz medições (samples) regulares em pontos aleatórios (a cada milisegundo, por exemplo). Essa forma não afeta a execução do código, mas pode ser pouco precisa. Um Intrumenting Profiler adiciona código para efetuar medições em lugares exatos dentro do seu programa. Essa categoria se subdivide em profilers que instrumentam seu código fonte inserindo essas verificações, e profilers que alteram o código de máquina conforme ele é executado para inseri-las. Uma maior precisão é conseguida desta forma, mas sob o custo de alterar sutilmente o fluxo do seu código. Como na mecânica quântica, o ato de medir pode pode estar alterando o resultado. Na prática, nenhum dos dois é extremamente superior: deve-se pesar os prós e contras, e quem sabe até utilizar os dois tipos como uma boa alternativa para encontrar diferentes tipos de gargalos.

A forma mais simples de determinar o tempo levado por um método é utilizar uma forma simples e manual de um source intrumenting profiler – escrever código para salvar o tempo inicial e final, e exibir/armazenar esse valor. A glanularidade é exatamente qual quiser, até cada linha. A precisão depende da forma que o tempo for medido – não recomendo a função Now, pois não é muito precisa. GetTickCount é adequada para a maioria dos casos, tendo precisão de 1ms. Pode ser usada mais ou menos assim:

var
  t: Cardinal;
begin
  t := GetTickCount;
  EfetuaProcessamento;
  WriteLn(GetTickCount - t);
end;

Se uma precisão maior que 1ms for necessária, recomendaria a API QueryPerformanceCounter. A precisão dela depende do processador, e é necessário chamar QueryPerformanceFrequency para determiná-la. Outra alternativa é o opcode RDTSC, mnemônico para read time stamp counter, que também retorna o número de ticks desde que o processador foi reiniciado, assim como GetTickCount, mas com maior resolução. Essa alternativa não é recomendada hoje em dia por potenciais problemas relacionados a processadores multicore e processadores com clock variável (geralmente em portáteis).

Essa forma manual de profiling é claramente adequada apenas para pequenos problemas e testes rápidos. Para casos mais elaborados, deve-se considerar utilizar algum dos profilers disponíveis:

ProDelphi é um source instrumenting profiler, portanto ele faz (e desfaz) alterações no seu código fonte. Possui uma versão gratuita, com limitações, e uma versão comercial por 47.50 €.

GpProfile é outro source intrumenting profiler. Gratuito, open source, mas sem atualizações por muitos anos.

Sampling Profiler é, como o nome diz, um sampling profiler, Freeware, mas sem código fonte. Uma boa alternativa pra quem não quer gastar com ferramentas comerciais e não quer alterações no seu código fonte.

AQTime é, sem dúvida, a ferramenta mais avançada e completa. É um instrumenting profiler que só altera o código de máquina ao ser executado, então não requer nenhuma alteração no código fonte. Além de tempo gasto em procedimentos e linha por linha, possui modos onde verifica uso de memória, de recursos, e diversas outras análises extremamente interessantes. Custa $599.

Na terceira parte desta série pretendo apresentar um exemplo prático de otimização de um projeto usando um profiler.

Anunciado Delphi 2009

Escrito em 25 de agosto de 2008 em Delphi por Leonel Togniolli

O Delphi 2009 acaba de ser anunciado. Já está disponível para pré-venda, e acredito que já esteja disponível para download para quem optar por Eletronic Software Delivery em pouco tempo.

Apesar de o principal novo recurso ser o total suporte de Unicode na VCL, vários outros novos recursos chamam a atenção:

  • Novos controles na VCL, com destaque ao Ribbons estilo Office 2007.
  • Melhorias na ImageList, incluindo suporte a PNG.
  • Novos recursos de linguagem, com destaque a Generics e Anonymous Methods.
  • VCL pra Web atualizada com suporte a AJAX e Silverlight.
  • DataSnap 2009, que simplifica o desenvolvimento de aplicações três camadas sem uso de COM.

Vale a pena conferir a lista de componentes de terceiros que já foram atualizados para ter suporte ao Delphi 2009.

Pretendo detalhar os novos recursos em futuros artigos, portanto aguarde atualizações.