Trabalho avançado com WebAssembly e C++: da teoria à prática
WebAssembly (Wasm) abre para desenvolvedores em C++ oportunidades únicas: executar código de alto desempenho no navegador com velocidade quase nativa. No entanto, tutoriais básicos frequentemente se limitam à compilação de funções simples. Neste artigo, nos aprofundaremos em técnicas avançadas: gerenciamento manual de memória, criação de bindings eficientes e perfilamento de desempenho. O material é voltado para desenvolvedores já familiarizados com os fundamentos do Emscripten e compilação de C++ para Wasm.
1. Gerenciamento manual de memória: superando as limitações do Wasm
Por padrão, o WebAssembly usa memória linear, gerenciada através de malloc/free. Para aplicações de alta carga (jogos, processamento de imagens, simulações), o alocador padrão pode se tornar um gargalo. Uma abordagem avançada é a implementação de um pool de memória próprio.
Vamos considerar um exemplo simples de um pool de tamanho fixo:
// 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); // Inicialização da lista de blocos livres 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_;};Esse alocador elimina a fragmentação e acelera a alocação/liberação de memória centenas de vezes. Para integração com 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. Integração avançada com JavaScript: passagem de estruturas complexas
A passagem de tipos escalares (int, float) é simples, mas para estruturas e arrays é necessária serialização. A maneira ideal é trabalhar diretamente com a memória linear do Wasm através de TypedArray.
Exemplo: passagem de um array de pontos de C++ para JavaScript sem cópia.
// 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(); }}<