ScalerByLeo/Src/Scaler/Bench/CLPattern.mq5

445 lines
15 KiB
MQL5

2026-09-16 18:20:12 -05:00
//+------------------------------------------------------------------+
//| BenchSubrangeAB_AllInOne.mq5 |
//| Foco: SOLO el caso interesante -> m_reconstruir=true con subrango |
//| real (fs != total_cols), comparando: |
//| CPU (patron validado: out=X + overwrite del rango) |
//| GPU-A (mandar TODO + offsets, leer completo, sin reconstruccion |
//| manual porque el resto nunca se toco) |
//| GPU-B (mandar SOLO el rango, reconstruir con out=X + overwrite) |
//| |
//| El caso fs==total_cols y el caso !reconstruir NO se benchmarkean |
//| aca a proposito: ya sabemos que van por la ruta trivial (0 copias |
//| intermedias, sin ambiguedad de estrategia). |
//+------------------------------------------------------------------+
#property script_show_inputs
#ifndef SCALERBYLEO_CL_DEVICE
#define SCALERBYLEO_CL_DEVICE (CL_USE_GPU_ONLY)
#endif
//+------------------------------------------------------------------+
static const string MinMaxKernelSrcTemplate =
"#define MATRIX_IDX(row, col, cols) ((row) * (cols) + (col)) \r\n"
"\r\n"
"__kernel void max_min_scaler_transform_matrix( \r\n"
" __global const real_t* min_values, \r\n"
" __global const real_t* diff_inv, \r\n"
" __global real_t* matrix, \r\n"
" const uint cols, \r\n"
" const uint istart \r\n"
") \r\n"
"{ \r\n"
" const uint row = get_global_id(0); \r\n"
" const uint col = get_global_id(1); \r\n"
" const uint matrix_idx = MATRIX_IDX(row, istart + col, cols); \r\n"
" matrix[matrix_idx] = (matrix[matrix_idx] - min_values[col]) * diff_inv[col]; \r\n"
"} \r\n"
"\r\n"
"__kernel void max_min_scaler_transform_matrix_exact( \r\n"
" __global const real_t* min_values, \r\n"
" __global const real_t* diff_inv, \r\n"
" __global real_t* matrix, \r\n"
" const uint cols \r\n"
") \r\n"
"{ \r\n"
" const uint row = get_global_id(0); \r\n"
" const uint col = get_global_id(1); \r\n"
" const uint matrix_idx = MATRIX_IDX(row, col, cols); \r\n"
" matrix[matrix_idx] = (matrix[matrix_idx] - min_values[col]) * diff_inv[col]; \r\n"
"} \r\n";
//+------------------------------------------------------------------+
class CCLCtxShared
{
private:
static int s_ref_count;
public:
static int s_context;
static bool ContextInit(void)
{
if(s_context != INVALID_HANDLE) { s_ref_count++; return true; }
::ResetLastError();
if((s_context = CLContextCreate(SCALERBYLEO_CL_DEVICE)) == INVALID_HANDLE)
return false;
s_ref_count = 1;
return true;
}
static void ContextRelease(void)
{
if(s_context == INVALID_HANDLE) return;
if(--s_ref_count <= 0)
{
CLContextFree(s_context);
s_context = INVALID_HANDLE;
s_ref_count = 0;
}
}
};
int CCLCtxShared::s_context = INVALID_HANDLE;
int CCLCtxShared::s_ref_count = 0;
//+------------------------------------------------------------------+
template<typename TFlt>
class CCLProgramManager
{
public:
static int s_program;
static int s_kernel_full;
static int s_kernel_exact;
static bool s_ready;
static bool Init(void)
{
if(s_ready) return true;
if(!CCLCtxShared::ContextInit()) return false;
const string real_t_def = (sizeof(TFlt) == sizeof(float))
? "#define real_t float \r\n"
: "#define real_t double \r\n";
const string src = real_t_def + MinMaxKernelSrcTemplate;
string build_log;
::ResetLastError();
if((s_program = CLProgramCreate(CCLCtxShared::s_context, src, build_log)) == INVALID_HANDLE)
{
Print("CLProgramCreate failed: ", build_log, " err=", GetLastError());
CCLCtxShared::ContextRelease();
return false;
}
if((s_kernel_full = CLKernelCreate(s_program, "max_min_scaler_transform_matrix")) == INVALID_HANDLE)
{
Print("CLKernelCreate(full) failed, err=", GetLastError());
return false;
}
if((s_kernel_exact = CLKernelCreate(s_program, "max_min_scaler_transform_matrix_exact")) == INVALID_HANDLE)
{
Print("CLKernelCreate(exact) failed, err=", GetLastError());
return false;
}
s_ready = true;
return true;
}
static void Deinit(void)
{
if(!s_ready) return;
if(s_kernel_full != INVALID_HANDLE) { CLKernelFree(s_kernel_full); s_kernel_full = INVALID_HANDLE; }
if(s_kernel_exact != INVALID_HANDLE) { CLKernelFree(s_kernel_exact); s_kernel_exact = INVALID_HANDLE; }
if(s_program != INVALID_HANDLE) { CLProgramFree(s_program); s_program = INVALID_HANDLE; }
CCLCtxShared::ContextRelease();
s_ready = false;
}
};
template<typename TFlt> int CCLProgramManager::s_program = INVALID_HANDLE;
template<typename TFlt> int CCLProgramManager::s_kernel_full = INVALID_HANDLE;
template<typename TFlt> int CCLProgramManager::s_kernel_exact = INVALID_HANDLE;
template<typename TFlt> bool CCLProgramManager::s_ready = false;
//+------------------------------------------------------------------+
//| MinMax scaler: ruta trivial + dos estrategias para subrango real |
//+------------------------------------------------------------------+
template<typename TFlt>
class MaxMinScalerCL
{
private:
bool m_loaded_scaler;
ulong m_i_start;
vector<TFlt> m_min_vals;
vector<TFlt> m_diff_inv;
int m_buffer_h_min_vals;
int m_buffer_h_diff_inv;
bool EnsureAuxBuffers(void)
{
const uint bytes = uint(m_min_vals.Size() * sizeof(TFlt));
if(m_buffer_h_min_vals == INVALID_HANDLE)
if((m_buffer_h_min_vals = CLBufferCreate(CCLCtxShared::s_context, bytes, CL_MEM_READ_ONLY)) == INVALID_HANDLE)
return false;
if(!CLBufferWrite(m_buffer_h_min_vals, 0, m_min_vals)) return false;
if(m_buffer_h_diff_inv == INVALID_HANDLE)
if((m_buffer_h_diff_inv = CLBufferCreate(CCLCtxShared::s_context, bytes, CL_MEM_READ_ONLY)) == INVALID_HANDLE)
return false;
if(!CLBufferWrite(m_buffer_h_diff_inv, 0, m_diff_inv)) return false;
return true;
}
public:
MaxMinScalerCL(void) : m_loaded_scaler(false), m_i_start(0),
m_buffer_h_min_vals(INVALID_HANDLE), m_buffer_h_diff_inv(INVALID_HANDLE) {}
~MaxMinScalerCL(void)
{
if(m_buffer_h_min_vals != INVALID_HANDLE) CLBufferFree(m_buffer_h_min_vals);
if(m_buffer_h_diff_inv != INVALID_HANDLE) CLBufferFree(m_buffer_h_diff_inv);
}
bool Fit(const matrix<TFlt> &X, const ulong i_start, const ulong fs)
{
if(!CCLProgramManager<TFlt>::Init()) return false;
m_i_start = i_start;
m_min_vals.Resize(fs);
m_diff_inv.Resize(fs);
for(ulong c = 0; c < fs; c++)
{
TFlt mn = X[0][i_start + c];
TFlt mx = X[0][i_start + c];
for(ulong r = 1; r < X.Rows(); r++)
{
const TFlt v = X[r][i_start + c];
if(v < mn) mn = v;
if(v > mx) mx = v;
}
const TFlt diff = (mx - mn);
m_min_vals[c] = mn;
m_diff_inv[c] = (diff == 0) ? TFlt(0) : TFlt(1.0 / double(diff));
}
m_loaded_scaler = true;
return EnsureAuxBuffers();
}
//--- SUBRANGO ESTRATEGIA A: mandar TODO + offsets, leer completo directo (0 copias CPU extra)
bool SubrangeStrategyA(matrix<TFlt> &X, matrix<TFlt> &out)
{
if(!m_loaded_scaler) return false;
const ulong rows = X.Rows();
const ulong total_cols = X.Cols();
const ulong fs = m_min_vals.Size();
int matrix_h = INVALID_HANDLE;
const uint bytes = uint(sizeof(TFlt) * total_cols * rows);
matrix_h = CLBufferCreate(CCLCtxShared::s_context, bytes, CL_MEM_READ_WRITE);
if(matrix_h == INVALID_HANDLE) return false;
bool ok = CLBufferWrite(matrix_h, 0, X);
if(ok)
ok = CLSetKernelArgMem(CCLProgramManager<TFlt>::s_kernel_full, 0, m_buffer_h_min_vals) &&
CLSetKernelArgMem(CCLProgramManager<TFlt>::s_kernel_full, 1, m_buffer_h_diff_inv) &&
CLSetKernelArgMem(CCLProgramManager<TFlt>::s_kernel_full, 2, matrix_h) &&
CLSetKernelArg(CCLProgramManager<TFlt>::s_kernel_full, 3, uint(fs)) &&
CLSetKernelArg(CCLProgramManager<TFlt>::s_kernel_full, 4, uint(m_i_start));
if(ok)
{
const uint work_off[2] = {0, 0};
uint work_size[2] = { (uint)rows, (uint)fs };
ok = CLExecute(CCLProgramManager<TFlt>::s_kernel_full, 2, work_off, work_size);
}
if(ok)
{
out.Resize(rows, total_cols);
ok = CLBufferRead(matrix_h, 0, out);
}
CLBufferFree(matrix_h);
return ok;
}
//--- SUBRANGO ESTRATEGIA B: mandar SOLO el rango, reconstruir con out=X + overwrite
bool SubrangeStrategyB(matrix<TFlt> &X, matrix<TFlt> &out)
{
if(!m_loaded_scaler) return false;
const ulong rows = X.Rows();
const ulong fs = m_min_vals.Size();
matrix<TFlt> narrow;
narrow.Resize(rows, fs);
for(ulong c = 0; c < fs; c++)
for(ulong r = 0; r < rows; r++)
narrow[r][c] = X[r][m_i_start + c];
int matrix_h = INVALID_HANDLE;
const uint bytes = uint(sizeof(TFlt) * fs * rows);
matrix_h = CLBufferCreate(CCLCtxShared::s_context, bytes, CL_MEM_READ_WRITE);
bool ok = (matrix_h != INVALID_HANDLE) && CLBufferWrite(matrix_h, 0, narrow);
if(ok)
ok = CLSetKernelArgMem(CCLProgramManager<TFlt>::s_kernel_exact, 0, m_buffer_h_min_vals) &&
CLSetKernelArgMem(CCLProgramManager<TFlt>::s_kernel_exact, 1, m_buffer_h_diff_inv) &&
CLSetKernelArgMem(CCLProgramManager<TFlt>::s_kernel_exact, 2, matrix_h) &&
CLSetKernelArg(CCLProgramManager<TFlt>::s_kernel_exact, 3, uint(fs));
if(ok)
{
const uint work_off[2] = {0, 0};
uint work_size[2] = { (uint)rows, (uint)fs };
ok = CLExecute(CCLProgramManager<TFlt>::s_kernel_exact, 2, work_off, work_size);
}
if(!ok) { CLBufferFree(matrix_h); return false; }
matrix<TFlt> res;
res.Resize(rows, fs);
ok = CLBufferRead(matrix_h, 0, res);
CLBufferFree(matrix_h);
if(!ok) return false;
out = X;
for(ulong c = 0; c < fs; c++)
for(ulong r = 0; r < rows; r++)
out[r][m_i_start + c] = res[r][c];
return true;
}
};
//+------------------------------------------------------------------+
//| Version CPU pura (patron validado: out=X + overwrite del rango) |
//+------------------------------------------------------------------+
template<typename TFlt>
class MaxMinScalerCPU
{
private:
ulong m_i_start;
vector<TFlt> m_min_vals;
vector<TFlt> m_diff_inv;
public:
MaxMinScalerCPU(void) : m_i_start(0) {}
bool Fit(const matrix<TFlt> &X, const ulong i_start, const ulong fs)
{
m_i_start = i_start;
m_min_vals.Resize(fs);
m_diff_inv.Resize(fs);
for(ulong c = 0; c < fs; c++)
{
TFlt mn = X[0][i_start + c];
TFlt mx = X[0][i_start + c];
for(ulong r = 1; r < X.Rows(); r++)
{
const TFlt v = X[r][i_start + c];
if(v < mn) mn = v;
if(v > mx) mx = v;
}
const TFlt diff = (mx - mn);
m_min_vals[c] = mn;
m_diff_inv[c] = (diff == 0) ? TFlt(0) : TFlt(1.0 / double(diff));
}
return true;
}
bool Transform(const matrix<TFlt> &X, matrix<TFlt> &out)
{
const ulong rows = X.Rows();
const ulong fs = m_min_vals.Size();
out = X;
for(ulong c = 0; c < fs; c++)
for(ulong r = 0; r < rows; r++)
out[r][m_i_start + c] = TFlt((double(X[r][m_i_start + c]) - double(m_min_vals[c])) * double(m_diff_inv[c]));
return true;
}
};
//+------------------------------------------------------------------+
//| Config del barrido: SOLO subrango real, fracciones chicas a 90% |
//+------------------------------------------------------------------+
int g_sizes[] = {500, 2000, 5000, 9600};
double g_fs_fracs[] = {0.01, 0.05, 0.10, 0.25, 0.50, 0.75, 0.90};
int g_repeats = 5;
//+------------------------------------------------------------------+
ulong RunOnce_CPU(matrix<float> &X, const ulong i_start, const ulong fs)
{
MaxMinScalerCPU<float> sc;
sc.Fit(X, i_start, fs);
matrix<float> out;
const ulong t0 = GetMicrosecondCount();
sc.Transform(X, out);
return GetMicrosecondCount() - t0;
}
//+------------------------------------------------------------------+
ulong RunOnce_A(matrix<float> &X, const ulong i_start, const ulong fs)
{
MaxMinScalerCL<float> sc;
sc.Fit(X, i_start, fs);
matrix<float> out;
const ulong t0 = GetMicrosecondCount();
sc.SubrangeStrategyA(X, out);
return GetMicrosecondCount() - t0;
}
//+------------------------------------------------------------------+
ulong RunOnce_B(matrix<float> &X, const ulong i_start, const ulong fs)
{
MaxMinScalerCL<float> sc;
sc.Fit(X, i_start, fs);
matrix<float> out;
const ulong t0 = GetMicrosecondCount();
sc.SubrangeStrategyB(X, out);
return GetMicrosecondCount() - t0;
}
//+------------------------------------------------------------------+
ulong Median(ulong &arr[])
{
const int n = ArraySize(arr);
ArraySort(arr);
return arr[n / 2];
}
//+------------------------------------------------------------------+
void OnStart()
{
Print("=== Bench SUBRANGO REAL (reconstruir=true, fs != total_cols): CPU vs GPU-A vs GPU-B ===");
PrintFormat("%-6s %-6s %-6s %-12s %-12s %-12s %-10s %-10s",
"size", "fs", "frac", "CPU(us)", "GPU_A(us)", "GPU_B(us)", "A_vs_CPU", "B_vs_CPU");
for(int si = 0; si < ArraySize(g_sizes); si++)
{
const int size = g_sizes[si];
matrix<float> X;
X.Resize(size, size);
X.Random(0.0, 20.0);
for(int fi = 0; fi < ArraySize(g_fs_fracs); fi++)
{
ulong fs = (ulong)MathMax(1, MathRound(size * g_fs_fracs[fi]));
if(fs >= (ulong)size) fs = size - 1; // forzamos subrango real, nunca fs==total_cols aca
const ulong i_start = 0;
ulong cpu_t[], a_t[], b_t[];
ArrayResize(cpu_t, g_repeats);
ArrayResize(a_t, g_repeats);
ArrayResize(b_t, g_repeats);
for(int rep = 0; rep < g_repeats; rep++)
{
cpu_t[rep] = RunOnce_CPU(X, i_start, fs);
a_t[rep] = RunOnce_A(X, i_start, fs);
b_t[rep] = RunOnce_B(X, i_start, fs);
}
const ulong cpu_med = Median(cpu_t);
const ulong a_med = Median(a_t);
const ulong b_med = Median(b_t);
const double a_ratio = (a_med > 0) ? (double)cpu_med / (double)a_med : 0.0;
const double b_ratio = (b_med > 0) ? (double)cpu_med / (double)b_med : 0.0;
PrintFormat("%-6d %-6d %-6.2f %-12I64u %-12I64u %-12I64u %-10.2f %-10.2f",
size, fs, g_fs_fracs[fi], cpu_med, a_med, b_med, a_ratio, b_ratio);
}
}
Print("=== Fin ===");
Print("Ratio > 1.0 = GPU mas rapida que CPU. Comparar tambien GPU_A vs GPU_B directamente en la tabla.");
CCLProgramManager<float>::Deinit();
}
//+------------------------------------------------------------------+