//+------------------------------------------------------------------+ //| Base64Bench.mq5 | //| Benchmark: decodificar Base64 con whitespace (\r\n, espacios) | //| embebido en cualquier posicion, segun gramatica "lax" RFC 7468. | //| | //| Competidor A: limpieza a buffer temporal + CryptDecode nativo | //| Competidor B: decode manual streaming, sin buffers intermedios, | //| tabla de lookup, todo en un solo recorrido | //+------------------------------------------------------------------+ #property script_show_inputs input int InpIterations = 20000; // Iteraciones por competidor input int InpPayloadBytes = 3000; // Bytes RAW (antes de b64) a generar para el test //+------------------------------------------------------------------+ //| Tabla de lookup Base64 -> 6 bits. | //| 0xFF marca: caracter invalido O whitespace (ambos se tratan igual:| //| "saltar"), simplificando el chequeo a una sola comparacion. | //+------------------------------------------------------------------+ const uchar g_b64_lut[256] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, 0xFF, 0xFF, 0x3F, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; /* Codigo de ia... lo cambie dado qeu el competidor manaul me parecio interenate asi que lo mejoraremos bool g_lut_ready = false; void BuildLUT() { ArrayInitialize(g_b64_lut, 0xFF); string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; for(int i = 0; i < 64; i++) g_b64_lut[(uchar)StringGetCharacter(alphabet, i)] = (uchar)i; g_lut_ready = true; }*/ //+------------------------------------------------------------------+ //| Genera un "raw" base64 con saltos de linea cada 64 chars (CRLF), | //| simulando un PEM real, a partir de bytes binarios random. | //+------------------------------------------------------------------+ void GenerateTestInput(uchar &rawOut[], int payloadBytes) { uchar bin[]; ArrayResize(bin, payloadBytes); for(int i = 0; i < payloadBytes; i++) bin[i] = (uchar)(MathRand() & 0xFF); uchar b64[]; uchar key[]; ArrayResize(key, 0); int n = CryptEncode(CRYPT_BASE64, bin, key, b64); if(n <= 0) { Print("Error generando base64 de prueba: ", GetLastError()); return; } // Insertar \r\n cada 64 caracteres, como un PEM real ArrayResize(rawOut, n + (n / 64 + 2) * 2); int w = 0, pos = 0; while(n - pos >= 64) { w += ArrayCopy(rawOut, b64, w, pos, 64); rawOut[w++] = '\r'; rawOut[w++] = '\n'; pos += 64; } if(pos < n) { w += ArrayCopy(rawOut, b64, w, pos, n - pos); rawOut[w++] = '\r'; rawOut[w++] = '\n'; } ArrayResize(rawOut, w); } //+------------------------------------------------------------------+ //| COMPETIDOR A: limpieza a buffer temporal + CryptDecode nativo | //+------------------------------------------------------------------+ int DecodeB64_Native(const uchar &raw[], uchar &out[]) { int rawLen = ArraySize(raw); // Buffer temporal presized al tamaƱo maximo posible (== rawLen, nunca mas) uchar clean[]; ArrayResize(clean, rawLen); int w = 0; for(int i = 0; i < rawLen; i++) { uchar c = raw[i]; // Whitespace: espacio, \t, \r, \n -- se descarta if(c < 33) continue; clean[w++] = c; } ArrayResize(clean, w); //uchar key[]; //ArrayResize(key, 0); // NIQUE: mejora para b quitamos array temporal return CryptDecode(CRYPT_BASE64, clean, clean, out); } //+------------------------------------------------------------------+ //| COMPETIDOR B: decode manual streaming, sin buffers intermedios. | //| Un solo recorrido de raw[], acumula 4 simbolos validos via LUT, | //| vuelca directo a out[] presized. Sin arrays temporales de ningun | //| tipo, sin llamadas a funciones dentro del hot loop. | //+------------------------------------------------------------------+ int DecodeB64_Manual(const uchar &raw[], uchar &out[]) { const int rawLen = ArraySize(raw); ArrayResize(out, (rawLen / 4 + 1) * 3); int w = 0; // puntero de escritura en out[] uint acc = 0; // acumulador de bits int bits = 0; // cuantos bits validos hay en acc for(int i = 0; i < rawLen; i++) { //--- const uchar c = raw[i]; if(c < 33 || c == '=') continue; //--- acc = (acc << 6) | g_b64_lut[c]; bits += 6; if(bits >= 8) { bits -= 8; out[w++] = uchar(acc >> bits); } } ArrayResize(out, w); return w; } //+------------------------------------------------------------------+ //| Verifica que dos buffers de salida sean identicos (sanity check) | //+------------------------------------------------------------------+ bool BuffersEqual(const uchar &a[], const uchar &b[]) { int n = ArraySize(a); if(n != ArraySize(b)) return false; for(int i = 0; i < n; i++) if(a[i] != b[i]) return false; return true; } //+------------------------------------------------------------------+ //| Script principal | //+------------------------------------------------------------------+ void OnStart() { // BuildLUT(); MathSrand((int)GetTickCount()); uchar raw[]; GenerateTestInput(raw, InpPayloadBytes); Print("=== Base64 Decode Benchmark ==="); Print("Payload binario: ", InpPayloadBytes, " bytes -> raw b64 (con CRLF): ", ArraySize(raw), " bytes"); Print("Iteraciones por competidor: ", InpIterations); // --- Sanity check: ambos deben producir el mismo resultado --- uchar outA[], outB[]; int resA = DecodeB64_Native(raw, outA); int resB = DecodeB64_Manual(raw, outB); if(resA <= 0) { Print("ERROR: Competidor A (nativo) fallo. GetLastError=", GetLastError()); return; } if(!BuffersEqual(outA, outB)) { Print("ERROR: Los resultados NO coinciden. A=", resA, " bytes, B=", resB, " bytes. Abortando benchmark."); return; } Print("Sanity check OK: ambos competidores producen el mismo resultado (", resA, " bytes)."); Print(""); // --- Warm-up (evita medir efectos de JIT/cache frio) --- uchar warm[]; for(int i = 0; i < 100; i++) { DecodeB64_Native(raw, warm); DecodeB64_Manual(raw, warm); } // --- Benchmark Competidor A --- uchar tmpOut[]; ulong t0 = GetMicrosecondCount(); for(int i = 0; i < InpIterations; i++) DecodeB64_Native(raw, tmpOut); ulong t1 = GetMicrosecondCount(); double usA = (double)(t1 - t0); // --- Benchmark Competidor B --- ulong t2 = GetMicrosecondCount(); for(int i = 0; i < InpIterations; i++) DecodeB64_Manual(raw, tmpOut); ulong t3 = GetMicrosecondCount(); double usB = (double)(t3 - t2); Print("--- Resultados ---"); PrintFormat("A) Nativo (clean buffer + CryptDecode): %.2f us total | %.4f us/iter", usA, usA / InpIterations); PrintFormat("B) Manual streaming (sin buffers temp): %.2f us total | %.4f us/iter", usB, usB / InpIterations); PrintFormat("Speedup B vs A: %.2fx", usA / usB); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+