CryptoByLeo/Src/DiseñoBenchs/B64 PUB.mq5
2026-08-30 14:39:16 -05:00

265 行
无行尾
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);
}
//+------------------------------------------------------------------+