How WebAssembly Crushes Technical Debt
Technical debt arises from decisions about implementing Information Technology (IT) systems. Practices like building monolithic applications may create technical debt and erode future profits.
You may have heard of a relatively new technology called WebAssembly (Wasm). Wasm can help you break apart monolithic structures, streamline performance, and future-proof systems by running lean, portable, high-efficiency code universally in browsers, servers, desktops, mobile, edge, and Internet of Things (IoT) devices. This article explains how technologies like cloud computing have evolved and how Wasm can now be leveraged to alter existing systems potentially alleviating technical debt.
So, how does Wasm tie into technical debt? In many systems, technical debt accumulates when developers rely on monolithic server-side codebases tied to specific platforms or (on the client’s side) perhaps bloated JavaScript codebases that are hard to maintain and scale. Wasm offers a way out by enabling lightweight, high-performance code that can be modularly implemented. By adopting Wasm, teams can refactor or replace outdated components and build a modern efficient alternative. The last several years of businesses/projects migrating legacy servers into alternative cloud solutions have helped this cause. Here’s why.
Almost all companies and government agencies have moved away from on-site, self-managed servers and started using cloud computing solutions. Cloud solutions offer many benefits, including making user (client) data available more closely to the client. This evolution of cloud computing is known as “edge” computing.
Edge computing changes “where” computation occurs; it shifts a portion of an entire application’s processing workload to a client’s device. This plays perfectly into the hands of Wasm whereby high-performance code can now be run in the browser at near-native speeds. (We will get to an example in a minute.)
Wasm also provides a level of flexibility whereby it avoids platform lock-in; enabling code to be written in different languages (but compiled into the same .wasm
target) and happily runs across different operating systems, browsers, and hardware architectures. (See this universal data compression example.) Wasm bridges the gap between native performance and cross-platform portability opening up new possibilities. For example, in the past, we would never have dreamed of designing a system that allows a client’s device to perform computationally intensive processing. But thanks to Wasm, we can.
Can you imagine a client’s web browser processing one million rows in an Excel spreadsheet? This scenario seems quite absurd; especially seeing how before 2007, the .xls
file format had a limit of only 65,536 rows in each Excel spreadsheet.
Imagine no longer. Below, we demonstrate how a client’s web browser can process 1 million rows of Excel data in just 800 milliseconds.
C++
Below is a C++ program I wrote. It:
- creates a Microsoft Excel workbook,
- generates 1 million rows of data,
- returns the sum of all data values, and
- returns how long that task took.
The code below is compiled to Wasm (as shown further down below):
#include <emscripten.h>
#include <xlnt/xlnt.hpp>
#include <thread>
#include <vector>
extern "C" {
EMSCRIPTEN_KEEPALIVE
float processSpreadsheet(float * data, int size) {
xlnt::workbook wb;
std::vector < std::thread > threads;
std::vector < float > partial_totals(4, 0.0 f);
auto worker = [ & ](int start, int end, int thread_id) {
xlnt::workbook thread_wb;
xlnt::worksheet thread_ws = thread_wb.active_sheet();
for (int i = start; i < end; i++) {
thread_ws.cell(xlnt::cell_reference(1, i + 1)).value(data[i]);
partial_totals[thread_id] += data[i];
}
};
int num_threads = 4;
int chunk_size = size / num_threads;
for (int i = 0; i < num_threads; i++) {
int start = i * chunk_size;
int end = (i == num_threads - 1) ? size : start + chunk_size;
threads.emplace_back(worker, start, end, i);
}
for (auto & t: threads) t.join();
float total = 0.0 f;
for (float part: partial_totals) total += part;
return total;
}
}
The above code harnesses the cross-platform user-friendly .xslx
library for C++ XLNT, by Thomas Fussell.
Compiling C++ to Wasm
The following Emscripten command compiles the C++ to a Wasm target (a .wasm
executable) and also generates the necessary Javascript (a .js
file) so that we can use this C++ in a web browser:
em++ src/spreadsheet.cpp \
-s EXPORTED_FUNCTIONS='["_processSpreadsheet", "_malloc", "_free"]' \
-s EXPORTED_RUNTIME_METHODS='["ccall", "cwrap", "allocate", "getValue"]' \
-s USE_ZLIB=1 -s WASM=1 -s MODULARIZE=1 -s EXPORT_NAME='createModule' \
-s USE_PTHREADS=1 \
-msimd128 \
-s PTHREAD_POOL_SIZE=4 \
-s INITIAL_MEMORY=536870912 \
-s MAXIMUM_MEMORY=1073741824 \
-s ALLOW_MEMORY_GROWTH=1 \
-I/Users/tpmccallum/webassembly-spreadsheet-engine/xlnt/include \
-L/Users/tpmccallum/webassembly-spreadsheet-engine/xlnt/source -lxlnt \
-o web/spreadsheet.js -O3
HTML
The HTML below allows me to interact with the above C++ code (in the form of Wasm and Javascript) via my web browser:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>WASM Spreadsheet Processor</title>
<!-- Bootstrap 5 CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body class="bg-light">
<div class="container text-center mt-5">
<h1 class="mb-4">Process 1 Million Records with C++ in WASM</h1>
<div class="input-group mb-3 w-50 mx-auto">
<input type="number" id="rowCount" class="form-control" value="1000000" min="1" max="10000000">
<button id="processButton" class="btn btn-primary" onclick="processData()" disabled>Process</button>
</div>
<pre id="output" class="alert alert-secondary p-3">Result will appear here...</pre>
<p class="fw-bold">Time: <span id="time">0</span> ms</p>
</div>
<script src="spreadsheet.js"></script>
<script>
let Module;
createModule().then((module) => {
Module = module;
console.log("WASM Ready");
window.malloc = Module._malloc;
window.free = Module._free;
document.getElementById('processButton').disabled = false;
}).catch((e) => {
console.error("WASM Initialization Failed:", e);
});
function processData() {
const rowCount = parseInt(document.getElementById('rowCount').value);
const data = new Float32Array(rowCount).map(() => Math.random() * 1000);
const start = performance.now();
const ptr = malloc(data.length * data.BYTES_PER_ELEMENT);
const batchSize = 100000;
const totalSize = data.length;
const chunkCount = Math.ceil(totalSize / batchSize);
for (let i = 0; i < chunkCount; i++) {
const startIdx = i * batchSize;
const endIdx = Math.min(startIdx + batchSize, totalSize);
const chunk = data.subarray(startIdx, endIdx);
Module.HEAPF32.set(chunk, ptr / 4 + startIdx);
document.getElementById('output').textContent = `Processing chunk ${i + 1}/${chunkCount}...`;
}
const result = Module.ccall('processSpreadsheet', 'number',
['number', 'number'], [ptr, data.length]);
free(ptr);
document.getElementById('output').textContent = `Total: ${result.toFixed(2)}`;
const time = performance.now() - start;
document.getElementById('time').textContent = time.toFixed(2);
}
</script>
</body>
</html>
The HTML lets me to choose the number of rows in the spreadsheet.
I enter 1000000
and click the “Process” button.
As we can see from this output, it took only 842.62
milliseconds to generate the million rows of data, and, sum the total of all of the rows ( in this case 500210272.00
)
I was curious and decided to test out different numbers of rows, the results are as follows:
Rows | Milliseconds
10 | 1.34
100 | 1.86
1000 | 2.31
10000 | 10.76
100000 | 82.55
1000000 | 842.62
Harnessing processing power directly in the browser via Wasm unlocks transformative advantages for computationally intensive tasks. Tasks that needed server-side execution can now run anywhere (everything from image rendering to video encoding, audio processing and more). This shift reduces server costs, opens up totally different design choices and ensures a seamless experience for end users; even offline.
Remember, technical debt kills profits. You now have more options than before. Rethink what normal is.
If you would like to implement Wasm or have any questions or comments please visit me on the web, or email me at mistermac2008@gmail.com. You can also find me on X and GitHub.