Seven of Eleven

Sempre que uma linguagem de programação é assunto entre as pessoas (programadores), normalmente surgem coisas como críticas e qualidades, muitas vezes a preferência de cada um é destacado nessa ou naquela funcionalidade – sem contar quando o assunto é esta ou aquela linguagem de programação esse tipo de discussão torna-se acalorada na maioria das vezes.

Durante o ano de 2013, tive a oportunidade de participar de alguns e eventos e palestrar sobre minha linguagem favorita: C++.

Na maioria dos eventos que participei e abordei o assunto desta linguagem de programação, o tema foi relacionado principalmente a C++ 11 (a versão padrão atual) e, a vezes, sobre a próxima versão, C++ 14 – onde o documento atual (Working Draft, Standard for Programming Language C++) é o N3797 datado em 13 de Outubro de 2013, e contempla C++ 11, bem como, o que será o futuro do C++.

seven_eleven

Sintetizo aqui as minhas 7 funcionalidades favoritas presentes no C++ 11. Essas conveniências da linguagem tornam o C++ mais elegante e simples de aprender e/ou ensinar.  O objetivo é apresentar a funcionalidade, onde ela é encontrada no documento padrão e a qual a motivação pela escolha.

Lambda: 5.1.2 Lambda expressions [expr.prim.lambda] [5. Expressions [expr]]

Lambda Expressions, é um recurso da linguagem, como o próprio padrão denota, que fornece uma maneira concisa para criar objetos do tipo função (ou functors). É possível declarar e aplicar um lambda diretamente numa função ou método que tem como parâmetro um functor. Em destaque alguns exemplos do uso do lambda.

lambda expression

Motivação: Gosto desta funcionalidade pelo simples fato dela possuir um relacionamento com o que estamos acostumados na programação funcional. Ela permite a criação de high-order functions, funções que tem como parâmetro ou retorno pelo menos uma função. Além disso, ela minimiza, se não elimina completamente, a aplicação em cenários onde eram necessários o uso de std::bind1st (obsoleta no C++ 11) ou similar. Ela também suporta o conceito de closure através da lista de captura (uma lista de argumentos que é indicada entre colchetes), diferente de outras linguagens de programação, é possível determiner a granularidade da captura, onde é possível definir se será por cópia ou por referência.

Range for: 6.5.4 The range-based for statement [stmt.ranged] [6. Statements [stmt.stmt]]

O range-for é outra conveniência para iterar containers ou qualquer coisa que um par de iterators begin e end.

range-for

Motivação: Talvez aqui pode ser aplicado o slogan: “do more with less”. A grande vantagem do range-for é a sua simplicidade para iteração, sem a verbosidade dos iterators. É fácil notar a similaridade com o for do Java, do Scala e do Python ou foreach do C# para iterar coleções para nomear alguns exemplos.

Alias: 7.1.3 The typedef specifier [dcl.typedef] [7. Declarations [dcl.dcl]]

O objetivo do using, neste caso, é oferecer uma extensão para as capacidades do typedef e permitir a criação de alias paramétricos, ou seja, alias com suporte ao mecanismo de templates.

using alias

Motivação: Gosto de utilizar a funcionalidade de alias, em qualquer linguagem de programação, para renomear algum conceito para uma coisa mais significativa e/ou seja mais rápido na digitação. E aqui não é diferente.

Type inference: 7.1.6.4 auto specifier [dcl.spec.auto] [7. Declarations [dcl.dcl]]

O auto permite a inferência de um tipo. Isto ocorrerá em tempo de compilação, ou seja, o compilador tentará deduzir o tipo de dado e substituí-lo pelo tipo apropriado ou até mesmo substituido por um tipo especificado após os tokens: ->.

auto type inference

Motivação: Inferência de tipo é um ótimo recurso quando o tipo a ser deduzido é óbvio ou seu uso contextual não for muito extenso (como no caso de a dedução do tipo de um iterator via begin ou cbegin, por exemplo). Gosto de usá-lo onde não há impacto que poderá levar a uma confusão, como por exemplo, em casos de polimorfismo, onde prefiro usar um tipo explícito.

Initializer list: 8.5.4 List-initialization [dcl.init.list] [8. Declarators [dcl.decl]]

A lista inicializadora permite seu tipo receber um lista arbitrária de elementos. No entanto, esta inicialização seguirá o padrão de inicialização uniforme proposta na nova versão de padrão do C++.

initializer list

Motivação: Tudo que é em prol da uniformidade para um código ser mantido é excelente. Esta é uma conveniência simples, porém fundamental para assegurar a uniformidade proposta.

Move semantics: 12.8 Copying and moving class objects [class.copy] [12. Special member functions [special]]

Move e copy semantics é uma dualidade. É muito comum copiar objetos em C++, as vezes isto gera uma inconveniência, ao qual poderá afetar o desempenho com excessivas sequências de criação de um novo objeto, a cópia dos dados do objeto antigo para o novo e a destruição do objeto antigo. A idéia de movimentar objetos é minimizar este overhead, principalmente quando o objeto antigo deve ser destruído. Normalmente, este comportamente ocorre na presença de objetos temporários – se um objeto temporário aparece a direita de uma atribuição (assignment), o compilador invocará uma operação de movimentação automaticamente, ao invés da cópia. A idéia presente nesta funcionalidade é de transferência de propriedade dos dados (transfer of data ownership). A diferença entre copiar e mover, e o que deve ser feito é apresentado no exemplo a seguir:

move_1_of_3

move_2_of_3

move_3_of_3

Motivação: Como me disseram uma vez, esta funcionalidade já justifica mudar do C++ 98 (ou C++ 03) para o C++ 11 – concordo plenamente! É possível “forçar” a invocação de um move constructor ou move assignment através do std::move.

Variadic templates: 14.5.3 Variadic templates [temp.variadic] [14. Templates [temp]]

Variadic templates é forma arbitrária de permitir 0 ou mais argumentos em templates ou em funções.

variadic templates

Motivação: A grande vantagem dos variadic templates é a possibilidade de escrever funções que emulem o comportamento de um “printf”, só que em tempo de compilação. Ou templates que atuem genericamente para uma grande quantidade de parâmetros e tipos distintos. Por exemplo, o std::tuple da biblioteca padrão é implementado com este recurso.

Finalizando

Bom, aqui é apresentado uma versão sintetizada de 7 conveniências do C++ 11 de minha preferência. Optei por colocar códigos de simples entendimento ao invés de uma quantidade de texto explicativos sobre o recurso. Para uma tour mais detalhada sobre estes assuntos recomendo:  A Tour of C++ de Bjarne Stroustrup.

Os slides da última palestra que ministrei em 2013 pode ser encontrada no endereço: C++ 11 e o Futuro do C++.

Acesse o código fonte completo aqui: http://bit.ly/1dUx5lY.

Versão PDF deste Post

Posted in C++ | Leave a comment

Three Types of Trees (T3)

Não canso de assistir esta ótima aula do Donald Knuth. Além de informativa e complexa, ela é muito divertida. Segundo Knuth, de tempos em tempos, durante a época do Natal, ele ministra uma aula sobre árvores (matemáticas e/ou computacionais), denominadas de Christmas Tree Lecture. Inclusive ele referencia o cartoon do XKCD sobre árvore (de natal) – onde ele é frequentemente homenageado.

1 tree

Este cartoon é um dos meus favoritos pela quantidade de coisas ligadas (intencionalmente ou não) com algoritmos e estruturas de dados. A árvore de natal representa uma árvore binária balanceada, abaixo uma estrutura de heap organizada pelo máximo (max priority queue) e a estrela no topo da árvore pode ser interpretada como o algoritmo A-Star (A-Estrela ou A*), cujo objetivo é encontrar o caminho mais curto entre um ponto de origem e um determinado ponto de destino.

Árvore Balanceada ou Não Balanceada, eis a questão

A estrutura de dados do tipo árvore é aplicada em diversos lugares: na organização do sistema de arquivos, na organização de dados para busca de informações, para determinar um caminho, na representação de uma expressão algébrica, entre outros. Um tipo comum de árvore é a binary search tree (BST), que permite a organização dos dados de modo que a busca seja eficiente. Por exemplo, a figura abaixo, retirada da Wikipedia, apresenta uma BST:

2 Sorted_binary_tree.svg

A regra básica de uma BST é: dado um determinado nó (ou vértice), todos os nós (ou vértices) filhos (ou adjacentes) estarão a esquerda, se forem menores do que o pai, ou estarão a direita, se forem maiores que o pai. Quando os valores forem iguais, é convencionado livremente se irá para esquerda ou para a direita quando aceitar entradas repetidas, caso contrário irá ignorá-lo ou subscrevê-lo. Na figura anterior, onde não há nós (ou vértices) com valores repetidos, é notado que: G é pai de I, e I está a direita porque lexicograficamente G é menor do que I. I é pai de H, e H está a esquerda de porque lexicograficamente I é maior do que H. Ou seja, G < I e I > H. Então, montar e buscar numa árvore do tipo BST é relativamente um trabalho trivial. Por exemplo, o código F# abaixo é uma estrutura cujo representa um nó (ou vértice) com bifurcação de forma genérica:

