265 lines
9.4 KiB
MQL5
265 lines
9.4 KiB
MQL5
//+------------------------------------------------------------------+
| |||
//| Base64Bench.mq5 |
| |||
//| Benchmark: CryptEncode chunked vs CryptEncode bulk vs manual b64 |
| |||
//+------------------------------------------------------------------+
| |||
#property script_show_inputs
| |||
#property strict
| |||
| |||
input int InpKeyBits = 2048; // simula tamaño de clave RSA (2048/4096)
| |||
input int InpIterations = 10000; // repeticiones por variante (para promediar)
| |||
| |||
//--- tabla de lookup Base64 estándar, precomputada como const global
| |||
// (se resuelve una sola vez al cargar el módulo, no en cada llamada)
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Genera un buffer "raw" de tamaño típico de un DER de clave RSA |
| |||
//| (aproximado, solo para el benchmark; el contenido no importa) |
| |||
//+------------------------------------------------------------------+
| |||
void GenerateFakeRaw(uchar &raw[], int keyBits)
| |||
{
| |||
// Aproximación: un DER de SubjectPublicKeyInfo para RSA ronda
| |||
// ~1.15-1.3x el tamaño de la clave en bytes, dependiendo del exponente
| |||
// y overhead ASN.1. Usamos una estimación conservadora.
| |||
int approxLen = (keyBits / 8) + 300; // overhead ASN.1 + headers
| |||
ArrayResize(raw, approxLen);
| |||
for(int i = 0; i < approxLen; i++)
| |||
raw[i] = (uchar)((i * 37 + 11) % 256); // datos pseudo-aleatorios deterministas
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| VARIANTE A: CryptEncode en chunks de 48 bytes -> 64 chars c/u |
| |||
//| Escribe directo a buf con \r\n inline, sin post-proceso |
| |||
//+------------------------------------------------------------------+
| |||
int EncodeB64_ChunkedCryptEncode(const uchar &raw[], uchar &buf[])
| |||
{
| |||
int rawLen = ArraySize(raw);
| |||
int chunk = 48;
| |||
int numChunks = (int)MathCeil((double)rawLen / chunk);
| |||
| |||
// reserva pesimista: 64 chars + 2 (\r\n) por chunk, + margen final
| |||
int maxOut = numChunks * (64 + 2) + 8;
| |||
if(ArraySize(buf) < maxOut) ArrayResize(buf, maxOut);
| |||
| |||
uchar tempIn[48];
| |||
// ArrayResize(tempIn, chunk); // reservado UNA sola vez, al tamaño máximo de chunk
| |||
uchar tempOut[]; // CryptEncode lo redimensiona el solo
| |||
| |||
int w = 0;
| |||
int pos = 0;
| |||
| |||
// --- loop "caliente": procesa en bloques de 48 bytes mientras alcance ---
| |||
// 48 es múltiplo de 3 => cada chunk da exactamente 64 chars, SIN '=' intermedio
| |||
for(; pos + chunk <= rawLen; pos += chunk)
| |||
{
| |||
ArrayCopy(tempIn, raw, 0, pos, chunk); // copia #1, siempre 48 bytes
| |||
int outLen = CryptEncode(CRYPT_BASE64, tempIn, EMPTY_BUFFER_ph, tempOut);
| |||
if(outLen <= 0) { Print("CryptEncode error: ", GetLastError()); return -1; }
| |||
| |||
w += ArrayCopy(buf, tempOut, w, 0, outLen); // copia #2, directo a posición
| |||
buf[w++] = '\r';
| |||
buf[w++] = '\n';
| |||
}
| |||
| |||
// --- resto final: 0..47 bytes ---
| |||
int remaining = rawLen - pos;
| |||
if(remaining > 0)
| |||
{
| |||
// de ese resto, la parte múltiplo de 3 tampoco lleva '=' y puede
| |||
// seguir tratándose en el mismo estilo "limpio"; solo el remanente
| |||
// de 1 o 2 bytes al final del TODO lleva padding.
| |||
int cleanPart = remaining - (remaining % 3); // 0, 3, 6, ... hasta 45
| |||
if(cleanPart > 0)
| |||
{
| |||
uchar cleanChunk[];
| |||
ArrayResize(cleanChunk, cleanPart);
| |||
ArrayCopy(cleanChunk, raw, 0, pos, cleanPart);
| |||
int outLen = CryptEncode(CRYPT_BASE64, cleanChunk, EMPTY_BUFFER_ph, tempOut);
| |||
if(outLen <= 0) { Print("CryptEncode error: ", GetLastError()); return -1; }
| |||
w += ArrayCopy(buf, tempOut, w, 0, outLen);
| |||
pos += cleanPart;
| |||
}
| |||
| |||
int tail = rawLen - pos; // 0, 1 o 2 bytes: el ÚNICO tramo que puede llevar '='
| |||
if(tail > 0)
| |||
{
| |||
uchar tailChunk[];
| |||
ArrayResize(tailChunk, tail);
| |||
ArrayCopy(tailChunk, raw, 0, pos, tail);
| |||
int outLen = CryptEncode(CRYPT_BASE64, tailChunk, EMPTY_BUFFER_ph, tempOut);
| |||
if(outLen <= 0) { Print("CryptEncode error: ", GetLastError()); return -1; }
| |||
w += ArrayCopy(buf, tempOut, w, 0, outLen);
| |||
}
| |||
| |||
buf[w++] = '\r';
| |||
buf[w++] = '\n';
| |||
}
| |||
ArrayResize(buf, w);
| |||
return w;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| VARIANTE B: CryptEncode de una sola vez sobre todo el buffer, |
| |||
//| luego post-proceso insertando \r\n cada 64 chars (copia extra) |
| |||
//+------------------------------------------------------------------+
| |||
int EncodeB64_BulkThenSplit(const uchar &raw[], uchar &buf[])
| |||
{
| |||
uchar fullB64[];
| |||
int fullLen = CryptEncode(CRYPT_BASE64, raw, fullB64, fullB64);
| |||
if(fullLen <= 0) { Print("CryptEncode error: ", GetLastError()); return -1; }
| |||
| |||
int lines = (fullLen+63)>>6; // ceil
| |||
int maxOut = fullLen + lines * 2;
| |||
if(ArraySize(buf) < maxOut) ArrayResize(buf, maxOut);
| |||
| |||
int w = 0;
| |||
int pos=0;
| |||
while(fullLen - pos >= 64)
| |||
{
| |||
w += ArrayCopy(buf, fullB64, w, pos, 64); // copia extra #3 (por línea)
| |||
buf[w++] = '\r';
| |||
buf[w++] = '\n';
| |||
pos+=64;
| |||
}
| |||
if(pos<fullLen)
| |||
{
| |||
int n = fullLen - pos;
| |||
w += ArrayCopy(buf, fullB64, w, pos, n); // copia extra #3 (por línea)
| |||
buf[w++] = '\r';
| |||
buf[w++] = '\n';
| |||
}
| |||
| |||
//ArrayResize(buf, w);
| |||
return w;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| VARIANTE C: Base64 manual en MQL5 puro, tabla precomputada, |
| |||
//| sin arrays temporales, escribiendo directo a buf con \r\n inline |
| |||
//+------------------------------------------------------------------+
| |||
int EncodeB64_Manual(const uchar &raw[], uchar &buf[])
| |||
{
| |||
int rawLen = ArraySize(raw);
| |||
int fullLen = ((rawLen + 2) / 3) << 2;
| |||
| |||
int lines = (fullLen+63)>>6; // ceil
| |||
int maxOut = fullLen + lines * 2;
| |||
if(ArraySize(buf) < maxOut) ArrayResize(buf, maxOut);
| |||
| |||
const uchar B64_TABLE[64] =
| |||
{
| |||
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
| |||
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
| |||
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
| |||
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
| |||
};
| |||
| |||
| |||
int w = 0;
| |||
int col = 0; // columna actual dentro de la línea de 64
| |||
| |||
int i = 0;
| |||
// procesamos de a 3 bytes -> 4 chars, sin copiar a ningun array intermedio
| |||
for(; i + 3 <= rawLen; i += 3)
| |||
{
| |||
uchar b0 = raw[i], b1 = raw[i+1], b2 = raw[i+2];
| |||
| |||
buf[w++] = B64_TABLE[b0 >> 2];
| |||
buf[w++] = B64_TABLE[((b0 & 0x03) << 4) | (b1 >> 4)];
| |||
buf[w++] = B64_TABLE[((b1 & 0x0F) << 2) | (b2 >> 6)];
| |||
buf[w++] = B64_TABLE[b2 & 0x3F];
| |||
| |||
col += 4;
| |||
if(col >= 64)
| |||
{
| |||
buf[w++] = '\r';
| |||
buf[w++] = '\n';
| |||
col = 0;
| |||
}
| |||
}
| |||
| |||
// resto (0, 1 o 2 bytes) con padding '='
| |||
int rem = rawLen - i;
| |||
if(rem == 1)
| |||
{
| |||
uchar b0 = raw[i];
| |||
buf[w++] = B64_TABLE[b0 >> 2];
| |||
buf[w++] = B64_TABLE[(b0 & 0x03) << 4];
| |||
buf[w++] = '=';
| |||
buf[w++] = '=';
| |||
col += 4;
| |||
}
| |||
else if(rem == 2)
| |||
{
| |||
uchar b0 = raw[i], b1 = raw[i+1];
| |||
buf[w++] = B64_TABLE[b0 >> 2];
| |||
buf[w++] = B64_TABLE[((b0 & 0x03) << 4) | (b1 >> 4)];
| |||
buf[w++] = B64_TABLE[(b1 & 0x0F) << 2];
| |||
buf[w++] = '=';
| |||
col += 4;
| |||
}
| |||
| |||
if(col > 0)
| |||
{
| |||
buf[w++] = '\r';
| |||
buf[w++] = '\n';
| |||
}
| |||
| |||
//ArrayResize(buf, w);
| |||
return w;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| Script start |
| |||
//+------------------------------------------------------------------+
| |||
uchar EMPTY_BUFFER_ph[]; // placeholder de key vacia para CryptEncode BASE64
| |||
| |||
void OnStart()
| |||
{
| |||
uchar raw[];
| |||
GenerateFakeRaw(raw, InpKeyBits);
| |||
Print("Tamaño del buffer raw simulado: ", ArraySize(raw), " bytes (key=", InpKeyBits, " bits)");
| |||
| |||
uchar bufA[], bufB[], bufC[];
| |||
ulong t0, t1;
| |||
long accA = 0, accB = 0, accC = 0;
| |||
int lenA = 0, lenB = 0, lenC = 0;
| |||
| |||
// warm-up (evitar medir costos de primera carga/JIT)
| |||
EncodeB64_ChunkedCryptEncode(raw, bufA);
| |||
EncodeB64_BulkThenSplit(raw, bufB);
| |||
EncodeB64_Manual(raw, bufC);
| |||
| |||
for(int i = 0; i < InpIterations; i++)
| |||
{
| |||
t0 = GetMicrosecondCount();
| |||
lenA = EncodeB64_ChunkedCryptEncode(raw, bufA);
| |||
t1 = GetMicrosecondCount();
| |||
accA += (long)(t1 - t0);
| |||
| |||
t0 = GetMicrosecondCount();
| |||
lenB = EncodeB64_BulkThenSplit(raw, bufB);
| |||
t1 = GetMicrosecondCount();
| |||
accB += (long)(t1 - t0);
| |||
| |||
t0 = GetMicrosecondCount();
| |||
lenC = EncodeB64_Manual(raw, bufC);
| |||
t1 = GetMicrosecondCount();
| |||
accC += (long)(t1 - t0);
| |||
}
| |||
| |||
Print("=== Resultados (", InpIterations, " iteraciones) ===");
| |||
PrintFormat("A) CryptEncode chunked (48B/call): total=%d us | avg=%.3f us | outLen=%d",
| |||
accA, (double)accA / InpIterations, lenA);
| |||
PrintFormat("B) CryptEncode bulk + split: total=%d us | avg=%.3f us | outLen=%d",
| |||
accB, (double)accB / InpIterations, lenB);
| |||
PrintFormat("C) Manual B64 (tabla, sin temp): total=%d us | avg=%.3f us | outLen=%d",
| |||
accC, (double)accC / InpIterations, lenC);
| |||
| |||
// Verificación de correctitud: A, B y C deben producir el mismo output
| |||
bool aEqB = (lenA == lenB);
| |||
bool aEqC = (lenA == lenC);
| |||
if(aEqB) { for(int i=0;i<lenA && aEqB;i++) if(bufA[i]!=bufB[i]) aEqB=false; }
| |||
if(aEqC) { for(int i=0;i<lenA && aEqC;i++) if(bufA[i]!=bufC[i]) aEqC=false; }
| |||
Print("Output A == B: ", aEqB, " Output A == C: ", aEqC);
| |||
}
| |||
//+------------------------------------------------------------------+
|