Langsung ke konten utama
BlockchainMar 28, 2026

Deep EVM #7: Loop dan Kondisional Efisien Gas di Yul

OS
Open Soft Team

Engineering Team

Loop di Yul vs Solidity

Solidity mengkompilasi loop for menjadi serangkaian opcode yang menyertakan pemeriksaan overflow, penanganan revert, dan boilerplate lainnya. Yul memungkinkan Anda menulis loop yang lebih ringkas dan efisien.

Solidity For Loop (Default)

for (uint256 i = 0; i < length; i++) {
    // badan loop
}

Dikompilasi menjadi: inisialisasi + pemeriksaan overflow pada i++ + perbandingan + JUMPI + badan + increment + JUMP kembali.

Yul For Loop (Teroptimasi)

assembly {
    for { let i := 0 } lt(i, length) { i := add(i, 1) } {
        // badan loop
    }
}

Perbedaan kunci: Yul tidak menyisipkan pemeriksaan overflow pada add(i, 1). Untuk counter loop yang tidak mungkin overflow (karena length < 2^256), ini menghemat ~30 gas per iterasi.

Teknik Optimasi Loop

1. Cache Batas Loop

// Buruk: membaca length dari storage setiap iterasi
assembly {
    for { let i := 0 } lt(i, sload(lengthSlot)) { i := add(i, 1) } {
        // SLOAD setiap iterasi = 100 gas warm per iterasi!
    }
}

// Baik: cache length di stack
assembly {
    let len := sload(lengthSlot)  // Satu SLOAD
    for { let i := 0 } lt(i, len) { i := add(i, 1) } {
        // len dari stack = 3 gas (DUP)
    }
}

Penghematan: (100 - 3) * jumlah_iterasi gas.

2. Hitung Mundur

assembly {
    // Menghitung mundur menghemat gas karena perbandingan dengan 0 (ISZERO)
    // lebih murah daripada perbandingan dengan nilai non-zero (LT)
    let i := length
    for { } gt(i, 0) { i := sub(i, 1) } {
        let index := sub(i, 1)  // index 0-based
        // ... gunakan index
    }
}

Penghematan: kecil (1-2 gas per iterasi) tetapi bertambah untuk loop panjang.

3. Loop Unrolling

Untuk loop dengan jumlah iterasi yang diketahui, unrolling menghilangkan overhead JUMP:

assembly {
    // Daripada loop 4 iterasi:
    // for { let i := 0 } lt(i, 4) { i := add(i, 1) } { ... }
    
    // Unroll:
    let s0 := sload(0)
    let s1 := sload(1)
    let s2 := sload(2)
    let s3 := sload(3)
    let total := add(add(s0, s1), add(s2, s3))
}

Menghemat: ~15 gas per iterasi yang di-unroll (JUMP + JUMPDEST + perbandingan + increment).

4. Batch Processing

assembly {
    // Proses 32 byte sekaligus daripada 1 byte
    let words := div(dataLength, 32)
    for { let i := 0 } lt(i, words) { i := add(i, 1) } {
        let word := mload(add(dataPtr, mul(i, 32)))
        // Proses seluruh word 32-byte
    }
    // Handle sisa byte
    let remaining := mod(dataLength, 32)
    if remaining {
        let lastWord := mload(add(dataPtr, mul(words, 32)))
        // Mask byte yang relevan
    }
}

Kondisional di Yul

If Statement

assembly {
    if condition {
        // dieksekusi jika condition != 0
    }
    // Yul TIDAK memiliki else!
}

Yul tidak memiliki else. Untuk logika if-else, gunakan switch:

Switch Statement

assembly {
    switch condition
    case 0 {
        // false branch
    }
    default {
        // true branch (non-zero)
    }
}

Multi-Way Switch

assembly {
    // Dispatch berdasarkan function selector
    let selector := shr(224, calldataload(0))
    
    switch selector
    case 0xa9059cbb { // transfer(address,uint256)
        // handle transfer
    }
    case 0x70a08231 { // balanceOf(address)
        // handle balanceOf
    }
    case 0x095ea7b3 { // approve(address,uint256)
        // handle approve
    }
    default {
        revert(0, 0)  // fungsi tidak dikenali
    }
}

Short-Circuit Evaluation

Solidity mengkompilasi && dan || dengan branching terpisah. Di Yul, Anda bisa menggunakan operasi bitwise untuk evaluasi tanpa branching:

assembly {
    // Solidity: require(a > 0 && b > 0 && c > 0)
    // Menghasilkan 3 JUMPI
    
    // Yul: satu pemeriksaan gabungan
    if iszero(and(and(gt(a, 0), gt(b, 0)), gt(c, 0))) {
        revert(0, 0)
    }
    // Hanya 1 JUMPI!
}

Penghematan: ~20 gas per kondisi tambahan yang digabungkan.

Perbandingan Gas: Yul vs Solidity

OperasiSolidityYulPenghematan
Loop increment (i++)~35 gas~6 gas~29 gas
Pemeriksaan 3 kondisi~45 gas~25 gas~20 gas
Array sum (10 elemen)~800 gas~550 gas~250 gas
Storage cache + loopbervariasi-30%signifikan

Pola Tingkat Lanjut

Early Exit dari Loop

assembly {
    let found := 0
    for { let i := 0 } lt(i, length) { i := add(i, 1) } {
        let val := sload(add(baseSlot, i))
        if eq(val, target) {
            found := 1
            i := length  // Paksa keluar dari loop
        }
    }
}

Loop dengan Akumulasi Memory

assembly {
    let ptr := mload(0x40)  // Alokasi output
    let outPtr := ptr
    
    for { let i := 0 } lt(i, count) { i := add(i, 1) } {
        let val := sload(add(baseSlot, i))
        if gt(val, threshold) {
            mstore(outPtr, val)
            outPtr := add(outPtr, 0x20)
        }
    }
    
    // Update FMP
    mstore(0x40, outPtr)
    // Jumlah hasil = (outPtr - ptr) / 32
}

Perangkap Umum

  1. Infinite loop — Tanpa batas gas, loop Yul yang salah menghabiskan semua gas. Selalu pastikan kondisi terminasi.
  2. Off-by-one — Tanpa pemeriksaan batas otomatis, sangat mudah membaca melewati akhir array.
  3. Stack depth — Loop kompleks bisa mendorong stack mendekati batas 16 variabel lokal. Pindahkan data ke memory jika diperlukan.

Kesimpulan

Loop dan kondisional di Yul memberikan kontrol penuh atas biaya gas. Cache batas loop, gabungkan kondisi, gunakan loop unrolling untuk iterasi yang diketahui, dan proses data dalam batch word 32-byte. Teknik-teknik ini secara konsisten menghasilkan penghematan 20-40% dibanding Solidity murni.