2026-08-30 15:01:10 -05:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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. |
|
| | | //+------------------------------------------------------------------+
|
2026-08-30 15:25:31 -05:00 | | | 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
|
2026-08-30 15:01:10 -05:00 | | | bool g_lut_ready = false;
|
| | |
|
| | | void BuildLUT()
|
2026-08-30 15:25:31 -05:00 | | | {
|
| | | 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;
|
| | | }*/
|
2026-08-30 15:01:10 -05:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 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)
|
2026-08-30 15:25:31 -05:00 | | | {
|
| | | 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);
|
| | | }
|
2026-08-30 15:01:10 -05:00 | | |
|
2026-08-31 08:11:57 -05:00 | | |
|
| | |
|
2026-08-30 15:01:10 -05:00 | | | //+------------------------------------------------------------------+
|
| | | //| COMPETIDOR A: limpieza a buffer temporal + CryptDecode nativo |
|
| | | //+------------------------------------------------------------------+
|
| | | int DecodeB64_Native(const uchar &raw[], uchar &out[])
|
2026-08-30 15:25:31 -05:00 | | | {
|
| | | 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);
|
| | | }
|
2026-08-30 15:01:10 -05:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| 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[])
|
2026-08-30 15:25:31 -05:00 | | | {
|
2026-08-31 08:11:57 -05:00 | | | const int rawLen = ArraySize(raw);
|
2026-08-30 15:25:31 -05:00 | | | 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];
|
2026-08-31 08:11:57 -05:00 | | | if(c < 33 || c == '=')
|
2026-08-30 15:25:31 -05:00 | | | continue;
|
| | | //---
|
| | | acc = (acc << 6) | g_b64_lut[c];
|
| | | bits += 6;
|
| | | if(bits >= 8)
|
2026-08-30 15:01:10 -05:00 | | | {
|
2026-08-30 15:25:31 -05:00 | | | bits -= 8;
|
| | | out[w++] = uchar(acc >> bits);
|
2026-08-30 15:01:10 -05:00 | | | }
|
2026-08-30 15:25:31 -05:00 | | | }
|
2026-08-30 15:01:10 -05:00 | | |
|
2026-08-30 15:25:31 -05:00 | | | ArrayResize(out, w);
|
| | | return w;
|
| | | }
|
2026-08-30 15:01:10 -05:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Verifica que dos buffers de salida sean identicos (sanity check) |
|
| | | //+------------------------------------------------------------------+
|
| | | bool BuffersEqual(const uchar &a[], const uchar &b[])
|
2026-08-30 15:25:31 -05:00 | | | {
|
| | | int n = ArraySize(a);
|
| | | if(n != ArraySize(b))
|
| | | return false;
|
| | | for(int i = 0; i < n; i++)
|
| | | if(a[i] != b[i])
|
2026-08-30 15:01:10 -05:00 | | | return false;
|
2026-08-30 15:25:31 -05:00 | | | return true;
|
| | | }
|
2026-08-30 15:01:10 -05:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| Script principal |
|
| | | //+------------------------------------------------------------------+
|
| | | void OnStart()
|
2026-08-30 15:25:31 -05:00 | | | {
|
| | | // 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);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //+------------------------------------------------------------------+
|