Pular para o conteúdo principal

Utilizando os múltiplos núcleos do computador - Usando Parallel.For

Estes dias estive remodelando algumas aplicações que já tinha feito, Especialmente meu plug-in para exportação de pontos do AutoCAD para o Google Earth e PLS-CADD.

O problema que vinha sendo enfrentado era quando a quantidade de pontos era muito elevada o programa acabava por demorar muito tempo para realizar a coleta de pontos. Isso isso acontece porque desde o inicio dos computadores todos os códigos e loop's foram estruturados para executar ações sequencialmente. Se você observar quando o computador executa cálculos complexos seja no AutoCAD ou qualquer outro programa como PLS-CADD, Excel, etc... apenas um núcleo é utilizado para processamento, ou seja, se o seu computador é Dual Core 50% da capacidade em utilização, Quad-core 25% e assim por diante... O fato é que temos uma geração de aplicativos que está adaptada para uma geração atrás de Computadores e é natural que certas atividades as vezes acabam por demandar muito mais tempo e subaproveitar o computador.


Eu realmente nunca tinha me utilizado da programação paralela mas dada a demanda resolvi me aventurar por esse novo mundo.

Meu objetivos era apenas acelerar o processo de importação dos pontos, por dois motivos:
  1. Precisava criar pontos no meio das retas - quando queria representar estradas e afins, como o programa original só pegava os vértices, os pontos no meio das retas não ficavam representados corretamente, assim, resolvi que teria que criar esses pontos de alguma maneira, o objeto Line já tinha um recuro para pegar pontos ao longo das retas mas erá necessário fazer isso muitas vezes, uma reta de 1200 queria que fosse dividida em 2400 partes!
  2. O programa já apresentava uma demora grande na importação dos pontos naturalmente que somado ao recurso acima ia se tornar impraticável criar o arquivo PLS-CADD a partir de um desenho de AutoCAD.
A solução foi utilizar o recurso de processamento paralelo oferecido pelo Visual Studio já implementado no .NET Framework. 

Essa foi uma grande vantagem de ter migrado do VBA para .NET, um recurso que só está disponível porque os desenvolvedores de .NET Framework fizeram para outras aplicações.

A minha principal fonte de informação foi o site da MSDN que apresenta bem simples como implenetar os loop's de programação paralela, veja aqui o Parallel.For, a Simple Parallel For Loop.

Quando você escreve um loop para execuçaõ paralela tem que ter em mente duas coisas:
  1. A sequência de montagem, se você está montando um vetor, não existe mais. Isso acontece porque a função será executada ao mesmo tempo por vários núcleos e o .NET Framework vai fazer a divisão to tamanho total da amostra pelos núcleos e em função do tempo de processamento de cada um a saída vai ser gerada.
  2. Dentro dos Loop's paralelos não pode ter acesso a entidades de acesso exclusivo, como StreamWrite, ou GetObject() porque dois processadores podem acessar o mesmo objeto ao mesmo tempo causando um erro.
