JavaScript ile WebAssembly Performans Karşılaştırması

Web uygulamalarında performans her zaman kritik bir konu olmuştur. Ancak son birkaç yılda WebAssembly’nin sahneye çıkmasıyla birlikte “tarayıcıda ne kadar hızlı kod çalıştırabiliriz?” sorusunun cevabı kökten değişmeye başladı. Bir sistem yöneticisi veya backend developer olarak WebAssembly’yi sadece “frontend meselesi” diye geçiştirmek büyük hata olur. Çünkü bu teknoloji artık edge computing, serverless fonksiyonlar ve hatta konteyner alternatifleri olarak karşımıza çıkıyor.

Bu yazıda JavaScript ile WebAssembly arasındaki performans farkını somut kod örnekleri ve gerçek dünya senaryolarıyla masaya yatıracağız.

WebAssembly Nedir, Neden Önemli?

WebAssembly (kısaca WASM), tarayıcılarda ve diğer ortamlarda çalışabilen düşük seviyeli bir bytecode formatıdır. JavaScript’in aksine, WASM önceden derleme yapıldığı için JIT (Just-In-Time) optimizasyonuna ihtiyaç duymadan hızlı çalışır. Şu an için C, C++, Rust, Go ve hatta Python gibi dilleri WASM’a derleyebiliyorsunuz.

Peki neden önemli?

  • Taşınabilirlik: Bir kez derle, her yerde çalıştır. Tarayıcı, Node.js, Cloudflare Workers, Fastly Compute@Edge…
  • Performans: CPU yoğun işlemler için JavaScript’e göre belirgin avantaj sağlar.
  • Güvenlik: Sandbox ortamı sayesinde izole çalışır, host sisteme doğrudan erişim yoktur.
  • Dil bağımsızlığı: Rust ile yazdığın kodu tarayıcıda çalıştırabilirsin.

Temel Fark: JavaScript vs WASM Çalışma Modeli

JavaScript, V8 veya SpiderMonkey gibi motorlar tarafından yorumlanır ve JIT ile optimize edilir. Bu süreç dinamiktir; motor kodu analiz eder, sık kullanılan fonksiyonları makine koduna çevirir. Ancak bu optimizasyon süreci zaman alır ve her zaman tahmin edilebilir değildir.

WebAssembly ise zaten optimize edilmiş binary formatında gelir. Tarayıcı WASM modülünü yükler, doğrulama yapar ve doğrudan makine koduna çok daha hızlı bir şekilde çevirir. “Streaming compilation” özelliği sayesinde dosya indirilirken bile derleme başlayabilir.

# Wasm-pack ile Rust projesini WASM'a derleme
cargo install wasm-pack
wasm-pack build --target web --out-dir ./pkg
ls -lah ./pkg/
# pkg/my_wasm_bg.wasm  <- Binary dosya
# pkg/my_wasm.js       <- JS wrapper
# pkg/my_wasm.d.ts     <- TypeScript tanımları

Senaryo 1: Fibonacci Hesaplama

Klasik ama etkili bir başlangıç noktası. Recursive Fibonacci, CPU’yu yoran ve JIT optimizasyonunun sınırlarını zorlayan bir test vakasıdır.

JavaScript implementasyonu:

// fib.js - Recursive Fibonacci
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

console.time("JS Fibonacci");
const result = fibonacci(45);
console.timeEnd("JS Fibonacci");
console.log("Sonuc:", result);

// Tipik cikti: JS Fibonacci: ~8500ms

Rust ile yazıp WASM’a derleme:

// src/lib.rs
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn fibonacci(n: u32) -> u32 {
    if n <= 1 {
        return n;
    }
    fibonacci(n - 1) + fibonacci(n - 2)
}
// wasm_test.js - WASM versiyonunu kullanma
import init, { fibonacci } from './pkg/my_wasm.js';

async function run() {
    await init();
    
    console.time("WASM Fibonacci");
    const result = fibonacci(45);
    console.timeEnd("WASM Fibonacci");
    console.log("Sonuc:", result);
    
    // Tipik cikti: WASM Fibonacci: ~1200ms
}

run();

