sexta-feira, 25 de julho de 2014

Exemplo: Paralelismo em C# e VB.NET

Objetivo
Mostrar a sintaxe correta de loops e execução de métodos, em paralelismo, nas linguagens C# e VB.NET.
Consideramos que para os exemplos a seguir, seja usado o import/using do namespace System.Threading.Tasks.


Nota: se você não sabe o que é paralelismo, não deixe de visitar os outros artigos:


FOR - C#
            //Loop for tradicional
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine(i.ToString());
            }

            //Loop for paralelo
            Parallel.For(0, 10, i => 
            {
                Console.WriteLine(i.ToString());
            });

FOR - VB.NET
        'Loop for tradicional
        For i As Integer = 0 To 10 - 1
            Console.WriteLine(i.ToString())
        Next

        'Loop for paralelo
        Parallel.For(0, 10, Sub(i)
                                       Console.WriteLine(i.ToString())
                                 End Sub)

LISTA DE NÚMEROS
Para os códigos abaixo, consideramos a criação de uma lista de números inteiros, conforme código abaixo:
C#
            List<int> numbers = new List<int>();
            numbers.Add(100);
            numbers.Add(200);
            numbers.Add(300);
            numbers.Add(400);
            numbers.Add(500);

VB.NET
        Dim numbers As New List(Of Integer)
        numbers.Add(100)
        numbers.Add(200)
        numbers.Add(300)
        numbers.Add(400)
        numbers.Add(500)

FOR EACH - C#
            //Foreach tradicional
            foreach (int n in numbers)
            {
                Console.WriteLine(n.ToString());
            }

            //Foreach Paralelo
            Parallel.ForEach(numbers, n =>
            {
                Console.WriteLine(n.ToString());
            });

FOR EACH - VB.NET
        'Foreach tradicional
        For Each n As Integer In numbers
            Console.WriteLine(n.ToString())
        Next

        'Foreach Paralelo
        Parallel.ForEach(numbers, Sub(n)
                                      Console.WriteLine(n.ToString())
                                  End Sub)

LOOP EM LISTA - C#
            //Loop em lista
            numbers.ForEach(n => Console.WriteLine(n.ToString()));

            //Loop paralelo em lista
            numbers.AsParallel().ForAll(n => Console.WriteLine(n.ToString()));

LOOP EM LISTA - VB.NET
        'Loop em lista
        numbers.ForEach(Sub(n)
                            Console.WriteLine(n.ToString())
                        End Sub)

        'Loop paralelo em lista
        numbers.AsParallel().ForAll(Sub(n)
                                        Console.WriteLine(n.ToString())
                                    End Sub)

EXECUÇÃO DE MÉTODOS
Para os códigos a seguir, considere a criação do seguinte método:
C#
        private static void WriteNumber(int number)
        {
            Console.WriteLine(number.ToString());
        }

VB.NET
    Private Sub WriteNumber(number As Integer)
        Console.WriteLine(number.ToString())
    End Sub

MÉTODOS - C#
            //Método tradicional
            WriteNumber(888);

            //Método paralelo
            Parallel.Invoke(() => WriteNumber(999));

MÉTODOS - VB.NET
        'Método tradicional
        WriteNumber(888)

        'Método paralelo
        Parallel.Invoke(Sub()
                            WriteNumber(999)
                        End Sub)

Conclusão
Apesar de existir algumas diferenças de sintaxe entre as linguagens VB.NET e C#, as duas linguagens possuem suporte para o paralelismo, a partir do framework 4.0.

domingo, 20 de julho de 2014

Tutorial: Limitar uso de CPU do paralelismo

Introdução
Paralelismo utiliza os núcleos livres de uma CPU para executar rotinas com maior desempenho. Isso pode ser um problema quando o computador que executa o código é compartilhado e existem outros processos e sistemas rodando que não podem parar.

Nota: para mais informações sobre como o paralelismo funciona, visite o outro artigo do blog: Análise: Programação Paralela

Solução
A solução para este problema não é deixar de criar rotinas paralelas, mas sim limitá-las para não usarem 100% de CPU e permitir que o código seja executado com maior desempenho, mas sempre mantendo o processador com uma folga para a execução de outros processos e sistemas.
Para isso, faremos o uso do ParallelOptions.MaxDegreeOfParallelism.

