Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

An OpenCL code in MQL5 does not get distributed jobs to each GPU core

I have created a GPU based indicator for MetaTrader Terminal platform, using OpenCL and MQL5.

I have tried hard that my [ MetaTrader Terminal: Strategy Tester ] optimization job must get transferred on GPU to maximum. Most of the calculations are done by the indicator. Hence, I made changes in the indicator and has completely transferred on GPU.

But the real issue arises when I try to go for optimization process in the strategy tester section.
The process I see uses both my GPU and CPU but there is no effect on the complete process.

I suspect that the process is not getting distributed to each GPU core for processing, instead all the GPU cores are working on the same process or function for execution.

Kindly, let me know what I need to do to get the single GPU work for on single function execution to give faster output.

Here is my code link attached: Complete code with Expert

The kernel of my code is :

__kernel void calSMA(
                     int limit, 
                     int rates_total, 
                     __global double *price, 
                     __global double *ExtLineBuffer,
                     int InpMAPeriod

                   )
         { 

                int count = 0;
                int len = get_global_id(2);
                for(int i=limit;i<rates_total;i++) 

                     ExtLineBuffer[len+i] = ExtLineBuffer[len+ i-1]+(price[len+i]-price[len+i-InpMAPeriod])/InpMAPeriod;

         }


 __kernel void calcSMALoop(int begin, int limit, __global double *price, __global double *firstValue, int InpMAPeriod)
          { 
                int i, len = get_global_id(2);
                for(i=begin;i<limit;i++) 
                  firstValue[len]+=price[i]; 
                firstValue[len]/=InpMAPeriod;

          }

__kernel void calcEMA(int begin, int limit, __global double *price, __global double *ExtLineBuffer, double SmoothFactor)
          {
            int len = get_global_id(2);
            for(int i=begin;i<limit;i++)
               ExtLineBuffer[len + i]=price[len + i]*SmoothFactor+ExtLineBuffer[len + i-1]*(1.0-SmoothFactor);
          }

__kernel void calcSSMA(int limit, int rates_total, __global double *price, __global double *ExtLineBuffer, int InpMAPeriod)
          {
            int len = get_global_id(2);
            for(int i=limit;i<rates_total;i++)
               ExtLineBuffer[len + i]=(ExtLineBuffer[len + i-1]*(InpMAPeriod-1)+price[len + i])/InpMAPeriod;         
          }

__kernel void calcLWMALoop(int begin, int limit, __global double *price, __global double *firstValue, int weightsum, __global int *weightreturn)
          {

            weightsum = 0;
            int len = get_global_id(2);
            for(int i=begin;i<limit;i++)
            {                 
               weightsum+=(i-begin+1);
               firstValue[len]+=(i-begin+1)*price[i];
            }
      firstValue[len]/=(double)weightsum;
          weightreturn[0] = weightsum;
          }
//__global int counter = 0;
double returnCalculation(int InpMAPeriod, double price, int j)
{

   return ((InpMAPeriod-j)*price);
}
__kernel void calcLWMA(int limit, int rates_total, __global double *price, __global double *ExtLineBuffer, int InpMAPeriod, int weightsum)
          {
          int len = get_global_id(2);
            for(int i=limit;i<rates_total;i++)
            {
               double     sum = 0;
               for(int j=0;j<InpMAPeriod;j++) sum+=returnCalculation(InpMAPeriod,price[len + i-j],j);
                  ExtLineBuffer[len + i]=sum/weightsum;
            }
          }

Please suggest me the way out for distributing the function with different values or frames in MQL5 using GPU on OpenCL.

EDITED

Its a great challenge for the challenge seekers... Even I am eager to know whether there can be anything done with OpenCL and MQL5 for optimization task. I hope I will get answers for what I am seeking.

EDITED AGAIN the MAGPU.mqh file

#include "CHECKMA.mq5"
#define CUDA_CORE 2