type Node<‘a> = { value : ‘a; left : Node<‘a> option; right : Node<‘a> option }

A função newNode, é uma helper para criação nós (ou vertices) também em F#:

let newNode (value, left, right) = Some { value = value; left = left; right = right }

Logo, para reproduzir a árvore da figura anterior, basta a sequência de instruções F# como segue:

let c = newNode(‘C’, None, None)
let e = newNode(‘E’, None, None)
let h = newNode(‘H’, None, None)
let a = newNode(‘A’, None, None)
let d = newNode(‘D’, c, e)
let i = newNode(‘I’, h, None)
let b = newNode(‘B’, a, d)
let g = newNode(‘G’, None, i)
let f = newNode(‘F’, b, g)

Onde é obtido o seguinte resultado:

3 fsi_node

O valor em questão é do tipo caractere, o tipo Node é genérico, logo o parametro genérico ’a será substituido por char devido ao mecanismo de inferência de tipos do F#. Por causa do tipo de dado inferido, o exemplo utiliza comparação lexicográfica, mas para outros tipos de dados isto não é necessáriamente verdade e um tipo customizado deverá prover via interfaces de comparação o comportamento desejado.

Uma ação comum com árvores é percorrê-las, onde a frase em inglês correspondente é tree traversal. Existem dois modos de busca comuns para isso: por profundidade, denominada de depth-first search e por extensão, denominada de  breadth-first search.

Em profundidade, significa que a partir de um dado nó (ou vértice), o próximo elemento a ser visitado é um elemento filho mais a esquerda. Se este filho tiver outros filhos, eles  serão os próximos a serem visitados da esquerda para direita. E isto é feito até a exaustão, ou seja, todos forem visitados. Bem, este é o tipico procedimento que é dificil explicar por palavras. Portanto, “plotando” com o Mathematica a árvore equivalente ao código anterior, temos:

4 btree

Uma busca parcial por profundidade, iniciando por F, teria como resultado as visitas subsequentes a B e A. No caso de um depth-first search, é necessário saber que para percorrer todos os nós (ou vértices) e ter um resultado de visitas, existem 3 formas categorizadas como: pre-order, in-order e post-order. Estas formas indicam, como o nó em questão é visitado ou uma função de visita atua, e como os nós (ou vértices) filhos são percorridos. Isto altera totalmente o resultado final – como será visto a seguir. Em linhas gerais, no contexto de uma BST, a ação pre-order é: visitar o nó (ou vértice) atual, percorrer a sub-árvore da esquerda e percorrer a sub-árvore da direita. A ação in-order é: percorrer a sub-árvore da esquerda, visitar o nó (ou vértice) atual e percorrer a sub-árvore da direita. A ação post-order é: percorrer a sub-árvore da esquerda, percorrer a sub-árvore da direita e visitar o nó (ou vértice) atual. Abaixo o código em F#, em destaque, usando recursão para os 3 tipos de depth-first search:

5 df_traversal_code

Se for aplicado na árvore anterior, obtemos o seguinte resultado:

6 df_traversal

Como indicado anteriormente, outro modo para percorrer uma árvore é por extensão, também conhecido como percorrer a árvore por níveis ou horizontalmente. O código em C# a seguir apresenta como implementar a árvore e o breadth-first search em destaque. A versão em C# utiliza iterator para implementar o método AsBreadthFirst:

7 bf_traversal_code

Se for aplicado na árvore anterior, obtemos o seguinte resultado:

Level order traversal sequence: F B G A D I C E H

Uma vez conhecida as caracteristicas fundamentais para percorrer um árvore, vale analisar suas caracteristicas com relação ao balanceamento. As implementações das árvores apresentadas aqui até o momento são balanceadas por mera coincidência, pois sua montagem foi manual. No entanto, é possível configurarmos uma árvore não balanceada, onde todos os nós (ou vértices) estejam dispostos a extrema esquerda.

8 unbalanced_tree

A desvantagem de uma árvore não balanceada é com relação a busca, cujo no pior caso, se todos os elementos estiverem à esquerda, como no exemplo anterior, cada um destes elementos deverá ser visitado. Ou seja, o mesmo comportamento de um array não ordenado, com  a desvatagem da localidade – no caso do array todos os elementos estão contiguamente alinhados o que é bom, dado a hierarquia de memória dos computadores aos quais estamos submetidos. O balanceamento numa árvore do tipo binária, é fundamental para diminuir a complexidade de busca com relação ao tempo. Uma das técnicas mais eficientes para balanceamento de árvores que conheço é a left leaning red black tree, criada por Robert Sedgewick. Onde os nós (ou vértices) são classificados em vermelhos ou negros. Com as seguintes propriedades para balanceamento:

1)      O nó (ou vértice) raiz é negro. Bem como, todos os nós (ou vértices) nulos são negros;

2)      Todos os nós (ou vértices) vermelhos são dispostos a esquerda;

3)      Na inserção, o nó (ou vértice) é vermelho, exceto no caso da raíz;

4)      Executar em recursividade os itens a, b e c até a exaustão:

  • Quando o filho da direita for vermelho e o filho da esquerda for negro executar rotação a esquerda;
  • Quando o filho da esquerda for vermelho e o neto da esquerda for vermelho executar rotação a direita;
  • Quando ambos filhos forem vermelhos executar a troca de cores (flip). O flip consiste em tornar os dois filhos em negros e o pai em vermelho (exceto o pai raíz, ele permancerá em negro).

Apesar de intimidadora, esta técnica é bem simples. A seguir, é apresentado o balancemento de uma árvore que recebará a sequência de 1 a 10, na notação matemática de intervalo isto significa: [1, 10].

9 llrb

Imprimindo cada inserção de 1 a 10, é apresentado o conjunto das ações executadas em cada inserção e o resultado da árvore quando percorrida com breadth-first:

10 llrb_result

A seguir o código da left_leaning_red_black_bst, em C++ 11:

11 llrb_code_1

A essência da inserção de valores fica por conta do método recursive_insert que traduz as ações enumeradas anteriormente:

12 llrb_code_2

O método recursive_insert é dividido essencialmente em 2 partes: a busca para o ponto de inserção, ou seja, onde posicionar o nó (ou vértice) corretamente de acordo com o padrão de um BST, e ajustar os nós  (ou vértices) para manter as propriedades enumeradas anteriormente.

Abaixo encontram-se os métodos que detalham cada etapa operacional exigida pelo método recursive_insert:

13 llrb_code_3

E o test case do exemplo left_leaning_red_black_bst:

14 llrb_code_4

A seguir, algumas observações interessantes relacionadas a árvores, principalmente as balanceadas:

1)      Se folhar meu livro favorito sobre STL, especificamente na página 332, é indicado que a estrutura de dados interna dos tipos set, multiset, map e multimap são árvores binárias balanceadas, cujo a implementação é compartilhada entre elas. Curiosamente, na página 315 contém a informação que os tipos set e multiset utilizam como estrutura interna uma red black tree. Se esta árvore é uma LLRB vai depender do fornecedor da STL. TreeMap e TreeSet do Java também são implementadas em termos de uma red black tree. Para .NET, uma das melhores opcções para coleções não-concorrentes é a The C5 Generic Collection Library, onde existem algumas opções de red black trees.

2)      Boost tem suporte para grafos (e por tabela árvores) com a Boost Graph Library (BGL).

15 boost_graph

16 graph

3)     Existem estruturas de dados que são persistentes, ou seja, os estados anteriores não são destruídos. Isto é comum em estrutura de dados também denominadas imutáveis recentementes liberadas para a plataforma .NET e presentes em Clojure. A obra seminal, se não for pioneira no campo, Pure Functional Data Structures de Chris Okasaki explica em detalhes este tipo de estrutura de dados. Então como seria uma BST persistente?

17 persistent_bst

A medida que são inseridos os nós (ou vértices) na árvore, a referência anterior é mantida. No desenho anterior, é exibido a evolução da árvore a medida que são inseridos os itens, a aresta contínua indica que o elemento é uma cópia (valor imutável) e a aresta pontilhada uma referência.

A seguir o código em F# de um BST persistente:

18 persistent_bst_code

4)      Grafos podem ser representados como árvore. Por exemplo, o grafo conectado em forma de estrela:

19 graph_star

Pode ser expandido em uma árvore e ser percorrido como no clássico problema do caixeiro viajante ou do caminho mais curto (desde que visite todos os nós, problema exemplificado na figura abaixo):

20 brute_force_tsp

E o respectivo código em F#, usando a transformação de grafo para árvore e força bruta para encontrar a menor distância se todos os nós (ou vértices) forem percorridos:

21 graph_to_tree