Fibonacci 45 için bu fark yaklaşık 7 kat performans artışı anlamına gelir. Ancak şunu belirtmek gerekir: basit, kısa süreli işlemlerde WASM’ın başlangıç maliyeti (initialization) bu avantajı azaltır.

Senaryo 2: Görüntü İşleme

Gerçek dünyada WASM’ın parladığı en önemli alan görüntü işleme. Figma, Adobe Photoshop Web ve AutoCAD Web uygulamaları bu teknolojiyi bu sebeple kullanıyor.

// js_image_processing.js - Pure JavaScript ile Gaussian Blur
function gaussianBlur(imageData, width, height) {
    const kernel = [
        [1, 2, 1],
        [2, 4, 2],
        [1, 2, 1]
    ];
    const kernelSum = 16;
    const data = imageData.data;
    const output = new Uint8ClampedArray(data.length);
    
    console.time("JS Gaussian Blur");
    
    for (let y = 1; y < height - 1; y++) {
        for (let x = 1; x < width - 1; x++) {
            for (let c = 0; c < 3; c++) {
                let sum = 0;
                for (let ky = -1; ky <= 1; ky++) {
                    for (let kx = -1; kx <= 1; kx++) {
                        const idx = ((y + ky) * width + (x + kx)) * 4 + c;
                        sum += data[idx] * kernel[ky + 1][kx + 1];
                    }
                }
                output[(y * width + x) * 4 + c] = sum / kernelSum;
            }
        }
    }
    
    console.timeEnd("JS Gaussian Blur");
    return output;
}

// 1920x1080 goruntu icin yaklasik 450ms

Aynı işlemin WASM versiyonu için Rust’ta şu şekilde bir implementasyon kullanılabilir. WASM tarafı için memory yönetimi kritik öneme sahiptir:

// wasm_bridge.js - WASM ile goruntu isleme
import init, { apply_gaussian_blur } from './pkg/image_proc.js';

async function processImage(canvas) {
    await init();
    
    const ctx = canvas.getContext('2d');
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    
    // Float32Array olarak pixel verisi WASM'a gönderilir
    const pixelData = new Uint8Array(imageData.data.buffer);
    
    console.time("WASM Gaussian Blur");
    // WASM fonksiyonu dogrudan Uint8Array üzerinde calisir
    const result = apply_gaussian_blur(
        pixelData, 
        canvas.width, 
        canvas.height
    );
    console.timeEnd("WASM Gaussian Blur");
    
    // 1920x1080 goruntu icin yaklasik 45-60ms
    const outputImageData = new ImageData(result, canvas.width, canvas.height);
    ctx.putImageData(outputImageData, 0, 0);
}

Bu senaryoda 1920×1080 çözünürlüklü bir görüntü için JavaScript yaklaşık 450ms harcarken WASM 45-60ms civarında kalır. Bu, gerçek zamanlı video işleme için kritik bir fark.

Senaryo 3: Kriptografi ve Hash İşlemleri

Edge computing ve serverless ortamlarda sık karşılaşılan senaryo: büyük veri setleri üzerinde SHA-256 hesaplama.

// js_crypto_benchmark.js
const crypto = require('crypto');

function benchmarkSHA256(iterations) {
    const testData = Buffer.alloc(1024 * 1024); // 1MB veri
    testData.fill(0x42);
    
    console.time("Node.js SHA256 - " + iterations + " iterasyon");
    
    for (let i = 0; i < iterations; i++) {
        const hash = crypto.createHash('sha256');
        hash.update(testData);
        hash.digest('hex');
    }
    
    console.timeEnd("Node.js SHA256 - " + iterations + " iterasyon");
}

// Node.js native crypto modulu zaten C++ ile yazildigi icin hizlidir
// Saf JS implementasyonu cok daha yavas olurdu
benchmarkSHA256(100);

Burada önemli bir nokta var: Node.js’in crypto modülü zaten native C++ ile yazılmış. Saf JavaScript SHA-256 implementasyonunu WASM ile kıyaslamak daha adil olur. Bu bağlamda WASM, saf JS’e göre 10-15x hız avantajı sağlar.

Senaryo 4: Edge Computing Ortamında WASM

