Saturday, June 3, 2006

Concorrência

No curso de BizTalk 2006, um dos módulos que dei foi sobre Tunning e Boas Práticas. Um dos temas abordados foi a questão do tempo de latência, o tempo de resposta a pedidos. Não terá sido intuitivo para todos que para diminuir a latência se deve parametrizar o sistema para funcionar quase como se fosse single-threaded (e, por exemplo, sem fazer processamentos em batch).


Recentemente tivemos um pedido para procurar aumentar o desempenho de uma aplicação pela implementação de mecanismos de paralelismo. A aplicação não é sobre BizTalk, mas em .Net. Esta aplicação faz um processamento sequencial de pedidos recebidos, e um dos passos desse processamento, um pedido a outro sistema, é bastante demorado, sendo este o candidato para a utilização de multi-threading.


Inicialmente, e sem grande experiência em cenários de concorrência, pensei um cenário do tipo ThreadPool, em que 10 threads competiam pelo processamento de um pedido (Competing Consumers), e o despachavam em paralelo para o tal outro sistema. Uma implementação de teste muito simples funcionou perfeitamente, com o benefício adicional de um mecanismo de prioridades, mas que não testei contra o cenário inicial.


Depois, esta semana, tive a sorte de ouvir o ARCast do Ron Jacobs com o Jeffrey Richter, sobre Threading e I/O Assíncrono, e voltei à prancheta para reanalisar o problema. Olhando para trás, percebi que tinha feito com esta aplicação o inverso do que recomendara no caso do BizTalk.


Implementei então o seguinte cenário de testes:


- Uma classe "worker", que faz cálculos matemáticos intensivos, com cada execução a demorar cerca de 3,5 segundos. Só faz I/O para o ecrã, a indicar quando começou e quando terminou a execução, em milisegundos;
- Uma classe Sequence, que invoca 10x a worker;
- Uma classe Threaded, em que crio 10 threads, e as coloco a executar a worker em paralelo. O tempo de criação da thread não é contabilizado;


Os resultados foram os seguintes:


- A versão threaded, no total, demorou menos ~0,5 segundos a executar (apesar do Task Switching -- não consigo explicar este resultado)
- O tempo por execução do worker, na versão Sequence, é obviamente 3,5 segundos;
- O tempo por execução do worker, na versão threaded, disparou para os 22 segundos.


Apesar de estes resultados não serem no cenário real, e terem sido obtidos numa situação só com um CPU (single core), sem acessos a disco/rede/BD, dá para perceber que se as coisas se mantiverem, com o multithreading conseguiria precisamente o inverso do objectivo pretendido: um aumento do tempo de latência. A versão threaded, quanto muito, seria útil em cenários batch.


Resta fazer testes com os sistemas reais, mas se se mantiverem os resultados, ainda há a hipótese do I/O Assíncrono, como se faz com Web Services (BeginInvoke) ou com Sql Server/Ado.Net 2.0.


Por curiosidade, o Jeffrey Richter tem uma coluna ocasional na MSDN Magazine chamada Concurrent Affairs.


[Cross-Posted de http://www.arquitecturadesoftware.org/blogs/joaomartins]

No comments:

Post a Comment