int Execute_SMA(
                 const double &price[],                 
                 int rates_total,
                 int limit
                 )
 {

   int cl_mem = CLBufferCreate(cl_ctx,ArraySize(price)*sizeof(double),CL_MEM_READ_WRITE), 
       cl_price = CLBufferCreate(cl_ctx,ArraySize(price)*sizeof(double),CL_MEM_READ_WRITE);

          Check_Memory_Initialization(cl_mem, cl_price, cl_CommonKernel1, "Execute_SMA function error");
           if(!CLSetKernelArgMem(cl_CommonKernel1,2,cl_price))
            Print("Input Bufer Not Set");
          //else Print("Input Buffer Set");
           if(!CLSetKernelArgMem(cl_CommonKernel1,3,cl_mem))
            Print("Output Bufer Not Set");
           //else Print("Output Buffer Set");

           if(!CLBufferWrite(cl_price, price))
            Print("Could not copy Input buffer"); 
           //else Print("Copied: ",cl_price);    
           if(!CLBufferWrite(cl_mem, ExtLineBuffer))
            Print("Could not copy Input buffer"); 
           //else Print("Copied: ",cl_mem);  

           //else Print("Input Buffer Copied");
           if(!CLSetKernelArg(cl_CommonKernel1,0,limit))
           Print("Could Not Set Arg 0");
           //else Print("Set Arg 0");
           if(!CLSetKernelArg(cl_CommonKernel1,1,rates_total))
           Print("Could Not Set Arg 1");
           //else Print("Set Arg 1");
           //if(!CLSetKernelArg(cl_CommonKernel1,4,previous_value))
           //Print("Could Not Set Arg2");
           //else Print("Set Arg 2");
           if(!CLSetKernelArg(cl_CommonKernel1,4,InpMAPeriod))
           Print("Could Not Set Arg3: ",GetLastError());

           //Print(CLGetInfoInteger(cl_ctx,CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS));


           if(!CLExecute(cl_CommonKernel1,CUDA_CORE,offset,work))
               Print("Kernel not executed",GetLastError());
           //else Print("Executing Now!");
           //if(CLExecutionStatus(cl_krn) == 0) Print("Completed");
           //if(CLExecutionStatus(cl_krn) == 1) Print("CL_RUNNING");
           //if(CLExecutionStatus(cl_krn) == 2) Print("CL_SUBMITTED");
           //if(CLExecutionStatus(cl_krn) == 3) Print("CL_QUEUED");
           //if(CLExecutionStatus(cl_krn) == -1)Print("Error Occurred:", GetLastError());
           //if(!CLExecutionStatus(cl_krn))
            //Print(CLExecutionStatus(cl_krn));

           if(!CLBufferRead(cl_mem,ExtLineBuffer))
           Print("Buffer Copy Nothing: ", GetLastError());

      CLBufferFree(cl_price);
      CLBufferFree(cl_mem);
  return(1);
 } 

 double ExecuteLoop(
                 int begin,
                 int limit,
                 const double &price[]
                 )
 {

   int cl_mem = CLBufferCreate(cl_ctx,sizeof(double),CL_MEM_READ_WRITE), 
       cl_price = CLBufferCreate(cl_ctx,ArraySize(price)*sizeof(double),CL_MEM_READ_WRITE);
   double temp[];
   ArrayResize(temp,1);
   temp[0] = 0;

           Check_Memory_Initialization(cl_mem, cl_price, cl_CommonKernel2, "ExecuteLoop function error");

           if(!CLSetKernelArgMem(cl_CommonKernel2,2,cl_price))
            Print("Input Bufer Not Set 2");
           if(!CLSetKernelArgMem(cl_CommonKernel2,3,cl_mem))
            Print("Output Bufer Not Set 2");

           if(!CLBufferWrite(cl_price, price))
            Print("Could not copy Input buffer 2"); 
           if(!CLSetKernelArg(cl_CommonKernel2,0,begin))
            Print("Could Not Set Arg 0");
           if(!CLSetKernelArg(cl_CommonKernel2,1,limit))
            Print("Could Not Set Arg 1");
           if(!CLSetKernelArg(cl_CommonKernel2,4,InpMAPeriod))
            Print("Could Not Set Arg3: ",GetLastError());

           if(!CLExecute(cl_CommonKernel2,CUDA_CORE,offset,work))
               Print("Kernel not executed",GetLastError());

           if(!CLBufferRead(cl_mem,temp))
           Print("Buffer Copy Nothing: ", GetLastError());
      CLBufferFree(cl_price);
      CLBufferFree(cl_mem);
  return(temp[0]);
 } 
int ExecuteEMA(int begin, int limit, const double &price[], double SmoothFactor)
{

   int cl_mem = CLBufferCreate(cl_ctx,ArraySize(price)*sizeof(double),CL_MEM_READ_WRITE), 
       cl_price = CLBufferCreate(cl_ctx,ArraySize(price)*sizeof(double),CL_MEM_READ_WRITE);

         Check_Memory_Initialization(cl_mem, cl_price, cl_CommonKernel1, "ExecuteEMA function error");

           if(!CLSetKernelArgMem(cl_CommonKernel1,2,cl_price))
            Print("Input Bufer Not Set");
           if(!CLSetKernelArgMem(cl_CommonKernel1,3,cl_mem))
            Print("Output Bufer Not Set");

           if(!CLBufferWrite(cl_price, price))
            Print("Could not copy Input buffer"); 
           if(!CLBufferWrite(cl_mem, ExtLineBuffer))
            Print("Could not copy Input buffer"); 

           if(!CLSetKernelArg(cl_CommonKernel1,0,begin))
            Print("Could Not Set Arg 0");
           if(!CLSetKernelArg(cl_CommonKernel1,1,limit))
            Print("Could Not Set Arg 1");
           if(!CLSetKernelArg(cl_CommonKernel1,4,SmoothFactor))
            Print("Could Not Set Arg3: ",GetLastError());

           if(!CLExecute(cl_CommonKernel1,CUDA_CORE,offset,work))
               Print("Kernel not executed",GetLastError());

           if(!CLBufferRead(cl_mem,ExtLineBuffer))
            Print("Buffer Copy Nothing: ", GetLastError());

      CLBufferFree(cl_price);
      CLBufferFree(cl_mem);

  return(1);
 }   