Atentando para esses detalhes consegui montar um código que escrevia bem rápido a nuvem de pontos. Dentro do Loop da Transaction fiz a entrada no objeto é com o método GetPointAtDist() consegui gerar a minha nuvem de pontos bem rápido.

  foreach (SelectedObject acSSObj in acSSet)  
             {  
               String txt = "";  
               if (acSSObj != null)  
               {  
                 Entity acEnt = null;  
                 try  
                 {  
                   acEnt = (Entity)acTrans.GetObject(acSSObj.ObjectId, OpenMode.ForRead);  
                 }  
                 catch  
                 {  
                 }  
                 if (acEnt != null)  
                 {  
                   // If a "lightweight" (or optimized) polyline  
                   Polyline lwp = acEnt as Polyline;  
                   if (lwp != null)  
                   {  
                     if (!notshowdialog)  
                     {  
                       //Formulário para confirmação da elevação da linha  
                       frmte.txtbox_x.Text = lwp.NumberOfVertices.ToString() + "pontos, total de " + lwp.Length + "m";  
                       frmte.txtbox_y.Text = "ignore";  
                       frmte.txtbox_z.Text = lwp.Elevation.ToString("#################0.0###############", new CultureInfo("en-US"));  
                       frmte.cmbbox_fea.Items.AddRange(FeatureCodeList.GetListString().ToArray());  
                       if (frmte.ShowDialog() != System.Windows.Forms.DialogResult.OK)  
                         return;  
                     }  
                     //Cria os Vertices  
                     int vn = lwp.NumberOfVertices;  
                     Parallel.For(0, vn, i =>  
                     {  
                       Point2d pt = lwp.GetPoint2dAt(i);  
                       txt += frmte.txtbox_description.Text + "\t" + pt.X.ToString().Replace(",", ".") + "\t" + pt.Y.ToString().Replace(",", ".") +  
                            "\t" + lwp.Elevation.ToString("#################0.0###############", new CultureInfo("en-US")).Replace(",", ".") + "\t" + frmte.txtbox_h.Text.Replace(",", ".") + "\t" + frmte.cmbbox_fea.SelectedItem.ToString().Split(' ')[0] +  
                            "\t" + frmte.txt_profile_comment.Text + "\t" + frmte.txt_plan_comment.Text + "\r\n";  
                     });  
                     //Cria os pontos Intermediários  
                     vn = (int)lwp.Length;  
                     Parallel.For(0, vn, i =>  
                     {  
                       Point3d pt = lwp.GetPointAtDist((double)i);  
                       txt += frmte.txtbox_description.Text + "\t" + pt.X.ToString().Replace(",", ".") + "\t" + pt.Y.ToString().Replace(",", ".") +  
                            "\t" + lwp.Elevation.ToString("#################0.0###############", new CultureInfo("en-US")).Replace(",", ".") + "\t" + frmte.txtbox_h.Text.Replace(",", ".") + "\t" + frmte.cmbbox_fea.SelectedItem.ToString().Split(' ')[0] +  
                            "\t" + frmte.txt_profile_comment.Text + "\t" + frmte.txt_plan_comment.Text + "\r\n";  
                     });  
                     continue;  
                   }  
                   // If an old-style, 2D polyline  
                   Polyline2d p2d = acEnt as Polyline2d;  
                   if (p2d != null)  
                   {  
                     List<Vertex2d> v2dList = new List<Vertex2d>();  
                     //// Use foreach to get each contained vertex  
                     foreach (ObjectId vId in p2d)  
                     {  
                       v2dList.Add((Vertex2d)acTrans.GetObject(vId, OpenMode.ForRead));  
                       //acDoc.Editor.WriteMessage("\n" + v2d.Position.ToString());  
                     }  
                     Parallel.For(0, v2dList.Count, i =>  
                     {  
                       txt += frmte.txtbox_description.Text + "\t" + v2dList[i].Position.X.ToString().Replace(",", ".") + "\t" + v2dList[i].Position.Y.ToString().Replace(",", ".") +  
                       "\t" + v2dList[i].Position.Z.ToString("#################0.0###############", new CultureInfo("en-US")).Replace(",", ".") + "\t" + frmte.txtbox_h.Text.Replace(",", ".") + "\t" + frmte.cmbbox_fea.SelectedItem.ToString().Split(' ')[0] +  
                       "\t" + frmte.txt_profile_comment.Text + "\t" + frmte.txt_plan_comment.Text + "\r\n";  
                     });  
                     Parallel.For(0, (int)p2d.Length, i =>  
                     {  
                       Point3d pt = p2d.GetPointAtDist((double)i);  
                       txt += frmte.txtbox_description.Text + "\t" + pt.X.ToString().Replace(",", ".") + "\t" + pt.Y.ToString().Replace(",", ".") +  
                       "\t" + pt.Z.ToString("#################0.0###############", new CultureInfo("en-US")).Replace(",", ".") + "\t" + frmte.txtbox_h.Text.Replace(",", ".") + "\t" + frmte.cmbbox_fea.SelectedItem.ToString().Split(' ')[0] +  
                       "\t" + frmte.txt_profile_comment.Text + "\t" + frmte.txt_plan_comment.Text + "\r\n";  
                     });  
                     continue;  
                   }  
                   // Use foreach to get each contained vertex  
                 }  
               }  
               frm_plscadd.RichTextBox_Output.Text += txt;  
             }  

A parte paralela está na escrita dos valores no texto, que demora muito, nesse ponto a escrita paralela reduz mais e 10x o tempo de processamento. Não consegui medir ao certo quanto tempo demorava porque quando o volume era grande eu acabava por abortar e não media, mas demorava inicialmente mais de 4 horas para processar um .*dwg de elevações de 10Mb e agora com o Parallel.For gasta em torno de 5 minutos.


 vn = (int)lwp.Length;  