Exemplo
Utilizaremos um simples loop for em paralelo de 1 até 1.000.000 para analisar o comportamento da CPU.
Na chamada do método Parallel.For, podemos passar por parâmetro um objeto do tipo ParallelOptions, onde defimimos o valor da propriedade MaxDegreeOfParallelism para a quantidade de núcleos da CPU que serão usados no loop.

        static void Main(string[] args)
        {
            Console.Title = "Parallel";

            ParallelOptions options = new ParallelOptions();
            options.MaxDegreeOfParallelism = 3;

            Parallel.For(0, 1000000, options, i =>
            {
                Console.Write(i.ToString() + "; ");
            });

            Console.ReadLine();

        }

Definindo o parâmetro MaxDegreeOfParallelism como 3, significa que, serão feitas até 3 atividades em paralelo simultâneamente.
Em outras palavras, em um computador com 4 núcleos, só serão usados 3 núcleos e o uso de CPU ficaria por volta de 75%.
Segue abaixo algumas imagens que representam o uso de CPU, conforme o valor da propriedade MaxDegreeOfParallelism.

MaxDegreeOfParalelism = 1

MaxDegreeOfParalelism = 2

MaxDegreeOfParalelism = 3

MaxDegreeOfParalelism = 10

Notas finais
Se o valor da propriedade MaxDegreeOfParallelism for um número maior que a quantidade de núcleos da CPU, o processo executará normalmente, usando todos os núcleos disponíveis da CPU. Se o valor da propriedade for -1, serão usados todos os núcleos, se o valor for zero ou um número menor que -1, será lançada uma exception.

Conclusão
Utilizar paralelismo para executar rotinas com maior desempenho realmente pode ser muito viável, mas adicionar limite na execução de um código em paralelo também é um assunto que precisa ser discutido para manter o sistema executando sempre com folga, sem ficar com picos de 100% de uso de CPU, o que pode afetar outros sistemas e processos que estejam em execução no mesmo computador.

Análise: Programação Paralela

Objetivo
Criar rotinas que executem métodos em paralelo e analisar as diferenças entre a execução sem paralelismo dos mesmos métodos.
Nota: para saber mais sobre o conceito de paralelismo, visite o outro artigo do blog: Conceito: Programacao Paralela

Exemplos
Para os códigos abaixo, considere que foi criado um novo projeto do tipo Console Application, com o import/using do namespace System.Threading.Tasks.

Loop For
http://msdn.microsoft.com/pt-br/library/dd783539(v=vs.110).aspx


Utilizaremos o loop for para analisar a ordem das iterações e o consumo de CPU em cada situação.

        static void Main(string[] args)
        {
            Console.Title = "Parallel";

            Console.WriteLine("Loop For Comum");
            for (int i = 0; i < 100; i++)
            {
                Console.Write(i.ToString() + "; ");
            }

            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("Loop For Paralelo");
            Parallel.For(0, 100, i =>
            {
                Console.Write(i.ToString() + "; ");
            });

            Console.ReadLine();
        }


Os dois loops vão de 0 até 99, onde o primeiro é sequencial e o segundo é em paralelo, que não garante a ordem das iterações do loop.



Abaixo temos o output do código executado, mostrando que as iterações em loop comum são ordenadas e em paralelo podem ser desordenadas.













Abaixo temos a comparação de uso de CPU entre um loop for comum e um loop for em paralelo, podemos ver que o comum usa apenas um núcleo da CPU e o paralelo usa todos que estão disponíveis.
For Comum
For Paralelo












Parallel.Invoke

Utiliza-se o método Parallel.Invoke para executar um conjunto de métodos de forma paralela. Este método é bloqueante e aguarda todos os métodos serem executados para avançar para a próxima linha de código. Neste exemplo, vamos analisar e comparar o tempo de execução de um código.


        static void Main(string[] args)
        {
            Console.Title = "Parallel";

            DateTime dataInicio, dataFim;
            DateTime dataInicioParallel, dataFimParallel;

            dataInicio = DateTime.Now;
            Esperar(1000);
            Esperar(2000);
            Esperar(3000);
            dataFim = DateTime.Now;

            Console.WriteLine();

            dataInicioParallel = DateTime.Now;
            List<Action> listaAcoes = new List<Action>();
            listaAcoes.Add(() => Esperar(1000));
            listaAcoes.Add(() => Esperar(2000));
            listaAcoes.Add(() => Esperar(3000));
            Parallel.Invoke(listaAcoes.ToArray());
            dataFimParallel = DateTime.Now;

            Console.WriteLine();
            Console.WriteLine();

            Console.WriteLine("Duração comum: " + (dataFim - dataInicio).TotalMilliseconds.ToString("F0") + " ms");
            Console.WriteLine("Duração paralelismo: " + (dataFimParallel - dataInicioParallel).TotalMilliseconds.ToString("F0") + " ms");

            Console.ReadLine();
        }

        static void Esperar(int milissegundos)
        {
            Console.WriteLine("Esperando: " + milissegundos.ToString() + " ms");
            System.Threading.Thread.Sleep(milissegundos);
            Console.WriteLine("Ok! - " + milissegundos.ToString() + " ms");
        }