int Execute_SSMA(
                 const double &price[],                 
                 int rates_total,
                 int limit
                 )
 {

   int cl_mem = CLBufferCreate(cl_ctx,ArraySize(price)*sizeof(double),CL_MEM_READ_WRITE), 
       cl_price = CLBufferCreate(cl_ctx,ArraySize(price)*sizeof(double),CL_MEM_READ_WRITE);

       Check_Memory_Initialization(cl_mem, cl_price, cl_CommonKernel1, "Execute_SSMA function error");

           if(!CLSetKernelArgMem(cl_CommonKernel1,2,cl_price))
            Print("Input Bufer Not Set");
           if(!CLSetKernelArgMem(cl_CommonKernel1,3,cl_mem))
            Print("Output Bufer Not Set");

           if(!CLBufferWrite(cl_price, price))
            Print("Could not copy Input buffer"); 
           if(!CLBufferWrite(cl_mem, ExtLineBuffer))
            Print("Could not copy Input buffer"); 
//             
           //else Print("Input Buffer Copied");
           if(!CLSetKernelArg(cl_CommonKernel1,0,limit))
            Print("Could Not Set Arg 0");
           if(!CLSetKernelArg(cl_CommonKernel1,1,rates_total))
            Print("Could Not Set Arg 1");
           if(!CLSetKernelArg(cl_CommonKernel1,4,InpMAPeriod))
            Print("Could Not Set Arg3: ",GetLastError());

           if(!CLExecute(cl_CommonKernel1,CUDA_CORE,offset,work))
               Print("Kernel not executed",GetLastError());
           if(!CLBufferRead(cl_mem,ExtLineBuffer))
            Print("Buffer Copy Nothing: ", GetLastError());

      CLBufferFree(cl_price);
      CLBufferFree(cl_mem);
  return(1);
 } 

 double ExecuteLWMALoop(
                 int begin,
                 int limit,
                 const double &price[],
                 int weightsumlocal
                 )
 {

   int cl_mem = CLBufferCreate(cl_ctx,sizeof(double),CL_MEM_READ_WRITE), 
       cl_price = CLBufferCreate(cl_ctx,ArraySize(price)*sizeof(double),CL_MEM_READ_WRITE),
       cl_weightsumlocal = CLBufferCreate(cl_ctx,sizeof(int),CL_MEM_READ_WRITE);
   double temp[];
   int weight[];
   ArrayResize(temp,1);
   ArrayResize(weight,1);
   weight[0] = 0;
   temp[0] = 0;

           Check_Memory_Initialization(cl_mem, cl_price, cl_CommonKernel2, "ExecuteLWMALoop function error");

           if(!CLSetKernelArgMem(cl_CommonKernel2,2,cl_price))
            Print("Input Bufer Not Set 2");
           if(!CLSetKernelArgMem(cl_CommonKernel2,3,cl_mem))
            Print("Output Bufer Not Set 2");
           if(!CLSetKernelArgMem(cl_CommonKernel2,5,cl_weightsumlocal))
            Print("Output Bufer Not Set 2");

           if(!CLBufferWrite(cl_price, price))
            Print("Could not copy Input buffer 2"); 
           if(!CLSetKernelArg(cl_CommonKernel2,0,begin))
            Print("Could Not Set Arg 0");
           if(!CLSetKernelArg(cl_CommonKernel2,1,limit))
            Print("Could Not Set Arg 1");
           if(!CLSetKernelArg(cl_CommonKernel2,4,weightsumlocal))
            Print("Could Not Set Arg3: ",GetLastError());

           if(!CLExecute(cl_CommonKernel2,CUDA_CORE,offset,work))
               Print("Kernel not executed",GetLastError());

           if(!CLBufferRead(cl_mem,temp))
            Print("Buffer Copy Nothing: ", GetLastError());
           if(!CLBufferRead(cl_weightsumlocal,weight))
            Print("Buffer Copy Nothing: ", GetLastError());
       weightsum = weight[0];

       CLBufferFree(cl_weightsumlocal);
      CLBufferFree(cl_price);
      CLBufferFree(cl_mem);
  return(temp[0]);
 } 
int Execute_LWMA(const double &price[], int rates_total, int limit, int weightsum1)
       {

         int cl_mem = CLBufferCreate(cl_ctx,ArraySize(price)*sizeof(double),CL_MEM_READ_WRITE), 
       cl_price = CLBufferCreate(cl_ctx,ArraySize(price)*sizeof(double),CL_MEM_READ_WRITE);

       Check_Memory_Initialization(cl_mem, cl_price, cl_CommonKernel1, "Execute_SSMA function error");

           if(!CLSetKernelArgMem(cl_CommonKernel1,2,cl_price))
            Print("Input Bufer Not Set");
           if(!CLSetKernelArgMem(cl_CommonKernel1,3,cl_mem))
            Print("Output Bufer Not Set");

           if(!CLBufferWrite(cl_price, price))
            Print("Could not copy Input buffer"); 
           if(!CLBufferWrite(cl_mem, ExtLineBuffer))
            Print("Could not copy Input buffer"); 

           //else Print("Input Buffer Copied");
           if(!CLSetKernelArg(cl_CommonKernel1,0,limit))
            Print("Could Not Set Arg 0");
           if(!CLSetKernelArg(cl_CommonKernel1,1,rates_total))
            Print("Could Not Set Arg 1");
           if(!CLSetKernelArg(cl_CommonKernel1,4,InpMAPeriod))
            Print("Could Not Set Arg4: ",GetLastError());
           if(!CLSetKernelArg(cl_CommonKernel1,5,weightsum1))
            Print("Could Not Set Arg5: ",GetLastError());

           if(!CLExecute(cl_CommonKernel1,CUDA_CORE,offset,work))
               Print("Kernel not executed",GetLastError());
           if(!CLBufferRead(cl_mem,ExtLineBuffer))
            Print("Buffer Copy Nothing: ", GetLastError());

      CLBufferFree(cl_price);
      CLBufferFree(cl_mem);
  return(1);

       }
