7 de out. de 2009

Paralelismo no C# 4 - I

intel-multicore-1

Faz um tempo que fiquei sabendo das novidades do C# 4, e o paralelismo sempre me assustou (trauma dos trabalhos de faculdade).

Usar mais de um processador ou núcleo nunca foi uma tarefa muito fácil, e muitas vezes era tão complexo que recorríamos a alguns "workarounds", o que tornava o processamento paralelo ineficaz, devido aos algoritmos de divisões e sincronias.

Fantástico foi o jeito que o pessoal do C# e .NET facilitou e colocou desempenho nas bibliotecas de paralelismo da nova versão. E olha que ainda estamos em beta 1.

Após dar uma olhada superficial, comecei alguns testes, eis alguns códigos e resultados, acompanhem a facilidade e eficiência:

 

-> Calculando o fatorial de um número aleatório dez mil vezes:

using System;
using System.Diagnostics;
using System.Threading;

namespace Paralelismo
{
    class Program
    {
        static void Main(string[] args)
        {
            // -> pausa para garantir que o programa se estabilizou.
            Thread.Sleep(1000);

            // -> seleciona um número aleatório maior que 1000
            //    e menor que 10000 para calcular seu fatorial.
            var n = new Random().Next(1000, 10000);

            Console.WriteLine("Iniciando teste...");

            var cronometro = new Stopwatch();

            // -> laço simples sem paralelimso.
            cronometro.Start();
            for (int i = 0; i < 10000; i++)
            {
                CalcularFatorial(n);
            }
            cronometro.Stop();
            Console.WriteLine(string.Concat("Resultado sem paralelismo: ", cronometro.ElapsedMilliseconds));

            // -> re-estabilizando.
            Thread.Sleep(1000);

            // -> reseta o cronometro.
            cronometro.Reset();

            // -> laço com paralelismo
            cronometro.Start();
            Parallel.For(0, 10000, i =>
            {
                CalcularFatorial(n);
            });
            cronometro.Stop();
            Console.WriteLine(string.Concat("Resultado com paralelismo: ", cronometro.ElapsedMilliseconds));

            Console.Read();
        }

        // -> método recursivo para calculo do fatorial.
        static int CalcularFatorial(int n)
        {
            return (n >= 1) ? (n * CalcularFatorial(n - 1)) : 1;
        }
    }
}

image

image

 

-> Laço, sem instrução, de um milhão de vezes:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;

namespace Paralelismo
{
    class Program
    {
        static void Main(string[] args)
        {
            // -> pausa para garantir que o programa se estabilizou.
            Thread.Sleep(1000);
            Console.WriteLine("Iniciando teste...");

            var cronometro = new Stopwatch();

            // -> laço simples sem paralelimso.
            cronometro.Start();
            for (int i = 0; i < 1000000; i++)
            {

            }
            cronometro.Stop();
            Console.WriteLine(string.Concat("Resultado sem paralelismo: ", cronometro.ElapsedMilliseconds));

            // -> re-estabilizando.
            Thread.Sleep(1000);

            // -> reseta o cronometro.
            cronometro.Reset();

            // -> laço com paralelismo
            cronometro.Start();
            Parallel.For(0, 1000000, i =>
            {

            });
            cronometro.Stop();
            Console.WriteLine(string.Concat("Resultado com paralelismo: ", cronometro.ElapsedMilliseconds));

            Console.Read();
        }
    }
}

image

 

Como podemos ver, quando temos uma operação matemática mais complexa, recursividade ou até mesmo tomadas de decisões (if-else) temos um tremendo ganho de performance com o paralelismo… E de forma muito simples, fácil e objetiva.

Porém em um laço simples, sem instruções, o for paralelo foi ligeiramente mais lento. Bem, operações simples não compensam o custo de dividir a operação entre vários núcleos.

Daí é aquela velha história: Um pintor demora bem mais tempo para pintar uma parede com 300m² do que dois pintores. Mas para pintar 5m² o segundo pintor vai mais atrapalhar do que ajudar :)

Mesmo assim compensa e muito o processamento paralelo. Em breve vou mostrá-lo usando o LINQ… é impressionante!!!

Hoje as máquinas são multi-core, e a tendência é aumentar cada vez mais os núcleos. Nossas aplicações tem que acompanhar isso!

… Ainda mais que quase nenhuma aplicação consegue tirar proveito disto, talvez alguns jogos ou aplicações específicas. (e olha que agora podemos fazer isto nas nossas aplicações web com ASP.NET)

E nós temos o poder de fazer isso. Além do que é automático, ou seja, a aplicação vai funcionar normalmente se a máquina só tiver um núcleo (isso mesmo, NÃO vai dar erro!!!) e o .NET framework também se encarrega de entregar o processamento para quantos núcleos forem necessários/estiverem disponíveis. (vai tentar fazer isso com Delphi vai… em x64 ainda por cima :P)

Para que re-inventar a roda!? Isto é perda de tempo. Termine seu projeto de forma habilidosa, fácil, com qualidade e auto desempenho, ganhe dinheiro e seja feliz :D

obs: você pode ser um dos primeiros a fazer isto! lembrando que isto é no Visual Studio 2010 e C# 4.

2 comentários:

Anônimo disse...

quando q vc vai colocar um exemplo usando o linq?

Unknown disse...

Olá,

Implementei como teste em um projeto que estou desenvolvendo o "Parallel.For" em um Windows Services.

Estou com alguns problemas pois o serviço varre uma tabela do banco SQL e retorna aproximadamento uns 2 milhoes de registros, depois ele executa algumas ações e por fim ele grava o resultado de cadas operação em um arquivo texto.

Olhando o arquivo texto eu percebi que ele funciona bem mas alguns registros ficam comprometidos pois algumas Threads tentam escrever no arquivo ao mesmo tempo o valor do registro fica errado.

Como posso fazer para permitir que um resultado seja escrito no banco somente depois de uma Thread possa ter finalizado a escrita?

Vale a observação que estou utilizando um Servidor com 16 núcleos de processamento.

Parte do código:

//Cria arquivo de log
FileStream fs = new FileStream("C:\\Logs\\001_log.txt", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
StreamWriter fp = new StreamWriter(fs);

Parallel.For(0, table.Rows.Count, (k) =>
{
try
{
Int32 iReturn = ProcRegister((Int32)table.Rows[0][0]);
fp.WriteLine(iReturn);
fp.Flush();
}
catch{}
finally{}
});

fp.Close();
fp.Dispose();
fs.Dispose();
fs.Close();

Parte do arquivo de log com problema

...
985217
991848
985680
988684
989989
983342
7367 983342
7367 983342
7367
984540
986244
986016
984964
991237
...