Trabajo avanzado con WebAssembly y C++: de la teoría a la práctica
WebAssembly (Wasm) abre a los desarrolladores de C++ oportunidades únicas: ejecutar código de alto rendimiento en el navegador con velocidad casi nativa. Sin embargo, los tutoriales básicos a menudo se limitan a compilar funciones simples. En este artículo, profundizaremos en técnicas avanzadas: gestión manual de memoria, creación de enlaces eficientes y perfilado de rendimiento. El material está dirigido a desarrolladores que ya conocen los fundamentos de Emscripten y la compilación de C++ a Wasm.
1. Gestión manual de memoria: superando las limitaciones de Wasm
Por defecto, WebAssembly utiliza memoria lineal gestionada a través de malloc/free. Para aplicaciones de alta carga (juegos, procesamiento de imágenes, simulaciones), el asignador estándar puede convertirse en un cuello de botella. Un enfoque avanzado es la implementación de un grupo de memoria propio.
Consideremos un ejemplo de un grupo simple de tamaño fijo:
// pool_allocator.h#include <cstdint>#include <vector>
class PoolAllocator {public: PoolAllocator(size_t block_size, size_t block_count) : block_size_(block_size), block_count_(block_count) { memory_ = malloc(block_size * block_count); // Inicialización de la lista de bloques libres for (size_t i = 0; i < block_count; ++i) { free_blocks_.push_back( reinterpret_cast<uintptr_t>(memory_) + i * block_size ); } }
~PoolAllocator() { free(memory_); }
void* allocate() { if (free_blocks_.empty()) return nullptr; uintptr_t addr = free_blocks_.back(); free_blocks_.pop_back(); return reinterpret_cast<void*>(addr); }
void deallocate(void* ptr) { free_blocks_.push_back(reinterpret_cast<uintptr_t>(ptr)); }
private: void* memory_; size_t block_size_; size_t block_count_; std::vector<uintptr_t> free_blocks_;};Este asignador elimina la fragmentación y acelera la asignación/liberación de memoria cientos de veces. Para la integración con Emscripten, use EMSCRIPTEN_KEEPALIVE:
// main.cpp#include <emscripten.h>
PoolAllocator* g_allocator = nullptr;
extern "C" { EMSCRIPTEN_KEEPALIVE void init_allocator(size_t block_size, size_t block_count) { g_allocator = new PoolAllocator(block_size, block_count); }
EMSCRIPTEN_KEEPALIVE void* pool_alloc() { return g_allocator ? g_allocator->allocate() : nullptr; }
EMSCRIPTEN_KEEPALIVE void pool_free(void* ptr) { if (g_allocator) g_allocator->deallocate(ptr); }}2. Integración avanzada con JavaScript: transferencia de estructuras complejas
La transferencia de tipos escalares (int, float) es simple, pero para estructuras y arrays se requiere serialización. La forma óptima es trabajar directamente con la memoria lineal de Wasm a través de TypedArray.
Ejemplo: transferencia de un array de puntos desde C++ a JavaScript sin copia.
// point.h#pragma pack(push, 1)struct Point { float x; float y; float z;};#pragma pack(pop)
// main.cpp#include <emscripten.h>#include <vector>
std::vector<Point> points;
extern "C" { EMSCRIPTEN_KEEPALIVE void generate_points(int count) { points.resize(count); for (int i = 0; i < count; ++i) { points[i] = {static_cast<float>(i), static_cast<float>(i*2), static_cast<float>(i*3)}; } }
EMSCRIPTEN_KEEPALIVE float* get_points_ptr() { return reinterpret_cast<float*>(points.data()); }
EMSCRIPTEN_KEEPALIVE int get_points_count() { return points.size(); }}<