void checkKernel(int cl_kernel, string var_name)
{

           if(cl_kernel==INVALID_HANDLE )
           {
               CLProgramFree(cl_prg);
               CLContextFree(cl_ctx);
               Print("OpenCL kernel create failed: ERR_OPENCL_INVALID_HANDLE ", var_name);
               return;
           }
           if(cl_kernel==ERR_INVALID_PARAMETER )
           {
               CLProgramFree(cl_prg);
               CLContextFree(cl_ctx);
               Print("OpenCL kernel create failed: ERR_INVALID_PARAMETER ", var_name);
               return;
           }
           if(cl_kernel==ERR_OPENCL_TOO_LONG_KERNEL_NAME  )
           {
               CLProgramFree(cl_prg);
               CLContextFree(cl_ctx);
               Print("OpenCL kernel create failed: ERR_OPENCL_TOO_LONG_KERNEL_NAME ", var_name);
               return;
           }
           if(cl_kernel==ERR_OPENCL_KERNEL_CREATE )
           {
               CLProgramFree(cl_prg);
               CLContextFree(cl_ctx);
               Print("OpenCL kernel create failed 1: ERR_OPENCL_KERNEL_CREATE ", var_name);
               return;
           }

}

 int Check_Memory_Initialization(int cl_mem, int cl_price, int cl_ker, string name_process_call)
      {

         if(cl_mem==INVALID_HANDLE)
           {
               CLKernelFree(cl_ker);
               CLProgramFree(cl_prg);
               CLContextFree(cl_ctx);
               Print("OpenCL buffer create failed: cl_mem INVALID_HANDLE: ", name_process_call);
               return(0);
           }
           if(cl_mem==ERR_NOT_ENOUGH_MEMORY )
           {
               CLKernelFree(cl_ker);
               CLProgramFree(cl_prg);
               CLContextFree(cl_ctx);
               Print("OpenCL buffer create failed: cl_mem ERR_NOT_ENOUGH_MEMORY: ", name_process_call);
               return(0);
           }
           if(cl_mem==ERR_OPENCL_BUFFER_CREATE )
           {
               CLKernelFree(cl_ker);
               CLProgramFree(cl_prg);
               CLContextFree(cl_ctx);
               Print("OpenCL buffer create failed: cl_mem ERR_OPENCL_BUFFER_CREATE: ", name_process_call);
               return(0);
           }

           if(cl_price==INVALID_HANDLE)
           {
               CLKernelFree(cl_ker);
               CLProgramFree(cl_prg);
               CLContextFree(cl_ctx);
               CLBufferFree(cl_mem);
               Print("OpenCL buffer create failed: cl_price: ", name_process_call);
               return(0);
           }
           if(cl_price==ERR_NOT_ENOUGH_MEMORY)
           {
               CLKernelFree(cl_ker);
               CLProgramFree(cl_prg);
               CLContextFree(cl_ctx);
               CLBufferFree(cl_mem);
               Print("OpenCL buffer create failed: cl_price ERR_NOT_ENOUGH_MEMORY: ", name_process_call);
               return(0);
           }
           if(cl_price==ERR_OPENCL_BUFFER_CREATE)
           {
               CLKernelFree(cl_ker);
               CLProgramFree(cl_prg);
               CLContextFree(cl_ctx);
               CLBufferFree(cl_mem);
               Print("OpenCL buffer create failed: cl_price ERR_OPENCL_BUFFER_CREATE: ", name_process_call);
               return(0);
           }
        return(1);       
      }

MAIN INDICATOR FILE CHECKMA.mq5 file

    #resource "program_MA_GPU.cl" as string cl_program
    #include "MAGPU.mqh"
    #property indicator_chart_window
    #property indicator_buffers 1
    #property indicator_plots   1
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  Red
    input int            InpMAPeriod=13;         // Period
    input int            InpMAShift=0;           // Shift
    input ENUM_MA_METHOD InpMAMethod=MODE_SMA;  // Method
    //--- indicator buffers
    double               ExtLineBuffer[];
    int  offset[CUDA_CORE], work[CUDA_CORE];//={0,19,38,57,76,95,114,123};
    string str;   
       int cl_ctx, cl_prg, cl_CommonKernel1, cl_CommonKernel2;
    static int weightsum;    
    void CalculateSimpleMA(int rates_total,int prev_calculated,int begin,const double &price[])
      {
       int limit;
if(prev_calculated==0)
         {
          limit=InpMAPeriod+begin;
          ArrayFill(ExtLineBuffer,0,limit-1,0.0);
          ExtLineBuffer[limit-1]=ExecuteLoop(begin,limit,price);
         }
       else limit=prev_calculated-ArraySize(price)+InpMAPeriod+17;
         Execute_SMA(price,rates_total,limit);
      }
    void CalculateEMA(int rates_total,int prev_calculated,int begin,const double &price[])
      {
       int    limit;
       double SmoothFactor=2.0/(1.0+InpMAPeriod);

       if(prev_calculated==0)
         {

          limit=InpMAPeriod+begin;
          ExtLineBuffer[begin]=price[begin];
          ExecuteEMA(begin+1,limit,price,SmoothFactor);      
         }
       else limit=prev_calculated;
       ExecuteEMA(begin+99900,limit,price,SmoothFactor);
      }
    void CalculateLWMA(int rates_total,int prev_calculated,int begin,const double &price[])
      {
       int  limit; 
       if(prev_calculated==0)
         {
          weightsum=0;
          limit=InpMAPeriod+begin;
          //--- set empty value for first limit bars
          ArrayFill(ExtLineBuffer,0,limit,0.0);
          //--- calculate first visible value
          ExtLineBuffer[limit-1]=ExecuteLWMALoop(begin,limit,price,weightsum);
         }
       else limit=prev_calculated-ArraySize(price)+InpMAPeriod+17;
    //--- main loop
       Execute_LWMA(price,rates_total,limit,weightsum);
    }

    void CalculateSmoothedMA(int rates_total,int prev_calculated,int begin,const double &price[])
      {
       int limit;
    //--- first calculation or number of bars was changed
       if(prev_calculated==0)
         {
          limit=InpMAPeriod+begin;
          //--- set empty value for first limit bars
          ArrayFill(ExtLineBuffer,0,limit-1,0.0);
          ExtLineBuffer[limit-1]=ExecuteLoop(begin,limit,price);
         }
       else limit=prev_calculated-ArraySize(price)+InpMAPeriod+17;

          Execute_SSMA(price,rates_total,limit);
    //---
      }

    void OnInit()
      {
    //--- indicator buffers mapping
       SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA);
    //--- set accuracy
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
    //--- sets first bar from what index will be drawn
       PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpMAPeriod);
    //---- line shifts when drawing
       PlotIndexSetInteger(0,PLOT_SHIFT,InpMAShift);
    //--- name for DataWindow

    //---- sets drawing line empty value--
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
    //---- initialization done
       cl_ctx = CLContextCreate(CL_USE_GPU_ONLY);
       cl_prg=CLProgramCreate(cl_ctx,cl_program,str);

    if(cl_ctx==INVALID_HANDLE)
         {
          Print("OpenCL not found: ", GetLastError() );
          return;

         }  
       if(cl_prg==INVALID_HANDLE)
         {
          CLContextFree(cl_ctx);

          Print("OpenCL program create failed: ", str);
          return;
         }
         if(cl_prg==ERR_INVALID_PARAMETER )
         {
          CLContextFree(cl_ctx);

          Print("OpenCL program create failed: ", str);
          return;
         }
         if(cl_prg==ERR_NOT_ENOUGH_MEMORY )
         {
          CLContextFree(cl_ctx);

          Print("OpenCL program create failed: ", str);
          return;
         }
         if(cl_prg==ERR_OPENCL_PROGRAM_CREATE )
         {
          CLContextFree(cl_ctx);

          Print("OpenCL program create failed: ", str);
          return;
         }
         int c = 1;
         ArrayFill(work,0,CUDA_CORE,c);
         //ArrayInitialize(offset,0);
         int enter = -c;
         for (int i =0; i <  CUDA_CORE; i++)
         {
          offset[i] = enter +  c;
          enter = offset[i];
         }
       switch(InpMAMethod)
         {
          case MODE_SMA : cl_CommonKernel1 = CLKernelCreate(cl_prg,"calSMA");
                          checkKernel(cl_CommonKernel1,"cl_CommonKernel1 SMA");
                          cl_CommonKernel2 = CLKernelCreate(cl_prg,"calcSMALoop");  
                          checkKernel(cl_CommonKernel2,"cl_CommonKernel2 SMA");     
                          break;
          case MODE_EMA : cl_CommonKernel1 = CLKernelCreate(cl_prg,"calcEMA");
                          checkKernel(cl_CommonKernel1,"cl_CommonKernel1 EMA");                          
                          break;       
          case MODE_LWMA : cl_CommonKernel1 = CLKernelCreate(cl_prg,"calcLWMA");
                           checkKernel(cl_CommonKernel1,"cl_CommonKernel1 LWMA");
                           cl_CommonKernel2 = CLKernelCreate(cl_prg,"calcLWMALoop"); 
                           checkKernel(cl_CommonKernel2,"cl_CommonKernel2 LWMA");      
                          break;       
          case MODE_SMMA : cl_CommonKernel1 = CLKernelCreate(cl_prg,"calcSSMA");
                           checkKernel(cl_CommonKernel1,"cl_CommonKernel1 SSMA");
                           cl_CommonKernel2 = CLKernelCreate(cl_prg,"calcSMALoop"); 
                           checkKernel(cl_CommonKernel2,"cl_CommonKernel2 SSMA");    
                           break;  

         }


      }