Cloudflare Workers üzerinde WASM kullanmak artık production’da yaygın bir pratik. İşte gerçek bir senaryo:

// cloudflare-worker.js
// Cloudflare Workers + WASM ile JWT dogrulama

import wasmModule from './jwt_validator.wasm';

const wasmInstance = new WebAssembly.Instance(wasmModule);
const { validate_jwt, alloc, dealloc } = wasmInstance.exports;
const memory = wasmInstance.exports.memory;

addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
    const authHeader = request.headers.get('Authorization');
    
    if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return new Response('Unauthorized', { status: 401 });
    }
    
    const token = authHeader.slice(7);
    const encoder = new TextEncoder();
    const tokenBytes = encoder.encode(token);
    
    // WASM memory'e token yazma
    const ptr = alloc(tokenBytes.length);
    const memView = new Uint8Array(memory.buffer);
    memView.set(tokenBytes, ptr);
    
    // WASM fonksiyonu ile dogrulama (JS'e gore 3-5x daha hizli)
    const isValid = validate_jwt(ptr, tokenBytes.length);
    dealloc(ptr, tokenBytes.length);
    
    if (!isValid) {
        return new Response('Invalid token', { status: 403 });
    }
    
    return fetch(request);
}

Bu yapıyla Cloudflare Workers’da JWT doğrulama işlemi saf JavaScript implementasyonuna göre 3-5x daha hızlı çalışır. Binlerce eş zamanlı istek geldiğinde bu fark ciddi maliyet avantajına dönüşür.

WASM’ın Zayıf Olduğu Alanlar

Dürüst olmak gerekirse, WASM her zaman kazanmaz. Şu alanlarda JavaScript daha avantajlı olabilir:

DOM manipülasyonu: WASM doğrudan DOM’a erişemez. Her DOM işlemi için JS bridge üzerinden geçmek gerekir. Bu overhead bazen WASM’ın hız avantajını yok eder.

// dom_comparison.js - Bu senaryoda JS daha hizlidir
// WASM bu kadar basit DOM islemi icin gereksiz overhead yaratir

function updateDOMWithJS(items) {
    console.time("JS DOM Update");
    const container = document.getElementById('list');
    items.forEach(item => {
        const div = document.createElement('div');
        div.textContent = item.name;
        div.className = 'item';
        container.appendChild(div);
    });
    console.timeEnd("JS DOM Update");
}

// WASM versiyonu icin her appendChild cagrisi
// JS bridge'den gecmek zorundadir, bu da latency ekler
// Kucuk veri setleri icin JS burada kazanir

Startup süresi: WASM modüllerini yüklemek ve başlatmak zaman alır. Kısa ömürlü, tek seferlik işlemler için bu maliyet dezavantaj yaratır.

String işlemleri: JavaScript string yönetiminde çok optimize edilmiştir. Basit string manipülasyonları için WASM avantaj sağlamaz.

Performans Ölçüm Metodolojisi

Doğru karşılaştırma yapabilmek için tutarlı bir benchmark metodolojisi şart:

// benchmark_framework.js
class WASMBenchmark {
    constructor(name) {
        this.name = name;
        this.results = [];
    }
    
    async run(fn, iterations = 10) {
        // Ilk calismayi isi isitma (warm-up) olarak say
        await fn();
        
        for (let i = 0; i < iterations; i++) {
            const start = performance.now();
            await fn();
            const end = performance.now();
            this.results.push(end - start);
        }
        
        this.printStats();
    }
    
    printStats() {
        const sorted = [...this.results].sort((a, b) => a - b);
        const mean = this.results.reduce((a, b) => a + b, 0) / this.results.length;
        const median = sorted[Math.floor(sorted.length / 2)];
        const p95 = sorted[Math.floor(sorted.length * 0.95)];
        const min = sorted[0];
        const max = sorted[sorted.length - 1];
        
        console.log(`n=== ${this.name} Benchmark Sonuclari ===`);
        console.log(`Ortalama: ${mean.toFixed(2)}ms`);
        console.log(`Medyan: ${median.toFixed(2)}ms`);
        console.log(`P95: ${p95.toFixed(2)}ms`);
        console.log(`Min: ${min.toFixed(2)}ms`);
        console.log(`Max: ${max.toFixed(2)}ms`);
    }
}

