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;
}
}
}
-> 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();
}
}
}
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:
quando q vc vai colocar um exemplo usando o linq?
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
...
Postar um comentário