int OnCalculate(const int rates_total,
                    const int prev_calculated,
                    const int begin,
                    const double &price[])
      {
       if(rates_total<InpMAPeriod-1+begin)
          return(0);
       if(prev_calculated==0)
          ArrayInitialize(ExtLineBuffer,0);
       PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpMAPeriod-1+begin);
       switch(InpMAMethod)
         {
          case MODE_EMA:  CalculateEMA(rates_total,prev_calculated,begin,price);        break;
          case MODE_LWMA: CalculateLWMA(rates_total,prev_calculated,begin,price);       break;
          case MODE_SMMA: CalculateSmoothedMA(rates_total,prev_calculated,begin,price); break;
          case MODE_SMA:  CalculateSimpleMA(rates_total,prev_calculated,begin,price);   break;
         }
    //--- return value of prev_calculated for next call
       return(rates_total);
      }
 void OnDeinit(const int reason)
      {
       CLKernelFree(cl_CommonKernel1);
       CLKernelFree(cl_CommonKernel2);
       CLProgramFree(cl_prg);
       CLContextFree(cl_ctx);
      }
like image 682
Jaffer Wilson Avatar asked May 16 '18 07:05

Jaffer Wilson


1 Answers

Help me write this code in a proper way so that my process become faster and will give proper result with my GPU. Animated UI-interactive Law of Diminishing Returns, re-formulated so as to also contain processing add-on setup / termination overheads

Facts first,
since April-2nd
you already know,
MQL5 CustomIndicator will not work this way ...

Facts matter - if MQL4/5 code-execution architecture has explicitly documented that there is no place for any extended latency / async / blocking operation(s) to be put ever inside any MQL4/5 CustomIndicator code-execution unit, there might be a reasonable time to stop all these S/O Community members attack and to start simply respect the documented & published fact.

MQL5 documentation is quite explicit and warning on adverse effects of its single-shared-thread architecture implications on performance and/or warns on the risk of a complete, inadvertent deadlocking of the whole system :

All indicators calculated on one symbol, even if they are attached to different charts, work in the same thread. Thus, all indicators on one symbol share the resources of one thread.


An infinite loop ( block / increased latency / unexpected delay ) in one indicator will stop all other indicators on this symbol.

Sure, one may ask many kind OpenCL professionals, present in S/O Community, to help, express extreme interest in getting their sponsored knowledge et cetera, et cetera, if they decide to further spend their valuable time to sponsor and extend such efforts.

All this still has to meet reality and best respect the know facts, before any reasonable effort ( under whatever forces expressed ) may at least start to work in the proper direction.


How does the CPU : GPU topology work internally ?

Using an async, Out-of-Order delivery operated, async by definition, Queue-stored, sequence of zero-to-many GPU-device targetted computing job(s), all having a start-to-finish principally in-deterministic RTT-duration.

The CPU-side end of a GPU-device access-Queue can be instructed to send a job to GPU ( a task ~ a program to execute ):

The Finite State Automaton of the GPU-task-management workflow has the following topology-graph:

< START____________> s = GetMicrosecondCount();
( MQL5 RQSTs )
          |
          |
          |
       T0:+---+--> CL_QUEUED            : 3 == "queued", i.e. waiting for its turn ( submission )
          ?   |    |  |
          ?   +----+  |
          ?           v
       T0+?      +--> CL_SUBMITTED      : 2 == "submitted" for an OpenCL-device execution
          ?      |    |  |        
          ?      +----+  |        
          ?              v        
       T0+?         +--> CL_RUNNING     : 1 == "running" the kernel-code on a mapped OpenCL resource pool
          ?         |    |  |     
          ?         +----+  |     
          ?                 v     
       T0+?                 CL_COMPLETE : 0 == "program complete", processing has finished its remote outputs
          ?                 |  
       T0+?-----------------+
          |
( MQL5 FREEs )
< END_____________>  e = GetMicrosecondCount();
                     PrintFormat( "RTT-COST WAS ~ %9d [us] ( CLES==0 ? %d )",
                                   ( e - s ),
                                   CLExecutionStatus( _gpuKernelHANDLE )
                                   );

Next, let's respect the realm of GPU-architectures :

GPU computing devices are different, having other silicon-hardwired architectures, than any universal CPU CISC/RISC computing devices.

The reason WHY is very important here.

GPU devices use Streaming Multiprocessor eXecution units ( SMX units ), that are referred in some hardware-inspection tools.

While the letter M in the SMX abbreviation emphasises, there are multiple executions loadable onto the SMX-unit, yet, all such cases actually do execute ( sure, only if instructed in such a manner, which goes outside of the scope of this topic, to cover / span all over each of the SMX-present SM-cores ) the very same computing instructions - this is the only way they can operate - it is called a SIMT/SIMD-type of limited scope of parallelism achievable ( co-locally ) on the perimeter of the SMX only, where single-instruction-multiple-{ threads | data } can become executed within a present SIMT/SIMD-( WARP-wide | half-WARP-wide | WARP-ignoring-GreedyMode )-scheduler capabilities. Important to note, the more narrow the width of the scheduled SIMT/SIMD-execution gets, the less SMX/SM-cores actually do any useful part of the global job execution and the more wasted time devastates the battle on performance due to falling number of N-(CPUs) in effect, as discussed below.

Having listed those 384 cores, posted above, means a hardware limit, beyond which this co-locally orchestrated SIMT/SIMD-type of limited-scope parallelism cannot grow, and all attempts into this direction will lead to a pure-[SERIAL] internal scheduling of GPU-jobs ( yes, i.e. one-after-another ).

Understanding these basics is cardinal, as without these architecture features, one may expect a behaviour, that is actually principally impossible to get orchestrated in any whatever kind of the GPGPU system, having a formal shape of [ 1-CPU-host : N-GPU-device(s) ] compositions of autonomous, asynchronous distributed-system star-of-nodes.

Any GPU-kernel loaded from a CPU-host onto GPU will get mapped onto a non-empty set of SMX-unit(s), where a specified number of cores ( another, finer grain geometry-of-computing resources is applied, again going way beyond the scope of this post ) gets loaded with a stream of SIMT/SIMD-instructions, not violating the GPU-device limits:

 ...
+----------------------------------------------------------------------------------------
 Max work items dimensions:          3       // 3D-geometry grids possible
    Max work items[0]:               1024    // 1st dimension max.
    Max work items[1]:               1024
    Max work items[2]:               64      // theoretical max. 1024 x 1024 x 64 BUT...
+----------------------------------------------------------------------------------------
 Max work group size:                1024    // actual      max. "geometry"-size
+----------------------------------------------------------------------------------------
 ...