// Kullanim ornegi
async function runComparison() {
    await init(); // WASM init
    
    const jsBench = new WASMBenchmark("JavaScript Fibonacci(40)");
    const wasmBench = new WASMBenchmark("WASM Fibonacci(40)");
    
    await jsBench.run(() => fibonacci_js(40));
    await wasmBench.run(() => fibonacci_wasm(40));
}

Bu şekilde tek seferlik ölçümler yerine istatistiksel anlamlı sonuçlar elde edilir. P95 değeri özellikle önemli; üretim ortamında kullanıcı deneyimini etkileyen bu değerdir.

WASM Araç Zinciri Kurulumu

Gerçek bir projede WASM ile çalışmaya başlamak için gerekli toolchain:

# Rust ve wasm-pack kurulumu
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

# wasm-pack kurulumu
cargo install wasm-pack

# wasm-bindgen CLI
cargo install wasm-bindgen-cli

# Yeni WASM projesi olusturma
cargo new --lib wasm_perf_test
cd wasm_perf_test

# Cargo.toml guncelleme
cat >> Cargo.toml << 'EOF'
[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
EOF

# Derleme ve web icin paketleme
wasm-pack build --target web --release

# Boyut optimizasyonu icin wasm-opt
npm install -g wasm-opt
wasm-opt -O3 -o optimized.wasm pkg/wasm_perf_test_bg.wasm

# Boyut karsilastirmasi
ls -lah pkg/*.wasm optimized.wasm

WASM dosya boyutu da önemli bir faktördür. Optimize edilmemiş bir Rust WASM binary’si birkaç yüz KB olabilir, ancak wasm-opt ile %30-50 küçültme mümkündür.

Ne Zaman WASM, Ne Zaman JavaScript?

Bu sorunun net bir cevabı var:

WASM tercih et:

  • Görüntü, video veya ses işleme
  • Kriptografi ve hash hesaplamaları
  • Fizik simülasyonları ve oyun motorları
  • Sıkıştırma ve arşivleme işlemleri
  • Büyük veri setleri üzerinde matematiksel hesaplamalar
  • C/C++ veya Rust ile yazılmış mevcut kodu web’e taşıma

JavaScript’te kal:

  • DOM ağırlıklı UI işlemleri
  • Basit ve kısa süreli hesaplamalar
  • Network istekleri ve I/O yönetimi
  • Hızlı prototipleme ve iterasyon
  • Startup süresinin kritik olduğu durumlar

Hibrit yaklaşım kullan (en yaygın production senaryosu):

  • UI katmanı JavaScript ile yönetilir
  • Ağır hesaplama çekirdeği WASM olarak çalışır
  • Worker threads ile WASM, ana thread’i bloklamaz

Sonuç

JavaScript ile WebAssembly arasındaki performans tartışması “hangisi daha iyi?” sorusundan ziyade “hangi iş için hangisi?” sorusuna dönüşüyor. CPU yoğun işlemlerde WASM’ın sağladığı 5-10x ve hatta bazı senaryolarda 20x performans artışı göz ardı edilemez. Ancak DOM manipülasyonu, basit string işlemleri ve kısa süreli görevler için JavaScript hala daha pratik.

Bir sistem yöneticisi ya da platform mühendisi olarak bakıldığında WASM’ın asıl devrimsel yanı tarayıcı dışındaki potansiyeli. Cloudflare Workers, Fastly Compute@Edge ve Wasmer gibi runtime’lar sayesinde WASM artık serverless fonksiyonlarda, edge node’larında ve hatta Docker’a alternatif olarak container runtime’larında karşımıza çıkıyor. “Write once, run anywhere” vaadini Java’dan çok daha düşük overhead ile hayata geçiriyor.

Önümüzdeki birkaç yılda WebAssembly System Interface (WASI) standartlaştıkça, WASM’ı sadece web performansı için değil, dağıtık sistemlerin temel yapı taşı olarak görmemiz kaçınılmaz. Şimdiden araç zincirine aşina olmak, bu dönüşüme hazırlıklı olmak demek.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir