Marco Cantu publicó el interesante artículo Explaining Tasks in Delphi Parallel Library… by Creating too Many Tasks en donde usaba TTask con Delphi para procesar datos en paralelo usando diferentes hilos de ejecución.
Entonces decidí profundizar más en TParallel de Embarcadero, el objeto que encapsula las tareas en paralelo dentro de PPL (Parallel Programming Library), y con el que sin duda tendré que trabajar para implementar el soporte multithreading en FileOptimizer.
El concepto es lógico, puesto que la mayor parte de la complejidad es transparente para el programador, y gracias al Using the For Loop from the Parallel Programming Library, se entiende muy bien.
El problema es que el código se expone por partes, como conclusión de las explicaciones, y que éste no llega a compilar debido a que la función sqrt no tiene ninguna sobrecarga que acepte como argumento un entero:
k = (int)sqrt(N);
sqrt acepta float y double, pero no int como es el caso, de manera que me propuse solucionar todas estas carencias.
– Corregir el error de compilación de sqrt.
– Proporcionar el código completo.
– Aumentar las iteraciones totales de 5.000.000 a 50.000.000, para resaltar mejor la diferencia de rendimiento.
– Hacer que el formulario pueda maximizarse, y que todos los controles tengan un nombre más descriptivo.
– Refactorizar y formatear el código para resultar más claro y legible.
– Aplicar alguna optimización menor.
Finalmente el código completo ha quedado así:
cppMain.cpp
// ---------------------------------------------------------------------------
#include
#include
#include
#include
#pragma hdrstop
#include "cppMain.h"
const unsigned int KI_MAX = 50000000;
// ---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TfrmMain *frmMain;
// ---------------------------------------------------------------------------
__fastcall TfrmMain::TfrmMain(TComponent* Owner): TForm(Owner)
{
}
// ---------------------------------------------------------------------------
void __fastcall TfrmMain::butForLoopClick(TObject *Sender)
{
unsigned int iTot = 0;
System::Diagnostics::TStopwatch sw = System::Diagnostics::TStopwatch::Create();
sw.Start();
for (unsigned int iCont = 1; iCont <= KI_MAX; iCont++)
{
if (IsPrime(iCont))
{
iTot++;
}
}
sw.Stop();
mmoResults->Lines->Add(String().sprintf(L"Sequential For loop. Time (in milliseconds): %lld, Primes found: %u", sw.ElapsedMilliseconds, iTot));
}
// ---------------------------------------------------------------------------
void __fastcall TfrmMain::btnParallelForClick(TObject *Sender)
{
miTot = 0;
System::Diagnostics::TStopwatch sw = System::Diagnostics::TStopwatch::Create();
sw.Start();
TParallel::For(NULL, 1, KI_MAX, ParallelIterator);
sw.Stop();
mmoResults->Lines->Add (String().sprintf(L"Parallel For loop. Time (in milliseconds): %lld, Primes found: %u", sw.ElapsedMilliseconds, miTot));
}
// ---------------------------------------------------------------------------
void __fastcall TfrmMain::ParallelIterator(TObject* Sender, int AIndex)
{
if (IsPrime(AIndex))
{
TInterlocked::Increment((int &) miTot);
}
}
// ---------------------------------------------------------------------------
bool TfrmMain::IsPrime(unsigned int piN)
{
unsigned int iTest, iSquare;
bool bPrime;
if (piN <= 3)
{
return(piN > 1);
}
else if (((piN & 1) == 0) || ((piN % 3) == 0))
{
return(false);
}
else
{
bPrime = true;
iSquare = sqrt((double) piN);
iTest = 5;
while (iTest <= iSquare)
{
if (((piN % iTest) == 0) || ((piN % (iTest + 2)) == 0))
{
bPrime = false;
break; // jump out of the for loop
}
iTest += 6;
}
return(bPrime);
}
}
//---------------------------------------------------------------------------
cppMain.h
//---------------------------------------------------------------------------
#ifndef cppMainH
#define cppMainH
//---------------------------------------------------------------------------
#include
#include
#include
#include
//---------------------------------------------------------------------------
class TfrmMain : public TForm
{
__published: // IDE-managed Components
TButton *butForLoop;
TButton *butParallelLoop;
TMemo *mmoResults;
void __fastcall btnParallelForClick(TObject *Sender);
void __fastcall butForLoopClick(TObject *Sender);
private: // User declarations
void __fastcall ParallelIterator(TObject* Sender, int AIndex);
public: // User declarations
__fastcall TfrmMain(TComponent* Owner);
bool IsPrime(unsigned int piN);
unsigned int miTot;
};
//---------------------------------------------------------------------------
extern PACKAGE TfrmMain *frmMain;
//---------------------------------------------------------------------------
#endif
Os dejo el proyecto, los archivos fuente, y el compilado para descargar directamente aquí (1,1 Mb. en formato ZIP).
gran trabajo y una gran aportacion el que pueda usar integer.
Personalmente no me gusta usar recursos o elementos que no pueda introducir en la propia aplicacion, mas que nada porque al reinstalar siempre los pierdo 😀 tampoco me agrada el multihilo, no soy partidario de usarlo por cuestiones que no vienen a cuento aqui.
Ahora bien, quien necesite usarlo y lo requiera para sus aplicaciones, sin duda valorara muchisimo la flexibilidad que le aporta tu modificación (o tu mejora, mas bien). Graacias por compartirlo Guti.
Muchas gracias bianamaran. Ya nos explicarás porque no te gusta el multihilo. En mi caso, y ya desde los tiempos de DOS, me encantaba la multitarea. Recuerdo implementar usando el controlador DMA, que era lo poco que había en la época, una sencilla animación que se ejecutaba al mismo tiempo que el juego cargaba y descomprimía gráficos.
En la actualidad pienso que es aún más necesario. Todas las tareas de cálculo que llevan más tiempo del que necesita un humano para reaccionar, son susceptibles de ser aceleradas repartiéndose. El problema es que su implementación, no es nada fácil, y aún estamos en pañales en eso. Ya sea PPL, OpenMP o cualquier otra tecnología, facilitan, pero sigue sin ser fácil.