Parallel.For(0, vn, i =>  
{  
      Point3d pt = lwp.GetPointAtDist((double)i);  
      txt += frmte.txtbox_description.Text + "\t" + pt.X.ToString().Replace(",", ".") + "\t" + pt.Y.ToString().Replace(",", ".") +  
       "\t" + lwp.Elevation.ToString("#################0.0###############", new CultureInfo("en-US")).Replace(",", ".") + "\t" + frmte.txtbox_h.Text.Replace(",", ".") + "\t" + frmte.cmbbox_fea.SelectedItem.ToString().Split(' ')[0] +  
       "\t" + frmte.txt_profile_comment.Text + "\t" + frmte.txt_plan_comment.Text + "\r\n";  
});  

Espero que o artigo tenha sido útil escrevi em tempo recorde.


Comentários

  1. Software mal projetado mesmo o AutoCAD. Fiquei muito triste ao ver meu Q9550 travando com uma planta de 17MB sendo que o processamento está travado em 25%.

    Até quando a Autodesk vai deixar o software subutilizar nossos processadores?

    ResponderExcluir
    Respostas
    1. Olá Raul,
      Na verdade software é bem projetado só que ele ficou para trás com a evolução da tecnologia multi core. Eu acredito que já tenha funções que a Autodesk tenha implementado a função multi core, isso é um processo demorado porque são muitas funções e assim como eu mostro neste exemplo não é uma tarefa fácil e passa pela estrutura de acesso de cada camada de código.

      Mas vamos seguir na expectativa que a coisa vai melhorar!

      Abraço,
      Victor

      Excluir

Postar um comentário

Postagens mais visitadas deste blog

Numeração Automática no AutoCAD

Sem dúvida esse é uma ferramenta única que fazia falta a tempos e foi introduzida recentemente no AutoCAD, na última década… Em 2013 tinha feito um programa para criar uma bolinhas de numeração automática usando o conceito de Leader eu fazia um controle dos números dos itens na mão mesmo, depois de um tempo resolvi fazer um menuzinho para controlar o conteúdo do número dos bullets, hoje isso não é mais necessário, existe um comando no próprio AutoCAD para isso e faz parte das Ferramentas Expressas do AutoCAD. Gosto de compartilhar ferramentas e dicas que tendem a ser menos conhecidas ou subutilizadas, mas ainda podem fornecer um impulso para sua produtividade e fluxo de trabalho e que eu mesmo me surpreendi quando as encontrei Hoje, quero apresentar outra ferramenta, a numeração automática. Como o nome indica, o comando Numeração automática permite que você adicione automaticamente um número às etiquetas ou texto em um desenho. Vou mostrar como a numeração automática pode ser usada par...

Unindo blocos em AutoCAD utilizando .NET

Hoje me deparai com uma necessidade de unir blocos com propriedades então resolvi fazer um comando para realizar esses procedimento. A principio esse procedimento não é tão complexo de ser executado individualmente mas por outro lado gostaria também de analisar um pouco melhor as possibilidades de se manipular blocos utilizando o AutoCAD. Abaixo o código que utilizei: using Autodesk.AutoCAD.ApplicationServices ; using Autodesk.AutoCAD.DatabaseServices ; using Autodesk.AutoCAD.EditorInput ; using Autodesk.AutoCAD.Runtime ; using System.Linq ; // This line is not mandatory, but improves loading performances [assembly: CommandClass(typeof(Merging_AutoCAD_blocks.Comandos))] namespace Merging_AutoCAD_blocks { public class Comandos { /// <summary> /// Uni dois ou mais blocos em um unico bloco. /// </summary> [CommandMethod("MERGBLKS")] public static void UnindoBlocos () {...

Operando com Imagens Georreferenciadas no AutoCAD - Ortofotos

A coisa mais desagradável para quem trabalha com documentos em DWG com ortofotos ( fotos aéreas georreferenciadas) é quando se recebe o documento e a ortofoto não é carregada. Uma linha de transmissão onde a plotagem da estruturas é feita em um documento combinado de planta e perfil. Na área da planta é feito pela topografia um serviço de recorte e renderização das imagens em formato de ortofoto na região do desenho. Como a linha de transmissão a projeção é feita sempre linear na planta observando os vértices uma mesma ortofoto pode ser representada várias vezes numa mesma folha em um enjanelamento diferente. Tratamento de Ortofotos no AutoCAD - EXTERNALREFERENCES As ortofotos no AutoCAD são tratadas como referências a objetos externos. Isso é bom porque cada ortofoto pode ter de poucos kb até Gb de tamanho. Se for parte integrante do DWG poderia deixa-lo grande de mais para ser manipulado. Cada projeção no documento funciona como uma imagem projetada em um...