Output da execução do código:











O método Esperar() foi chamado três vezes, passando por parâmetro 1000, 2000 e 3000 em cada chamada, o que significa que ele iria esperar 1 segundo, depois 2 e depois 3.
Chamando o método as três vezes de maneira tradicional, temos que esperar 6 segundos para a execução total. Chamando paralelamente as três execuções do método, podemos ver que o tempo de execução caiu para 3 segundos; isso se dá pelo motivo que as três chamadas foram iniciadas ao mesmo tempo, ou seja, a contagem de tempo de cada execução do método foi iniciada no mesmo momento, não tendo que esperar o primeiro método acabar para só assim executar o próximo.

Conclusão
Não existem apenas essas duas possibilidades de se usar código em paralelo, esses casos foram citados apenas para servir de análise e comparação de um código comum e um código paralelo.
Não há como dizer se é melhor usar paralelismo ou se é melhor usar a maneira tradicional, pois é a necessidade que faz o uso de um código ou de outro se tornar viável para a aplicação.

Para saber como limitar o uso de CPU de uma rotina paralela, dê uma olhada no próximo artigo: Tutorial: Limitar uso de CPU do paralelismo

quinta-feira, 17 de julho de 2014

Conceito: Programação Paralela

Resumo
Programação paralela (também paralelismo ou computação paralela), é quando uma tarefa é executada em mais de um núcleo do processador, para aumentar o desempenho e diminuir o tempo de execução.

Introdução
Os computadores pessoais atualmente possuem geralmente processadores com dois ou quatro núcleos de CPUs, isso faz com que o processador possa executar mais processos "ao mesmo tempo", levando o fato de que em vez de executar comandos em sequência, podemos executar vários comandos em CPUs diferentes, de forma "simultânea".

Os termos "ao mesmo tempo" e "simultânea" estão entre aspas porque para a programação ser executada ao mesmo, por teoria, é necessário dois ou mais processadores, mas o que ocorre com programação paralela é que cada núcleo executa uma tarefa, o que dá a impressão de as tarefas estarem sendo executadas simultaneamente no mesmo processador.

Até então, para programar uma rotina que executasse dois ou mais códigos em paralelo, era necessário programação em baixo nível, mas com o Microsoft Framework 4 em diante, programação paralela se tornou algo simplificado ao desenvolvedor.

Vantagens:
  • Pode ser usado para aumento de desempenho em rotinas importantes.
  • Divisão de trabalho: dois núcleos trabalhando são mais rápidos do que um só.
  • O desempenho é diretamente proporcional ao número de processadores e núcleos do computador.
Desvantagens:
  • Em geral, é difícil programar e debugar o código.
  • Se usado desnecessariamente, pode causar uso de excessivo de CPU, podendo até travar o sistema ou o computador.
  • Não haverá diferença no desempenho no caso de processadores com apenas um núcleo.
  • A execução não é ordenada, ou seja, os métodos chamados em paralelo poderão ser executados em qualquer ordem.

Conclusão
Os benefícios da programação paralela realmente valem a pena para rotinas de grande importância e que precisam ser executadas no menor tempo possível. Porém sempre deve-se ter cautela em usar esse tipo de programação para não comprometer a integridade e estabilidade do sistema ou computador.


Mais informações em:
http://msdn.microsoft.com/pt-br/library/vstudio/ms171868(v=vs.100).aspx#parallel_computing
http://msdn.microsoft.com/pt-br/library/vstudio/dd460693(v=vs.100).aspx

Para ver uma análise feita sobre programação paralela e programação comum, não deixe de visitar o outro artigo do blog: Análise: Programação Paralela