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