//+------------------------------------------------------------------+ //| 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>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