So,

  • if 1-SM-core was internally instructed to execute some GPU-task unit ( a GPU-job ), just this one SM-core will fetch one GPU-RISC-instruction after another ( ignoring any possible ILP for the simplicity here ) and execute it one at a time, stepping through the stream of SIMD-instructions of the said GPU-job. All the rest of the SM-cores present on the same SMX-unit typically do nothing during that time, until this GPU-job get finished and the internal GPU-process management system decides about mapping some other work for this SMX.

  • if 2-SM-cores were instructed to execute some GPU-job, just this pair of SM-cores will fetch one ( and the very same ) GPU-RISC-instruction after another ( ignoring any possible ILP for the simplicity here ) and both execute it one at a time, stepping through the stream of SIMT/SIMD-instructions of the said GPU-job. In this case, if one SM-core gets into a condition, where an if-ed, or similarly branched, flow of execution makes one SM-core into going into another code-execution-flow path than the other, the SIMT/SIMD-parallelism gets into divergent scenario, where one SM-core gets a next SIMT/SIMD-instruction, belonging to it's code-execution path, whereas the other one does nothing ( gets a GPU_NOP(s) ), until the first one finished the whole job ( or was enforced to stop at some synchronisation barrier of fell into an unmaskable latency wait-state, when waiting for a piece of data to get fetched from "far" ( slow ) non-local memory location, again, details go way beyond the scope of this post ) - only after any one of this happens, the divergent-path, so far just GPU_NOP-ed SM-core can receive any next SIMT/SIMD-instruction, belonging to its ( divergent ) code-execution-path to move any forward. All the rest of the SM-cores present on the same SMX-unit typically do nothing during that time, until this GPU-job get finished and the internal GPU-process management system decides about mapping some other work for this SMX.

  • if 16-SM-cores were instructed to execute some GPU-job by the task-specific "geometry", just this "herd" of SM-cores will fetch one ( and the very same ) GPU-RISC SIMT/SIMD-instruction after another ( ignoring any possible ILP for the simplicity here ) and all execute it one at a time, stepping through the stream of SIMT/SIMD-instructions of the said GPU-job. Any divergence inside the "herd" reduce the SIMT/SIMD-effect and GPU_NOP-blocked cores remain waiting for the main part of the "herd" to finish the job ( same as was sketched right above this point ).

  • if more-SIMT/SIMD-threads-than-SM-cores-available were instructed to execute some GPU-job by the task-specific "geometry", the GPU-device silicon will operate this to flow as [SERIAL]-sequence of as many { WARP-wide | half-WARP-wide }-SIMT/SIMD-thread packs, until such sequence finishes all the instructed number of SIMT/SIMD-threads mapped onto the SMX. Time-coherence of such pack uniform-finalisation is therefore principally impossible, as they arrive to their respective ends in a WARP-scheduler specific fashion, but never synchronously ( yes, your CPU-side code here will have to wait till the very last ( the laziest ( due-to whatever reason, be it a capacity constrained scheduling reason, or a code-divergence scheduling reason or a bad mutual (re-)synchronisation reason ) code-execution flow ) will eventually, in some unknown time in the future, finish the __kernel-code processing and the OpenCL-operated device will allow for "remote"-detection of CL_COMPLETE state, before being able to fetch any meaningful results ( as you ask in a surprise in one of your other questions ).

anyways, all the other SM-cores, not mapped by the task-specific "geometry" on the respective GPU-devices' SMX-unit will typically remain doing nothing useful at all - so the importance of knowing the hardware details for the proper task-specific "geometry" is indeed important and profiling may help to identify the peak performance for any such GPU-task constellation ( differences may range several orders of magnitude - from best to common to worse - among all possible task-specific "geometry" setups ).


Secondly, when I have many cores, how openCL is distributing the task, is it on each core same process and same data or is it different core with different data ?

As explained in brief above - the SIMT/SIMD-type device silicon-architecture does not permit any of the SMX SM-cores to execute anything other than the very same SIMT/SIMD-instruction on the whole "herd"-of-SM-cores, that was mapped by a task-"geometry" onto the SMX-unit ( not counting the GPU_NOP(s) as doing " something else " as it is just wasting CPU:GPU-system time ).

So, yes, " .. on each core same process .. " ( best if never divergent in its internal code-execution paths after if or while or any other kind of code-execution path branching ), so if algorithm, based on data-driven values results in different internal state, each core may have different thread-local-state, based on which the processing may differ ( as exemplified with if-driven divergent code-execution paths above ). More details on SM-local registers, SM-local caching, restricted shared-memory usage ( and latency costs ), GPU-device global-memory usage ( and latency costs and cache-line lengths and associativity for best coalescing access-patterns for latency masking options - many hardware-related + programming eco-system details go into small thousands of pages of hardware + software specific documentation and are well beyond the scope of this simplified for clarity post )

same data or is it different core with different data ?

This is the last, but not least, dilemma - any well parametrised GPU-kernel activation may also pass some amount of external-world data downto the GPU-kernel, which may make SMX thread-local data different from SM-core to SM-core. Mapping practices and best performance for doing this are principally device specific ( { SMX | SM-registers | GPU_GDDR gloMEM : shaMEM : constMEM | GPU SMX-local cache-hierarchy }-details and capacities

  ...
 +---------------------------------------------------------
  ...                                               901 MHz
  Cache type:                            Read/Write
  Cache line size:                     128
  Cache size:                        32768
  Global memory size:           4294967296
  Constant buffer size:              65536
  Max number of constant args:           9
  Local memory size:                 49152
 +---------------------------------------------------------
  ...                                              4000 MHz
  Cache type:                            Read/Write
  Cache line size:                      64
  Cache size:                       262144
  Global memory size:            536838144
  Constant buffer size:             131072
  Max number of constant args:         480
  Local memory size:                 32768
 +---------------------------------------------------------
  ...                                              1300 MHz
  Cache type:                            Read/Write
  Cache line size:                      64
  Cache size:                       262144
  Global memory size:           1561123226
  Constant buffer size:              65536
  Max number of constant args:           8
  Local memory size:                 65536
 +---------------------------------------------------------
  ...                                              4000 MHz
  Cache type:                            Read/Write
  Cache line size:                      64
  Cache size:                       262144
  Global memory size:           2147352576
  Constant buffer size:             131072
  Max number of constant args:         480
  Local memory size:                 32768

are principally so different device to device, that each high-performance code project principally can but profile its respective GPU-device task-"geometry and resources-usage maps composition for actual deployment device. What may work faster on one GPU-device / GPU-drives stack, need not work as smart on another one ( or after GPU-driver + exo-programming eco-system update / upgrade ), simply only the real-life benchmark will tell ( as theory could be easily printed, but hardly as easily executed, as many device-specific and workload-injected limitations will apply in real-life deployment ).


suggest me the way out for distributing the function with different values or frames in MQL5 using GPU on OpenCL.

The honest and best suggestions is the very same as it was presented to you already on April-2nd.

Do not attempt to block / delay a flow-of-execution of any MQL5 CustomIndicator-type of code-execution-unit with any extensive-latency / async / blocking - code. Never, until MetaTrader Terminal platform documentation will explicitly remove such warnings ( present still in 2018/Q2 there ) and will explicitly advice on techniques using latency-avoided non-blocking distributed agents communication tools for coordinated (almost)-synchronous exchange of processing data/results between MQL5-side and the GPU-device-side ( which will not be available any soon, due to SIMT/SIMD nature of the Out-of-Order scheduling of the GPU-jobs in the contemporary classes of GPU-devices available.

This was documented for a natural flow-of-time, strobed by a flow of an external FX Market ( Broker-broadcast propagated ) Events, having about a few hundreds of [us] Event-to-Event cadence.

If going into the synthetic flow-of-time, as is orchestrated in the Terminal's [ Strategy Tester ] simulator eco-system, the problem documented above goes many orders of magnitude worse, as the simulator actually accelerates the flow-of-time / cadency and anything not capable of keeping pace will ( again ) block any speedup ( which was already bad in a natural pace of the flow-of-time above ). So, no, this is a very bad direction to invest in a single next bit of efforts ( again, at least until both platforms will have changed their architectural limits ).


... so that my process become faster ...

This part of the problem-definition has been decided already ~ 60 years back, by Dr. Gene AMDAHL.

The Law of Diminishing Returns and its contemporary Criticism and re-formulation

His ( then simplified ) Law of Diminishing Returns explains WHY a principal ceiling of any process speedup is linked to the still [SERIAL] part, given a distinction between a pure-[SERIAL] part and a potentially N-(CPU)-times true-[PARALLEL] parts are clearly identified.

This helps pre-estimate a cost / benefit effect of process re-engineering.

So, here, your GPU-kernel-code is the sort of (almost)-[PARALLEL] processing part. All the rest is still a pure-[SERIAL] processing part.

This suffice to guess the limits of the effect of trying to go into OpenCL-wrapped process re-design.


But, the Devil is hidden in detail ...

The real costs are way higher.

Guess the percentage + add the add-on overhead costs ...

  • The [SERIAL]-part will never get faster per-se.
  • The [SERIAL]-part will actually get "slower" and "extended", as there will be many more steps to execute, before the first SIMT/SIMD-instruction of the payload(s) ... being "remotely" delivered onto the OpenCL-Queue + OpenCL-Data-Transfer(s) + OpenCL-Queue Task Management waiting... + OpenCL-Queue TaskManagement submission onto device ... will even start to get executed + the task == the intended OpenCL-Device WARP-scheduled / SIMT/SIMD-execution + all the way back, from the remote circus --- OpenCL-Device task-completion overheads + MQL5-side async completion detection async add-on latencies + OpenCL-Data-Transfer(s)
  • The [PARALLEL]-part will get executed only "after" or "at" all the add-on costs were accrued ( not depicted in the Figure above, due to a need to avoid making it too complex and harder to comprehend the limit of the theoretical, overhead ignoring, speedup ( not- )scaling ), yet even worse, as getting executed at only about ~ 4x lower GPU_CLOCK-rate ( not mentioning ~ 10x ~ 1000x slower access-latency times to memory and cache ), and as the there teleported algorithm still remains a [SERIAL]-only, linearly-convoluted TimeSeries data-processing, thus cannot have but adverse net effect of << 1.0 improvement factor on theoretical processing speedup ( the achieved resulting performance gets worse than without such an attempt to "improve" ).

For a full reference of these net-effects, kindly read the section on Criticism, where both Overhead-strict re-formulation of the Amdahl's Law speedup and Overhead-strict and resources-aware re-formulation were more detailed :


               1
S =  __________________________; where s, ( 1 - s ), N were defined above
                ( 1 - s )            pSO:= [PAR]-Setup-Overhead     add-on
     s  + pSO + _________ + pTO      pTO:= [PAR]-Terminate-Overhead add-on
                    N

                           1                         where s, ( 1 - s ), N
S =  ______________________________________________ ;      pSO, pTO
                    / ( 1 - s )           \                were defined above
     s  + pSO + max|  _________ , atomicP  |  + pTO        atomicP:= further indivisible duration of atomic-process-block
                    \     N               /

The header graph, cited on top of this post, provides a link towards a live-GUI with interactive inputs and animated outputs, where one may test impacts of values for p == ( 1 - s ) going anywhere under 1.00 ( which is a just theoretical, absolutely 100% [PARALLEL] schedule ( which is technically impossible in any real-world scenario ) ) and also tweak impacts of all add-on overheads in o ( expressed as just a scalar fraction for simplicity reasons ) over an editable range of ~ < 0.0 ~ 0.0001 > values, so as to better sense the principal limits of real-world behaviour of many-core devices and become able to make better engineering decisions before even thinking about any coding steps.


And given the known ( easily measurable downto a single [us]-resolution on the MQL5-side of the code-execution, using a call to GetMicrosecondCount() ) values for add-on overheads and atomicity-of-processing -- pSO, pTO, atomicP -- the net-effect of trying to continue towards OpenCL-wrapped Simple Moving Average as was sketched in the GPU-kernel-code :

kernel void SMA_executeSMA(          float  ExtLineBufferi_1,
                                     float  price1,
                                     float  price2,
                                     int    InpMAPeriod,
                            __global float *output
                            )
{                                                  // 1: .STO 0x0001, REG
   int len = get_global_id( 1 );                   // 2: .JMP intrinsic_OpenCL_fun(), ... may get masked by reading a hardwired-const-ID#
                                                   // 3: .GET len, REG
   output[len] =                                   // 4: .STO MEM[*],
                 ExtLineBufferi_1                  // 5:     .ADD const,
               + ( price1 - price2 )               //             ( .SUB const, const
                 / InpMAPeriod;                    //               .FDIV REG, const )
}                                                  // 6: .RET

which has nothing but a few 900 MHz-clocked instructions - i.e. the p = ( 1 - s )-factor in the animated graph-visualisation will go somewhere close to p == 0 end, making the game ultimately dominated by the pure-[SERIAL]-part of the CPU:GPU-composition of the distributed-computing system -- ( ~ a few, max small tens of [ns] + naked ( non-maskable, as having zero-re-use here ) on-GPU-device memory access-latency ~ 350 - 700+ [ns] ).

Having such a low p is a performance-tweaking bad-sign ( if not an ANTI-PATTERN ) for any attempts of doing this.

Because even if going into N-(CPUs) ~ +INF, it will still never make the wished-for speedup ( ref.: may try to modify such factors in the interactive graph offered above and visually see the effect -- how low the numbers there will get ) - while the same could have been computed in almost less than ~ 0.5 [ns], further still vectorise-able, CPU instructions, here also having zero-add-on costs at all ).

These are the "economy-of-costs" reasons
( besides the principal MQL5 one ) WHY better not doing this

that will never pay back the sum of all the [SERIAL] add-on costs, introduced during the whole OpenCL-re-wrapping-there-sending-there-calc'd-and-after-detected-back-sending circus on the CPU-code / MQL5-side ( all in the name of making not more than just these indeed very few GPU_INSTR-s to happen ), that were just briefly mentioned above, even if an infinite number of GPU-cores were used.

You simply still try to pay way more than one will ever receive back.

like image 153
user3666197 Avatar answered Oct 22 '22 00:10

user3666197