5)      Sabendo que os sistemas operacionais utilizam estruturas de dados do tipo red black, fui atrás de uma aplicação real. Recentemente, investigando o código fonte do kernel do Linux (não que eu faça isto com frequência) encontrei as seguintes dependências e relações com estrutura de dados rbtree: kernel.h, rbtree_augmented.h, rbtree.h, rbtree.c e rbtree_test.c. Então, achei que seria divertido portá-la para rodar no Visual C++. Em linhas gerais, o código portado mantém todas as caracteristicas dos códigos escritos em C originalmente, porém eu precisei fazer alguns ajustes para tê-los em C++ no Windows.

22 rb_linux_1

A parte chata foi adaptar uma macro do kernel.h chamada container_of, no caso fui obrigado a usar um pequeno truque com lambda para manter o código original inalterado. O código é um pouco grande, então não vou reproduzi-lo aqui totalmente, mas ele está disponível no pacote para download deste post.

O que é interessante saber é o caso de teste:

23 rb_linux_2

E o resultado, onde podemos acompanhar a evolução da entrada de 10 nós (ou vértices) de 1 a 10:

24 rb_linux_3

A implementação encontrada no Linux, não é uma left leaning red black tree, que garante na média uma busca e uma inserção próximos de lg N (logaritmo na base 2 de N elementos). O resultado final é uma árvore não perfeitamente balanceada, mesmo assim obedecendo as caracteristicas de uma BST balanceada:

25 linux_rbtree_result

Faça o download dos códigos fontes e imagens dos exemplos e boa diversão…

Versão PDF deste Post

Posted in C++, Computer Science, F#, Functional Programming | Leave a comment

Retrospectiva 2012

Bem, este ano foi muito “agitado”. Quando no inicio do ano publiquei a mensagem que estaria replanejando meu blog, tinha intenção de escrever com mais frequencia. No entanto, para minha supresa, este ano foi muito “agitado”. (recursividade intencional)

Logo em Janeiro, nós do grupo C/C++ do Brasil juntamente com a Microsoft Brasil, e em especial, colaboração do Programa MVP, organizamos um evento chamado C++ Renaissance, para marcar a importância e a atenção dada pela indústria global com relação ao código nativo, em especial a C++. O evento C++ Renaissance está registrado na página do Programa MVP. Eu fui um dos palestrantes, e falei sobre C++ AMP. Além disso, mais ou menos neste período viajei para Redmond com o objetivo de participar do GoingNative 2012 e fui o revisor técnico do artigo New Standard Concurrency Features in Visual C++ 11, que saiu na MSDN Magazine de Março de 2012 – meu nome é mencionado no final do artigo – maneiro, não é mesmo?

fabiog_cppamp

Até ai estava tranquilo, conciliar o trabalho, meus estudos, minhas aulas (de Scala, C++, C#, …), e os posts para o blog. Porém, sedento por conhecimento e desejando aprimorar meu lado cientista (da Computação), resolvi ingressar num programa de mestrado Stricto Sensu, focando minhas pesquisas essencialmente em Inteligência Artificial, Programação Paralela e Algoritmos. Consumindo mais tempo do que eu imaginava. Assim sendo, fui obrigado a administrar melhor algumas coisas, mas prioridades são prioridades e tenho um planejamento a seguir.

Após conhecer o conceito dos MOOCs, paralelamente as atividades citadas, resolvi encarar mais um desafio – participar de alguns dos cursos online oferecidos pelas grandes universidades. Na minha primeira iteração, conclui o curso Algorithms Part I de Princeton, ao qual obtive no exame final o score 18.25 de 20.00 – para minha felicidade, no artigo indicado do New York Times, existe a seguinte passagem: Most offerings are adapted from existing courses: a Princeton Coursera course is a Princeton course.

algo part 1 - Princeton

Neste tipo de curso, você pode ser apenas um participante sem se preocupar com as atividades e os testes – mas o legal, além do aprendizado, é saber o quanto você está preparado e capacitado para lidar com os desafios propostos – e no final receber a recompensa.

Surpreendentemente, um dos treinamentos anunciados pela Coursera foi o do Martin Odersky, o criador da linguagem Scala, ao qual venho ministrando alguns treinamentos aqui no Brasil:

fabiog_and_scala

Onde consegui um score de 98,9% de aproveitamento completando tarefas de programação como: algoritmo de compactação (Huffman) e inteligência artificial (implementando um solver para o jogo Bloxorz), além das outras, obtendo assim o certificado de conclusão com distinção:

scala statement of accomplishment

Só isso? Parece brincadeira, mas além de todas estas atividades (trabalho, mestrado, cursos no Coursera, etc…), a poucas semanas atrás conclui o curso de Matemática Aplicada (onde a maioria das aulas de 6 horas ocorriam aos sábados e domingos) – um dos cursos mais bonitos que assisti em minha vida (A Matemática é bela por si só, quando ensinada com maestria fica ainda mais bela):

fabiog_matematica_aplicada - 2012

Foi um ano duro, com um nível de sociabilidade e diversão bem restristo, mas com um resultado final muito gratificante e satisfatório – e tem muito mais pela frente nesta jornada. Trabalho duro, foco, dedicação e disciplina foram essenciais para me manter no caminho.

Dos poucos momentos de diversão consegui asssisitr alguns dos principais shows de Rock (Progressivo, Metal e Clássico) que estiveram aqui em São Paulo – Fates Warning, Dream Theater, Marillion, Rick Wakeman, Asia, Steven Wilson Band, Unisonic e a formação clássica do Viper:

shows 2012

Além disso, tive a oportunidade de visitar o The Elvis Experience, e relembrar algumas coisas da juventude:

The Elvis Experience

Alguns livros que iniciei em 2012 e pretendo dar continuidade em 2013:

books 2012-2013

E  o que está no meu iPad:

bookshelf_ipad

O que vem pela frente eu não imagino, mas tenho um planejamento a seguir neste próximo ano!

Posted in Uncategorized | 6 Comments

Faster Than a Bullet

O que pode ser mais rápido do que a bala? This is the GPU…

No GPU in Science, a NVIDIA apresenta o seguinte titulo – Speed of Light: The Role of Visual and High Performance Computing in Scientific Innovation. Logo, a “luz” é mais rápida do que a bala (Speed of Light versus Speed of Bullet). Sarcastic smile

clip_image002 clip_image004

Assim, este cara bonitão, simpático e inteligente da foto abaixo tem “brincado” com GPUs a um certo tempo – inclusive isto rendeu algumas palestras sobre CUDA ou C++ AMP.

clip_image006

Empolgado pelo assunto decidiu escrever uma gentil introdução ao C++ Accelerated Massive Parallelism (C++ AMP) presente no Visual C++ 11 – VCAMP110.DLL:

clip_image008

Introdução ao C++ AMP

clip_image010

C++ Accelerated Massive Parallelism (C++ AMP) é uma tecnologia para usar e abusar dos hardwares que possuem suporte massivo a paralelismo de dados, ou seja, aceleradoras gráficas (GPU) e processadores com suporte a vetorização (por exemplo: AVX e SSE). O que ela tem de especial ou diferente das outras (CUDA, DirectCompute ou OpenCL)? Acho que vários pontos, assim como pode ser lido num dos primeiros anúncios, e também no VCBlog, editado por Diego Dagum, sobre esta tecnologia:

· É puramente C++. Ela se apresenta como uma biblioteca estilo Standard Template Library (STL). CUDA, DirectCompute e OpenCL são orientados a C. CUDA oferece a biblioteca Thrust para preencher esta lacuna;

· Não é necessário interagir com uma linguagem de shader. No OpenCL esta computação é feita usando GLSL, já no DirectCompute é através de HLSL;

· Não é necessário ter um arquivo com extensão diferente e um compiler driver (nvcc) como no caso do CUDA;

· É uma especificação aberta, logo aparecerão compiladores em outras plataformas.

As capacidades e o modelo de programação do C++ AMP permitem a exploração e a inclusão no universo da programação heterogênea. E isto faz todo o sentido, se pegarmos o último lançamento em termos de computadores pessoais, nota-se que eles possuem processadores multicore e aceleradoras gráficas discretas ou integradas. Nas palestras que ministrei sobre programação com computação heterogênea, mostro a seguinte figura, a qual caracteriza este poder computacional:

clip_image012

Para mostrar o C++ AMP em ação e fazer uma comparação com CUDA, eu adaptei o exemplo do fractal Julia set do livro CUDA by Example. É utilizado OpenGL para exibir o fractal.

clip_image014

Aqui temos o trecho que resolve o exemplo através da CPU e da GPU respectivamente. A versão CPU usa o que nós programadores C++ estamos acostumados. Já a versão GPU, apresenta novas abstrações providas pelo C++ AMP, para realização desta computação na aceleradora gráfica.

clip_image016

clip_image018

Estas novas abstrações são a mais pura aplicação dos idiomas que já conhecemos em C++. Por exemplo, array_view, não parece um array ou vector da STL? O parallel_for_each não segue o mesmo estilo da parallel_for_each da PPL e da TBB? Portanto, é bem tranquilo para programador C++ adotar este modelo de programação.

Provavelmente a novidade fica por conta do modificador restrict. Este modificador determina o contexto, ou as restrições que serão impostas pela função a ser executada. Isto faz sentido, pois o corpo desta função contém um código que será transformado num assembly compatível com o dispositivo target, no caso a GPU. Então o restrict impõe as regras, ou indica as instruções, que serão permitidas no corpo da função com o modificador. Por default, qualquer função (ou membro do tipo função) num compilador que suporta C++ AMP é restrict(cpu), ou seja, roda código compatível com a CPU target. O restrict não está limitado a um valor, como pode ser visto no membro do tipo função Generator:

clip_image020

Neste caso, a função suporta apenas compilar o que seria a intersecção entre as funcionalidades da CPU e da GPU. Assim, ela poderá ser chamada a partir de qualquer um dos contextos de execução indicados.

A versão do C++ AMP do exemplo apresentado exibe um desempenho na GPU 40,5% superior à versão CPU – este valor varia de acordo com os dispositivos. Ambos estão usando os flags de otimização do compilador (no caso da CPU o código gerado usa SSE).

Quando um programa na CPU interage com a GPU, há um conceito de host (CPU) e device (GPU), bem como existe uma hierarquia de memória na GPU diferente daquele que estamos acostumados com a CPU:

clip_image022

Basicamente, é necessário transferir a memória do host para o device, disparar o “kernel” (no caso do C++ AMP isto é feito com o parallel_for_each) e após o processamento, o resultado da computação é transferido da memória do device para o host.

clip_image024

Este é um papel desempenhado pelo array_view. Quando usado dentro de um contexto de execução da GPU, ele é responsável por fazer a transferência dos dados de forma transparente. Ao final é recomendado usar o método synchronize para atualização dos dados na CPU.

O array_view não está limitado apenas para a transferência dos dados entre os dispositivos, ele é um recurso que possibilita a visão (reshape) de vários modos para dados de um std::array, ou de um std::vector, ou de uma área densa e contínua, como por exemplo um ponteiro de uma sequência de floats alocada dinamicamente . Por exemplo, é possível enxergar um vector<int> com 10 elementos como uma matriz de 2×5. Na terminologia da STL, isto significa container adapter.

clip_image026

O array_view é muito simples de usar, os argumentos do template indicam o tipo e a dimensão, e o seu construtor aceita a tamanho e objeto a ser adaptado.

clip_image028

O array_view trabalha em conjunto com o extent, cujo representa os limites da visão. O membro operator() do array_view permite o acesso de até 3 dimensões. Além disso, há disponível a classe index que representa as posições de cada coordenada no espaço cartesiano. Os exemplos a seguir sintetizam a usabilidade destas classes:

clip_image030

clip_image032

clip_image034

Note que o extent e o index recebem em seus construtores das dimensões mais significativas a menos significativas. Isto quer dizer, se for apenas uma dimensão, o único parâmetro representa o eixo X. Se forem duas dimensões, o primeiro parâmetro representa o eixo Y e o segundo o eixo X, e assim sucessivamente.

Como visto no método Generator acima, é possível trabalhar com tipos compostos dentro de um domínio restrict. Este é o caso da classe ComplexNumber utilizada no exemplo:

clip_image036

Como descrito anteriormente, o parallel_for_each tem a responsabilidade de disparar uma computação. Normalmente isto é feito com um lambda ou um functor, onde estes elementos devem ser especificados com restrict. O uso da função parallel_for_each é exibido no clássico exemplo de multiplicação de matrizes. Ela possui diversas sobrecargas, no exemplo o primeiro parâmetro representa as dimensões e o tamanho do container. Para este caso, o C++ AMP possui uma heurística para determinar como será o particionamento deste container entre as threads da GPU.

clip_image038

O especificador restrict, parametrizado com amp, indica restrições dentro do contexto da aceleradora, no caso, a GPU. Desta maneira, a função é restrita em termos de capacidade com relação à versão padrão pura – restrict(cpu). Portanto as seguintes ações são desabilitadas quando uma função possuir tal restrição – amp: recursão, variáveis com volatile, funções virtuais, ponteiros para funções, ponteiros para membros do tipo funções, ponteiros em estruturas, ponteiros para ponteiros, goto, labels, try, catch, throw, variáveis globais, variáveis estáticas, dynamic_cast, typeid, bloco asm e varargs. O restrict do C++ AMP não tem relação com o restrict do C99.

No C++ AMP, através da classe tiled_extent, é permitido controlar o agrupamento e a granularidade das threads da aceleradora, no caso, uma GPU. Isto permite melhor explorar o modelo de memória do dispositivo. No exemplo a seguir, uma multiplicação de matrizes, o tile_extent é utilizado via o método tile do membro extend da instância array_view (c_view).

clip_image040

Os índices referentes à técnica de tiling devem ser interpretados conforme a figura abaixo:

tile_static

Imagine que existe uma matriz 8×8, onde se deseja dividi-la em quatro blocos (4×4 threads por bloco), sendo que cada um destes blocos serão tocados por threads distintas e estes blocos possuem uma característica peculiar onde suas threads podem acessar memória compartilhada (recap: GPU Memory Model) . A divisão destes blocos é representada no exemplo por c_view.extend.tile<4,4>(). Dentro do lambda, o acesso à memória é feito através dos valores dos índices do tipo tiled_index, cujos membros global, local, tile e tile_origin representam o índice em relação a matriz total, o índice em relação a submatriz, o índice ao qual o tile o elemento está relacionado e qual a origem deste tile, respectivamente – na figura anterior foram relacionados alguns valores como referencia a cada um destes membros.

No modelo de memória das GPUs existem alguns tipos ou categorias de memória, onde as interessam neste momento são duas: a global e a compartilhada.

A partir da memória global é onde ocorre à transferência de dados do host (CPU) para o device (GPU), esta transferência (ou cópia) acontece de forma transparente quando um array_view é consumido dentro de um lambda com restrict(amp). No entanto, é possível controlar esta cópia manualmente com funções estilo STL (copy ou copy_async). A memória global pode ser acessada de qualquer lugar dentro da GPU – por isto ela tenha o nome memória global, certo? Smile with tongue out

A memória compartilhada é limitada as threads dentro de um bloco. Ela oferece desempenho superior à memória global.

clip_image044 clip_image046
Fonte: “Shared memory is a key enabler for many high-performance CUDA applications” – NVIDIA Fermi Architecture Whitepaper

Para usar memória compartilhada no C++ AMP, uma nova palavra reservada é introduzida na especificação: tile_static (a outra é restrict). No exemplo, tile_static qualifica as matrizes a_shared e b_shared para residirem na memória compartilhada do bloco (cada bloco terá a sua própria cópia). No exemplo, de multiplicação de matrizes a ideia é que cada bloco tenha seu cache, ao qual o programador gerencia manualmente (diferente dos caches da CPU), onde para cada cache do bloco são transferidos os dados necessários.

clip_image048

A figura acima exibe os dados necessários para cada elemento da matriz resultado. No bloco (0,0) de C são necessários os dados dos blocos (0,0) e (0,1) de A e os dados dos blocos (0,0) e (1,0) de B. Logo, a estratégia desta computação é trazer para memória compartilhada os dados que serão usados no bloco resultado, visto que os dados dos blocos de A e B serão acessados mais de uma vez para computar os elementos do bloco C. Note que cada elemento destes blocos representa uma thread, e para calcular um elemento na matriz C é obrigatório todos os dados estejam transferidos para memória compartilhada. Onde há threads pode haver race condition. Neste caso, é necessário a sincronização, no caso do exemplo, isto é feito com o idx.barrier.wait(). O idx representa o tiled_index referente ao bloco corrente, o wait espera todas as threads daquele bloco alcançarem o ponto da barreira antes de liberar para os próximos passos. Em relação ao exemplo, a primeira sincronização aguarda a memória compartilhada ter todos os dados preenchidos – para a multiplicação de matrizes, a_shared precisará de todas as colunas de uma determinada linha e b_shared precisará de todas as linhas de uma determinada coluna.

Se quiser saber mais, Daniel Moth escreveu um ótimo artigo introdutório sobre tiling com C++ AMP.

Quando temos computação envolvendo paralelismo massivo de dados é inerente que teremos cálculos a fazer, e para isto precisamos de um conjunto de funções matemáticas para nos ajudar. O C++ AMP oferece este conjunto de funções em dois sabores (menor precisão, somente float, maior desempenho – fast_math, e maior precisão, float ou double, menor desempenho – precise_math) – elas são acessadas através do header amp_math.h.

clip_image050

As funções que residem nestes namespaces possuem assinaturas compatíveis com as funções encontradas no header cmath. Logo, é possível transferir qualquer tipo de “continha” para a GPU processar – como é o caso das trajetórias de balística exemplificado abaixo:

clip_image052

Note que neste exemplo, ao invés do array_view foi utilizado o array. O array tem o mesmo “look’n’feel” do container array da STL, porém este reside no domínio do C++ AMP. Ele representa uma área previamente alocada na aceleradora, tendo como características fundamentais: suas transferências são explicitas, usando o copy ou copy_async; no lambda só podem ser capturados por referencia.

No C++ AMP existe a capacidade de enumerar as aceleradoras (accelerator) existentes, bem como executar uma computação numa aceleradora especifica – a execução é feita através de um accelerator_view, cujo representa uma abstração ou visão da aceleradora. O C++ AMP considera a aceleradora um hardware capacitado a executar computação de dados em paralelo, isto pode ser um dispositivo conectado a um bus PCI Express, como uma GPU, ou também uma CPU – de preferencia com registradores vetoriais.

clip_image054

clip_image056

clip_image058

O accelerator_view é obtido através de um accelerator, ele é passado para uma das sobrecargas do parallel_for_each, orientando-o onde deve ser executado o código contido no lambda.

clip_image060

Se desejar se aprofundar neste assunto o Native Concurrency MSDN Blog é uma ótima referencia! Assim como os screencasts do Daniel Moth e o futuro livro da Kate Gregory e do Ade Miller. (Se livros em português vendessem bem, eu até empolgaria em escrever sobre este assunto. Mas no Brasil é a maior decepção escrever um bom livro técnico na língua nativa. Sad smile).

Faça o download do código fonte e binário dos exemplos de C++ AMP e rock’n’rollHot smile

Posted in C++, GPU | 1 Comment

λόγος and the Rise of Turtle Graphics

Logos é uma palavra forte de origem grega, ela pode significar razão, palavra ou discurso. Os próprios gregos foram responsáveis pela Trigonometria, cuja etimologia é a composição das palavras trigōnon (triângulo) e metron (medida) – algo como o estudo das medidas do triângulo (3 lados e 3 ângulos). Eles também foram responsáveis pelas inserções da Geometria (γεωμετρία = terra + medida) Euclidiana. Ambas as disciplinas são ramificações da Matemática, sendo que elas, juntamente com Análise Vetorial e Quaternion são os elementos fundamentais da Matemática para Computação Gráfica.

clip_image002

Escola de Atenas e os Elementos de Euclides

Se juntarmos todos estes ingredientes, misturarmos e adicionarmos algumas palavras mágicas (spells) – dentro de contexto da Computação, obviamente. Podemos extrair um produto fundamental composto por um software gráfico e educacional cujo “logo” (de logotipo) é uma tartaruga.

Sim, é sobre isto mesmo que estou falando, da linguagem de programação Logo. Que possui uma fundação, que a promove: Logo Foundation. O mais interessante, é que Logo é influenciada pela linguagem de John McCarthy, e ele dispensa apresentação. Fora isto, existe outra ferramenta educacional criada pela Microsoft, o Small Basic, que promove a linguagem Basic.

O Basic aquele que deixou Gates e Allen famosos. E também foi minha primeira linguagem de programação (que com o passar do tempo houve uma relação entre amor e ódio, hoje existe muito respeito para com ela). O ambiente do Microsoft Small Basic possui um objeto chamado Turtle, que permite você fazer a mesma coisa que um ambiente Logo, só que em Basic – muito recomendado para a garotada, ou para os profissionais que ainda acham que programar para SharePoint (ou qualquer outro produto) é programar. (Nada pessoal, com SharePoint foi apenas uma escolha aleatória baseado em popularidade e falácia, poderia ser BizTalk, sei lá Smile with tongue out).

The rise of Turtle Graphics Xbox

Há duas semanas, no mesmo dia que o Fates Warning tocou no Brasil, tive a oportunidade de ministrar uma palestra sobre Scala. E uma das minhas ideias era criar um exemplo especial fora do contexto do Scala. (O Fates Warning é uma das minhas bandas favoritas desde a adolescência). Flirt male

O exemplo especial foi construir um ambiente de Turtle Graphics para Windows usando C++, Direct2D, Java e Scala. E estendi este exemplo para minha segunda linguagem favorita, o F#. Ou seja, programei o engine e seu modelo de scripting compatíveis com a JVM e com a CLR.

clip_image002[4]

Na verdade, todo este trabalho surgiu pela oportunidade da palestra (Palestra Fundamentos de Scala). No entanto, criar este ambiente era uma idéia antiga, impulsionada pelo livro que está ai em cima: An Integrated Introduction to Computer Graphics and Geometric Modeling de Ronald Goldman. Neste livro, ele começa dizendo algo do tipo: “vamos começar aprender Computação Gráfica e sua Matemática usando o Turtle Graphics”. Assim, ele sugere o uso do Logo ou que você crie seu ambiente. Bem, eu optei pelo segundo, seguindo meu instinto de programador C++. Devil

Após fazer um rápido protótipo com Excel para validar as equações trigonométricas, li rapidamente a documentação do Direct2D para extrair o que eu precisava codificar e sai codificando. Com engine finalizado, parti para a parte do scripting e fiz uma coisa inédita – usei Java Native Interfaces (JNI), para expor o código nativo para Scala – sinceramente, foi bem tranquilo e divertido – e para aqueles que gostariam de saber: Sim, estou programando em Java, afinal (agora) sou um programador sem fronteiras! A mesma coisa eu fiz para o F#, que no caso pode usar P/Invoke diretamente.

Camada de interoperabilidade do scripting com o Java:

import java.nio.ByteBuffer;

final class JNITurtle
{
    private native ByteBuffer create();
    private native void rotate(ByteBuffer hTurtle, float angle);
    private native void resize(ByteBuffer hApp, float size);
    private native void move(ByteBuffer hApp, int distance);
    private native void speed(ByteBuffer hApp, int value);
    private native void destroy(ByteBuffer hApp);

    private boolean disposed;
    private ByteBuffer hTurtle;

    JNITurtle()
    {
        disposed = false;
        hTurtle = create();
    }

    static
    {
        System.loadLibrary("Turtle");
    }

    public void rotate(float angle)
    {
        rotate(hTurtle, angle);
    }

    public void resize(float size)
    {
        resize(hTurtle, size);
    }

    public void move(int distance)
    {
        move(hTurtle, distance);
    }

    public void speed(int value)
    {
        speed(hTurtle, value);
    }

    public void dispose()
    {
        if(!disposed)
        {
           disposed = true;
           destroy(hTurtle);
        }
    }

    protected void finalize()
    {
        dispose();
    }
}

Camada de interoperabilidade do scripting com o F#:

namespace global

open System
open System.Runtime.InteropServices

module private API =
    [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>]
    extern nativeint create()
    [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>]
    extern void rotate(nativeint hApp, float32 angle)
    [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>]
    extern void resize(nativeint hApp, float32 size)
    [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>]
    extern void move(nativeint hApp, int distance)
    [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>]
    extern void speed(nativeint hApp, int distance)
    [<DllImport(@"Turtle.dll", CallingConvention = CallingConvention.Cdecl)>]
    extern void destroy(nativeint hApp)

[<Sealed>]
type private PInvokeTurtle() =
    let mutable disposed = false
    let mutable hTurtle = API.create()

    override this.Finalize() = this.close()

    member this.rotate(angle: float32) = API.rotate(hTurtle, angle)

    member this.resize(size: float32) = API.resize(hTurtle, size)

    member this.move(distance: int) = API.move(hTurtle, distance)

    member this.speed(value: int) = API.speed(hTurtle, value)

    member private this.close() = 
        if not disposed then 
            disposed <- true
            API.destroy(hTurtle)

    interface IDisposable with
        member this.Dispose() = GC.SuppressFinalize(this); this.close()

Estes trechos de código interagem com a fachada que exporta o modelo de scripting: jnimain.cpp e dllmain.cpp do código nativo. Tanto o JNITurtle quanto o PInvokeTurtle implementam suas estratégias para liberação de recursos via dispose pattern.

jnimain.cpp:

#include "scriptable.hpp"

#include <jni.h>

extern "C" 
{
    JNIEXPORT jobject JNICALL Java_JNITurtle_create(JNIEnv* env, jobject)
    {
        return env->NewDirectByteBuffer(scriptable::create(), 0);
    }

    JNIEXPORT void JNICALL Java_JNITurtle_rotate(JNIEnv* env, jobject, jobject hTurtle, jfloat angle)
    {
        auto turtlePtr = reinterpret_cast<scriptable::TurtlePtr>(env->GetDirectBufferAddress(hTurtle));
        scriptable::rotate(turtlePtr, angle);
    }

    JNIEXPORT void JNICALL Java_JNITurtle_resize(JNIEnv* env, jobject, jobject hTurtle, jfloat size)
    {
        auto turtlePtr = reinterpret_cast<scriptable::TurtlePtr>(env->GetDirectBufferAddress(hTurtle));
        scriptable::resize(turtlePtr, size);
    }

    JNIEXPORT void JNICALL Java_JNITurtle_move(JNIEnv* env, jobject, jobject hTurtle, jint distance)
    {
        auto turtlePtr = reinterpret_cast<scriptable::TurtlePtr>(env->GetDirectBufferAddress(hTurtle));
        scriptable::move(turtlePtr, distance);
    }

    JNIEXPORT void JNICALL Java_JNITurtle_speed(JNIEnv* env, jobject, jobject hTurtle, jint speed)
    {
        auto turtlePtr = reinterpret_cast<scriptable::TurtlePtr>(env->GetDirectBufferAddress(hTurtle));
        scriptable::speed(turtlePtr, speed);
    }

    JNIEXPORT void JNICALL Java_JNITurtle_destroy(JNIEnv* env, jobject, jobject hTurtle)
    {
        auto turtlePtr = reinterpret_cast<scriptable::TurtlePtr>(env->GetDirectBufferAddress(hTurtle));
        scriptable::destroy(turtlePtr);
    }
}

dllmain.cpp:

#include "scriptable.hpp"

extern "C"
{
    __declspec(dllexport) scriptable::TurtlePtr create()
    {
        return scriptable::create();
    }

    __declspec(dllexport) void rotate(scriptable::TurtlePtr hTurtle, float angle)
    {
        scriptable::rotate(hTurtle, angle);
    }

    __declspec(dllexport) void resize(scriptable::TurtlePtr hTurtle, float size)
    {
        scriptable::resize(hTurtle, size);
    }

    __declspec(dllexport) void move(scriptable::TurtlePtr hTurtle, int distance)
    {
        scriptable::move(hTurtle, distance);
    }

    __declspec(dllexport) void speed(scriptable::TurtlePtr hTurtle, int value)
    {
        scriptable::speed(hTurtle, value);
    }

    __declspec(dllexport) void destroy(scriptable::TurtlePtr hTurtle)
    {
        scriptable::destroy(hTurtle);
    }
}

Você poderá carregar o Turtle.dll (o engine do Turtle Graphics) nos respectivos ambientes REPL (F# e Scala) usando uma fachada de código gerenciado, no meu caso TurtleFSharp.dll e turtle.jar.

clip_image002[7]

clip_image004

E com isto, produzir uma sequência de riscos variados, ou seja, desenhos:

clip_image006

clip_image008

Aqui um exemplo de script em Scala:

clip_image010

Se tiver curiosidade. A Turtle.dll, o engine do meu Turtle Graphics, é composto das seguintes dependências:

clip_image012

D2D1.DLL é o Direct2D, MSCRV100.DLL é a C Runtime Library (CRT) do Visual C++ 10 e MSVCP100.DLL é a Standard C++ Library (SCL) do Visual C++ 10. O resto já é conhecido do sistema operacional Windows.

Se quiser digerir a parte que interage com o Direct2D, você encontrará no program.cpp definições como:

Construtor e os recursos a serem utilizados (estes caras que precisarão ser liberados pela ação de dispose):

clip_image002[9]

Inicializador da Janela:

clip_image004[6]

O message loop:

clip_image006[5]

Método que desenha o caminho da tartaruga:

clip_image008[5]

O “renderizador”:

clip_image010[6]

clip_image012[5]

Responsável por carregar o “cursor” da tartaruga:

clip_image014

As funções matemáticas de auxilio:

clip_image016

E o componente de script:

clip_image018

Enfim, vai encontrar algumas pérolas. Hot smile

Faça o download do código fonte e binário do Turtle Graphics e assim como eu divirta-se. Na verdade, acho que você vai preferir mesmo o Small Basic ou o Logo, mas isto não é mais problema meu – mas o meu funciona nos REPLs do Scala e do F# (e C#, quando o Roslyn estiver ok). Winking smile

Posted in C++, Computer Graphics, Direct2D, F#, Functional Programming, Math, Scala, Windows SDK | Leave a comment

Polimorfismo, polymorphism, πολυμορφισμού, …

Polimorfismo, já refletiu sobre ele? (no sentido de pensar, não é necessário usar Reflector ou algo parecido). Certamente, a linguagem de programação que você usa, possui uma ou mais formas de manifestação deste recurso.

A definição da palavra polimorfismo conecta com a palavra pleomorfismo, um termo usado na Biologia – isto significa: “a ocorrência de duas ou mais forma no ciclo de vida de um organismo”.

Se assim como eu, você deseja simplificar sua vida, assuma que as palavras poly + morph, ambas de origem grega (polýs + morphos), podem representar muitas + forma. Quando contextualizadas pode significar a capacidade de alguma coisa (função, tipo de dado, instância, …) assumir muitas formas.

A minha idéia não é definir formalmente o que é polimorfismo (mesmo porque este assunto poderia ser polêmico, e eu não estou muito afim de discussão conceitual). Mas, a idéia é mostrar livremente algumas manifestações do “muitas formas” no contexto aplicado a Programação de Software, mesmo se tal recurso não for formalmente categorizado como polimorfismo.

Uma das manifestações do polimorfismo é através de Dynamic Dispatching, onde existe um mecanismo de passagem de mensagem resolvido em tempo de execução. Quando isto ocorre, existe uma espécie de contrato entre o caller e o callee. Neste caso, é esperado um certo número de argumentos e retorno compartilhados por um nome. Alguns exemplos deste tipo de cenário, seria os serviços do Windows Communication Foundation (WCF) ou o modelo de atores do Erlang – em linhas gerais, eles recebem uma requisição (request) através de um nome roteado internamente (endpoint no WCF, identifier num receiver no Erlang) e respondem (response ou reply) ou ignoram tal chamada.

Sobre dynamic dispatching, não posso deixar de citar uma das implementações mais populares atualmente: Objective-C. A passagem de mensagem ocorre através da notação [receiver message], message contempla o método (selector) a ser chamado e seus argumentos (actual parameters). Embora o C exerceu forte influência sobre o Objective-C, foi do Smalltalk que ele baseou suas extensões de Orientação a Objetos e o mecanismo de passagem de mensagem.

clip_image002

No exemplo apresentado, isto está materializado na chamada da função callConvert. Note que, ao contrário do C++, C# ou Java, as instâncias chamadas não possuem vinculos de herança, exceto pelo NSObject que é obrigatório para suportar o message passing (objc_msgSend). Então desde que uma instância suporte tal contrato, ela poderá ser chamada e responderá com sucesso, senão responderá com falha e retornará um valor default (note o comportamento das chamadas no painel inferior direito da figura). Isto não combina com Duck Typing? (por enquanto, vamos deixar o pato de lado…)

#import <Foundation/Foundation.h>
//Sample provided by Fabio Galuppo
const float PI = 3.14159265f;

@interface Radians2Degree : NSObject
{
}

-(float) Convert:(float)value;
@end

@implementation Radians2Degree
-(float) Convert:(float)value
{
    return 180 * (value / PI);
}
@end

@interface Degree2Radians : NSObject
{
}

-(float) Convert:(float)value;
@end

@implementation Degree2Radians
-(float) Convert:(float)value
{
    return PI * (value / 180);
}
@end

float callConvert(id receiver, float value)
{
    return [receiver Convert:value];
}

int main(int argc, const char* argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];    

    Radians2Degree* r2d = [[Radians2Degree alloc] init];
    Degree2Radians* d2r = [[Degree2Radians alloc] init];

    NSLog(@"2PI in degrees == %f\n", [r2d Convert:(2 * PI)]);
    NSLog(@"360 in radians == %f\n", [d2r Convert:(360)]);
    NSLog(@"PI in degrees == %f\n", [r2d Convert:([d2r Convert:(180)])]);
    NSLog(@"180 in radians == %f\n", [d2r Convert:([r2d Convert:(PI)])]);

    NSLog(@"(1/2)PI in degrees == %f\n", callConvert(r2d, PI / 2));
    NSLog(@"90 in radians == %f\n", callConvert(d2r, 90));

    NSLog(@"270 in radians == %f\n", callConvert(nil, 270));

    [pool release];

    return 0;
}

Dado uma classe X e outra Y que não se correspondem diretamente, seria possível encontrar dynamic dispatching em outras linguagens? Certamente! A linguagem C# (na verdade a Common Language Runtime – CLR) suporta Invoke via reflection desde a versão 1.0. Porém, usar reflection para isto é muito chato e irritante. No entanto, para melhor suportar este mecanismo, o C# 3.0 introduziu a palavra chave dynamic. O compilador + CLR fazem “a mágica” acontecer, veja:

using System;

//Sample provided by Fabio Galuppo

class Program
{
    sealed class X
    {
        public String M(String value){ return value.ToUpper(); }
    }

    sealed class Y
    {
        public String M(String value)
        {
            char[] temp = value.ToCharArray();
            Array.Reverse(temp);
            return new String(temp);
        }
    }

    sealed class Z : System.Dynamic.DynamicObject
    {
        Func<String, String> DefaultResult_ = s => String.Empty;

        public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
        {
            result = DefaultResult_;
            return true;
        }
    }

    static String Selector(dynamic id, String value)
    {
        String result = String.Empty;

        try
        {
            result = id.M(value);
        }
        catch(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException){}

        return result;
    }

    static void Main(string[] args)
    {
        Action<dynamic, String> m = (id, value) => Console.WriteLine("{0} -> {1}", value, Selector(id, value));

        m(new X(), "Hello");
        m(new Y(), "World");
        m(new object(), "Something");
        m(new Z(), "Something");

        Console.ReadLine();
    }
}

O método estático Selector invoca dinamicamente um método chamado M que recebe uma String cujo responde uma String (em destaque o contrato da operação). O comportamento é exatamente igual ao mecanismo de passagem de mensagem do Objective-C! Exceto que no Objective-C, o resultado retornado para uma instância cujo não possui um contrato compatível é o valor default da operação. Já no C# ocorre uma exceção pomposa: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException. Se desejar ter o mesmo comportamento do Objective-C (retornar o valor default), é necessário criar um interceptador, como a classe Z do exemplo.

Uma reflexão sobre notação:

Objective-C: [receiver Convert: value To: RADIANS];

C#: receiver.Convert (value, to = RADIANS);

Assim como as derivadas possuem suas diversas notações (da/dx – Leibniz, å – Newton) , o mecanismo de passagem de mensagem também possui tal notação baseado no gosto e expressão de seus criadores (Brad Cox, Anders Hejlsberg).

Continuando, tudo isto soou como Late Binding, não é mesmo? E late binding nos remete aos anais do Component Object Model (COM). O que seria do COM sem ele? (talvez a não existência de programadores Visual Basic, pensando bem…, deixa para lá… Hot smile brincadeira – Visual Basic é uma nobre linguagem de programação que sofre preconceito de programadores elitistas, porém se usada pelas pessoas certas, podem fazer coisas muito legais) .

Com toda esta gloriosa volta do COM (WinRT, o novo COM), seria um bom momento para revisitá-lo. Focando no late binding com Active Template Library (ATL) e C++:

clip_image004

COM IS LOVE by Developmentor in their golden years (Don Box and team)

Eu ainda tenho a camiseta que eles distribuiam nos eventos. Surprised smile

O COM tem seu mecanismo de dynamic dispatching implementado em termos da interface IDispatch. ATL ajuda muito, tanto na construção do componente suportando late binding, bem como no seu consumo. Tudo relacionado aos componentes COM é dependente de interfaces, e estas por sua vez são descritas no código fonte (C++, por exemplo) e na interface definition language (IDL). Abaixo temos os melhores momentos da construção de 2 componentes COM suportando IDispatch (XType e YType):

class ATL_NO_VTABLE CXType :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CXType, &CLSID_XType>,
    public IDispatchImpl<IXType, &IID_IXType, &LIBID_InvokeMeLib, 1, 0>
{
public:
…

BEGIN_COM_MAP(CXType)
    COM_INTERFACE_ENTRY(IXType)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

…

public:
    STDMETHOD(M)(BSTR value, BSTR* result);
};

#include "XType.h"

STDMETHODIMP CXType::M(BSTR value, BSTR* result)
{
    CComBSTR c(value);

    HRESULT hr = c.ToUpper();
    if(SUCCEEDED(hr))
    {
        *result = c;
        return S_OK;
    }

    return hr;
}

class ATL_NO_VTABLE CYType :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CYType, &CLSID_YType>,
    public IDispatchImpl<IYType, &IID_IYType, &LIBID_InvokeMeLib, 1, 0>
{
public:
…

BEGIN_COM_MAP(CYType)
    COM_INTERFACE_ENTRY(IYType)
    COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

…

public:
    STDMETHOD(M)(BSTR value, BSTR* result);
};

#include "YType.h"

STDMETHODIMP CYType::M(BSTR value, BSTR* result)
{
    CComBSTR c(value);

    USES_CONVERSION;

    char* x = OLE2A(c);
    strrev(x);
    *result = CComBSTR(x);

    return S_OK;
}

As classes CXType e CYType do exemplo ATL/COM/C++ imitam as classes X e Y, respectivamente, implementadas no C#. Então, você leitor conhecedor do C# (se não conhece, recomendo fortemente este livro), poderá mapear tais recursos e tirar suas conclusões.

O consumo do componente e a chamada late binding feita através de Invoke (via Selector) são mostradas na figura abaixo:

clip_image006

No exemplo, está em destaque o comportamento de uma chamada com contrato não suportado, onde a resposta (HRESULT) é Unknown name. O CComPtr<IDispatch> é uma especialização (usando o vocabulário do C++, isto quer dizer um template specialization) que ajuda MUITO as chamadas via Invoke (e outros).

#import "..\\..\\InvokeMe\\Debug\\InvokeMe.dll" raw_interfaces_only named_guids no_namespace

#include <atlbase.h>

#include <iostream>

//Sample provided by Fabio Galuppo

HRESULT Selector(CComPtr<IDispatch>& id, const CComBSTR& value, CComBSTR& result)
{
    CComVariant vValue(value), vResult;
    HRESULT hr = id.Invoke1(OLESTR("M"), &vValue, &vResult);
    if(SUCCEEDED(hr)) result = vResult.bstrVal;
    return hr;
}

int main(int argc, char* argv[])
{
    CoInitialize(NULL);
    {
    HRESULT hr = E_FAIL;

    CComPtr<IDispatch> x, y;    

    auto m = [](CComPtr<IDispatch>& id, const CComBSTR& value)
    {
        CComBSTR result;
        USES_CONVERSION;
        HRESULT hr = Selector(id, value, result);
        std::cout << OLE2CA(value) << " -> " << (SUCCEEDED(hr) ? OLE2CA(result) : "") << std::endl;
    };

    hr = x.CoCreateInstance(CLSID_XType, NULL, CLSCTX_ALL);
    if(SUCCEEDED(hr))
        m(x, CComBSTR("Hello"));

    hr = y.CoCreateInstance(CLSID_YType, NULL, CLSCTX_ALL);
    if(SUCCEEDED(hr))
        m(y, CComBSTR("World"));

    CComPtr<IDispatch> z;
    z.CoCreateInstance(OLESTR("Shell.Explorer"), NULL, CLSCTX_ALL);
    if(SUCCEEDED(hr))
        m(z, CComBSTR("Something"));
    }
    CoUninitialize();

    std::cin.get();
}

Compare o método Selector do C# com a função Selector do C++, elas fazem a mesma tarefa: invocam dinamicamente um método (função ou mensagem, dependendo da nomenclatura que deseja usar) chamado “M”.

Interessante isto, não é mesmo? Você notou que para um determinado recurso apareceu diversas notações, nomenclaturas e conceitos? Parece que é para ferrar (para não qualificar com outro adjetivo) com a cabeça do programador.

Dynamic dispatching é um recurso que pode ser muito custoso do ponto de vista de desempenho. Então, antes de usar e abusar, se atente aos requisitos não-funcionais do seu software.

Dentro do universo polimorfismo, existiria alguma coisa nesta linha (de comportamento semelhante) cujo a resolução ocorresse em tempo de compilação e tivesse as garantias da tipagem estática? Com absoluta certeza! Posso garantir que isto existe e formalmente é denominado de Polimorfismo Paramétrico. Isto é refletido diretamente num recurso do C++ chamado templates (e não confunda isto com generics, cujo possuem uma intercecção).

Vamos criar um nome paradoxal para esta brincadeira e chamar tal recurso de static duck typing (olha o pato aparecendo novamente).

Abaixo, uma implementação que remete ao comportamento dos exemplos anteriores com templates e C++:

clip_image008

Internamente, as funções selector e m, serão recriadas pelo compilador, baseado na quantidade de template instantiation.

Ocultamente, o F# também suporta este tal de “static duck typing”. Isto é feito através de inline functions. Estas funções carregam na sua assinatura um contrato formal, por exemplo, (member M : String -> String):

clip_image010

clip_image012

Apesar de ter o mesmo comportamento do C++ template, replicando código com o tipo adequado durante a compilação, você notará 2 coisas que as funções inline fazem:

1. O que um inline supostamente deveria fazer. Substituir a chamada pelas respectivas linhas de código no contexto chamador (linhas L_002f – L_0052).

clip_image014

2. Conserva a estrutura da função inline, mas se chamada dinamicamente gerará uma exceção.

clip_image016

Agora o último ato, o popular, le magnifique, o inconfundível: polimorfismo baseado em hierarquia de classes. Ele mesmo! Aquele que conhecemos através de classes, métodos virtuais e interfaces das linguagens C++, Java, C#, entre outros. Formalmente, este tipo de polimorfismo é denominado de Subtyping Polymorphism.

Aqui vamos explorar apenas 1 modelo: o subtyping polymorphism com dynamic dispatching. Para isto ser possível, em C++, o recurso usado são membros do tipo função virtual. Imagine a seguinte hierarquia e seu consumo:

#include <cstdio>

const float PI = 3.14159265f;

class IConverter
{
public:
    virtual float Convert(float value) = 0;
};

class Radians2Degree : public IConverter
{
public:
    virtual float Convert(float value)
    {
        return 180 * (value / PI);
    }
};

class Degree2Radians : public IConverter
{
public:
    virtual float Convert(float value)
    {
        return PI * (value / 180);
    }
};

int main()
{
    Radians2Degree r2d;
    Degree2Radians d2r;

    IConverter* converter = &r2d;
    std::printf( "2PI in degrees == %f\n", converter->Convert(2 * PI) );

    converter = &d2r;
    std::printf( "360 in radians == %f\n", converter->Convert(360.f) );

    return 0;
}

Você notará que existe uma classe virtual pura (IConverter) servindo de interface base para outras classes. Outros exemplos de tal técnica com interfaces são IUnknown e IInspectable, do COM e da WinRT, respectivamente. Quando existir um membro virtual, existirá a famosa vtable.

A vtable é o mecanismo que possibilita o dynamic dispatching. Sendo que, seu funcionamento é bem simples: imagine um vetor de ponteiros de funções, cujo existe um outro ponteiro (vtable pointer) que referencia esta tabela de funções como membro “privado” ou interno da instância chamadora. Veja este relacionamento na figura abaixo. Toda esta indireção ocorre através do vtable pointer, onde um objeto aciona o ponteiro de função que deseja invocar através de um indice:

clip_image018

No detalhe, em assembly x64, os registradores RCX e RAX mantêem os endereços do objeto (seu this pointer) e do endereço do endereço da função a ser chamada (Convert – 0x000000013F4E68A0). O resultado apresentados estão na forma Little Endian:

clip_image020

Um resumo das principais operações assembly x64 são:

000000013F4E1079  lea         rax,[r2d]
000000013F4E107E  mov         qword ptr [converter],rax

000000013F4E1093  mov         rax,qword ptr [converter]
000000013F4E1098  mov         rax,qword ptr [rax]

000000013F4E109E  mov         rcx,qword ptr [converter]
000000013F4E10A3  call        qword ptr [rax]  <= breakpoint

RAX    = 0x000000013F4E68A0
RCX    = 0x00000000002CFB38
Memory = 0x000000013F4E68A0  14 10 4e 3f 01 00 00 00 … = littleEndianOf(0x000000013f4e1014)

XMM0 = 00000000000000000000000043B40000 =  resultado final

Apenas por curiosidade, vamos registrar o disassembly do método Convert de Radians2Degree.

clip_image022

A primeira coisa a ser verificada é que para o assembly não existe um método. Há apenas um endereço 64-bit, que poderá ser invocado através do opcode call, cujo a stack indica a presença de 2 paramêtros formais: o this pointer e um float denominado value. Os 2 primeiros passos são movimentos dos registradores onde os parametros foram armazenados antes da chamada para áreas de memória referentes a seus argumentos, o resto das operações são pertinentes aos cálculos. O “S” no final das instruções assembly significa Single, ou seja, operações com float (32-bit ou 4 bytes). Por final, a cópia do resultado vai para o registrador SSE XMM0. Ou seja, simples, muito simples, até “um cavalo entende”. Winking smile

Dentro do universo amplo do polimorfismo, gostaria de destacar mais 2 modelos:

1. Ad-hoc polymorphism – um nome bonito para sobrecarga de métodos, funções ou operadores.

2. Co-variância e Contra-variância – Conversão do mais específico para o mais genérico e conversão do mais genérico para o mais especifico, respectivamente.

Mas estes são assuntos para uma outra oportunidade, ou para alguma palestra, ou treinamento que eu venha a ser contratado para ministrar.

Samples Polymorphism Source Code

Posted in C++, Computer Science, F# | 1 Comment

Recursão, recursion, ricorsione, αναδρομή, …

Dentro da Teoria da Computação, um programa pode ser categorizado de acordo com 3 tipos de estruturação: monolítica, iterativa e recursiva.

Fazendo uma analogia com alguma linguagem de programação posso associar a estruturação monolítica ao BASIC que programava quando garoto caracterizado pelo seus desvios incondicionais (vulgo GOTOs e GOSUBs – eu ainda lembro disto, uau!) e estruturação iterativa – populares em linguagens estruturadas, como por exemplo Pascal, e muito forte por banir os desvios incondicionais por serem considerados grosseiros, rídiculos, perigosos, vai dar m… No entanto, isto é teoria, pois na prática todas as (ou a maioria das) linguagens de programação(bem, pelo menos as que eu escolhi para programar) suportam estruturação recursiva, ou seja, suportam recursividade. Alias, isto é um négocio tão simples que “para você entender de recursividade, basta você entender de recursividade”, sacou? Não? Então novamente: “para você entender de recursividade, basta você entender de recursividade” – agora sim!

Baseado num famoso trecho de Orwell, posso dizer: “… que algumas linguagens são mais recursivas do que outras”.

A maioria dos programadores que conheço sabem ou já ouviram falar sobre recursividade. Mas se você perguntar o que é, provavelmente a resposta seria: “Ah, é uma função que chama a si própria”. Ok! Este é um tipo: a função recursiva. O outro é a estrutura de dados recursiva: o nó de uma lista ligada (Linked List) ou de uma árvore (Tree), a enumeração de dirétorios e arquivos ou de janelas.

Recursividade é tão importante que é um dos pilares das Linguagens Funcionais e um recurso fundamental para a Matemática. Por exemplo, o algoritmo babilônico para a computação do lado do quadrado (square root) é definido recursivamente por:

image

O “sr” na função representa um chute (guess), você calculará inúmeras (“n”) vezes até chegar num resultado coerente dentro de “k” casas decimais para o valor de “x” – você perceberá que esta coerência surgirá quando uma execução sucessiva produzirá resultados semelhantes ou muito, mas muito, próximos. Let’s try:

image

Note que no Mathematica, o algoritmo está um pouco modificado em relação ao apresentado.

Interessante isto, não é mesmo? Tão interessante quanto, é saber que é possível representar estruturas de controle, tais como for ou while, em termos recursivos.

image

No exemplo, escrito em Scala, a função recursiva While é uma função tail recursive. Resumidamente, isto significa que o compilador ou máquina virtual (ou ambos) conseguem otimizar a chamada e não sobrecarregar a call stack.

Imagine por um momento, que surgisse uma vontade incontrolável de implementar um código para gerar séries harmonicas em C++. E falaram para você que este tal de C++ suporta Orientação a Objetos. Juntamente com esta vontade te consumindo, você também ouviu falar num cara chamado Template Method Pattern. Decidido a misturar tudo isto e ver o que pode dar, escreveu o seguinte código, que por sinal usa chamadas recursivas e é compilado com a máaaaaaxima otimização possível:

image

Apesar de eu não ter te explicado, você percebeu que o método SummationCore da classe Harmonic é implementado totalmente segundo os principios de comandam uma boa função tail recursive. O problema é que você caiu numa armadilha do C++.

O fato é: o método é virtual, ele necessita ser resolvido em tempo de execução, então o C++ (pelo menos o cl.exe [Microsoft (R) C/C++ Optimizing Compiler Version 16.00.40219.01], digo o compilador do Visual C++ 10) não pode otimizar agressivamente este código. Note a quantidade de chamadas na Call Stack!

Para concertar este problema, sem alterar a estrutura do código ou usar templates, como regra básica assuma: toda vez que um método virtual for resolvido como tail recursive, implemente-o em termos de outro método não virtual. Assim, o compilador poderá produzir um resultado otimizado! (E este resultado é exatamente resolvido através de desvios – JMPs ou GOTOs, mas esta é uma outra estória)

image

A versão tail recursive em C++, na integra:

#include <cstdio>

struct Serie
{
    virtual ~Serie(){}

    float Summation(int n)
    {
        return SummationCore(n);
    }

protected:
    virtual float SummationCore(int n) = 0;
};

struct Harmonic : public Serie
{
    Harmonic() : Acc_(0){}

protected:
    virtual float SummationCore(int n)
    {
        return TailRecursiveSummation(n);
    }

    float TailRecursiveSummation(int n)
    {
        if(n == 1)
            return Acc_;

        Acc_ += 1.0 / n;

        return TailRecursiveSummation(n - 1);
    }

private:
    float Acc_;
};

int main()
{
    std::printf( "%f", Harmonic().Summation(7) );
}

Não esqueça de olhar a Call Stack (impressionante, não acha?)

Posted in C++, Functional Programming, Math | 4 Comments