“Programmer menghabiskan banyak waktu untuk mengkhawatirkan kecepatan program mereka, dan upaya untuk mencapai efisiensi sering kali memiliki dampak negatif yang dramatis pada kemampuan debug dan mendukung mereka. Penting untuk melupakan optimasi kecil, katakanlah, pada 97% kasus. Optimalisasi prematur adalah akar dari semua kejahatan! Tapi kita tidak boleh kehilangan 3% dari mereka yang benar-benar penting! ”
Donald Knut.

Saat melakukan audit kontrak pintar, kadang-kadang kita bertanya pada diri sendiri apakah pengembangannya terkait dengan 97% di mana tidak perlu memikirkan optimasi atau kita hanya berurusan dengan 3% dari kasus di mana itu penting. Menurut pendapat kami, lebih mungkin yang kedua. Tidak seperti aplikasi lain, kontrak pintar tidak diperbarui, mereka tidak dapat dioptimalkan "saat bepergian" (asalkan algoritma mereka tidak diletakkan, tetapi ini adalah topik yang terpisah). Argumen kedua yang mendukung optimalisasi kontrak
awal adalah bahwa, tidak seperti kebanyakan sistem di mana sub-optimalitas hanya diwujudkan dalam skala, terkait dengan spesifikasi besi dan lingkungan, itu diukur dengan sejumlah besar metrik, kontrak pintar pada dasarnya memiliki satu-satunya metrik kinerja - konsumsi gas.
Oleh karena itu, secara teknis lebih mudah untuk mengevaluasi efektivitas kontrak, tetapi pengembang sering terus mengandalkan intuisi mereka dan melakukan "optimasi prematur" buta yang sama yang dibicarakan oleh Profesor Knut. Kami akan memeriksa seberapa intuitif solusi sesuai dengan kenyataan dengan contoh pemilihan kedalaman bit suatu variabel. Dalam contoh ini, seperti dalam kebanyakan kasus praktis, kami tidak akan mencapai penghematan, dan bahkan sebaliknya, kontrak kami akan menjadi lebih mahal dalam hal konsumsi gas.
Gas jenis apa?
Ethereum seperti komputer global, yang "prosesornya" adalah mesin virtual EVM, "kode program" adalah serangkaian perintah dan data yang dicatat dalam kontrak pintar, dan panggilan adalah transaksi dari dunia luar. Transaksi dikemas dalam struktur terkait - blok yang terjadi setiap beberapa detik sekali. Dan karena ukuran blok secara definisi terbatas, dan protokol pemrosesan bersifat deterministik (memerlukan pemrosesan yang seragam dari semua transaksi dalam blok oleh semua node jaringan), maka untuk memenuhi permintaan yang berpotensi tidak terbatas dengan sumber daya node yang terbatas dan melindungi terhadap DoS, sistem harus menyediakan algoritma yang adil untuk memilih permintaan yang akan dilayani, dan yang diabaikan, sebagai mekanisme di banyak blockchain publik, ada prinsip sederhana - pengirim dapat memilih jumlah remunerasi kepada penambang untuk melakukan trans ktsii dan memilih penambang yang kebutuhannya termasuk blok, dan yang tidak, memilih yang paling menguntungkan untuk diri mereka sendiri.
Misalnya, dalam Bitcoin, di mana blok dibatasi hingga satu megabyte, penambang memilih untuk memasukkan transaksi dalam blok atau tidak berdasarkan panjang dan komisi yang diusulkan (memilih orang-orang dengan rasio satoshi maksimum per byte).
Untuk protokol Ethereum yang lebih kompleks, pendekatan ini tidak cocok, karena satu byte dapat mewakili tidak adanya operasi (misalnya, kode STOP) dan operasi penulisan yang mahal dan lambat ke penyimpanan (SSTORE). Oleh karena itu, untuk setiap op-code di udara harganya disediakan, tergantung pada konsumsi sumber dayanya.
Jadwal Biaya dari spesifikasi protokol
Tabel aliran gas untuk berbagai jenis operasi. Dari spesifikasi protokol
Kertas Kuning Ethereum .
Tidak seperti Bitcoin, pengirim transaksi Ethereum tidak menetapkan komisi dalam cryptocurrency, tetapi jumlah gas maksimum yang bersedia ia keluarkan - mulai
Gas dan harga per unit gas - Harga gas. Ketika mesin virtual mengeksekusi kode, jumlah gas untuk setiap operasi selanjutnya dikurangi dari startGas sampai keluar dari kode tercapai atau gas habis. Rupanya, itulah mengapa nama aneh digunakan untuk unit kerja ini - transaksi diisi dengan gas seperti mobil, dan itu akan mencapai titik tujuan atau tidak tergantung pada apakah ada cukup volume yang diisi dalam tangki. Setelah menyelesaikan eksekusi kode, jumlah udara yang diterima dengan mengalikan gas yang dikonsumsi sebenarnya dengan harga yang ditentukan oleh pengirim (
wei per gas) didebit dari pengirim transaksi. Dalam jaringan global, ini terjadi pada saat "menambang" blok, yang mencakup transaksi yang sesuai, dan dalam lingkungan Remix, transaksi tersebut "ditambang" secara instan, gratis dan tanpa syarat apa pun.
Alat kami - Remix IDE
Untuk "profiling" konsumsi gas, kami akan menggunakan lingkungan online untuk mengembangkan kontrak Ethereum dari
IDE Remix . IDE ini berisi editor kode penyorotan sintaks, penampil artefak, render antarmuka kontrak, debugger visual mesin virtual, kompiler JS dari semua versi yang mungkin dan banyak alat penting lainnya. Saya sangat merekomendasikan memulai studi eter dengannya. Kelebihan lainnya adalah tidak memerlukan instalasi - cukup buka di browser dari
situs resmi .
Pemilihan Jenis Variabel
Spesifikasi bahasa Solidity menawarkan pengembang sebanyak tiga puluh dua bit tipe integer uint - dari 8 hingga 256 bit. Bayangkan Anda sedang mengembangkan kontrak pintar yang dirancang untuk menyimpan usia seseorang selama bertahun-tahun. Berapa kedalaman bit yang tidak Anda pilih?
Akan sangat wajar untuk memilih jenis minimum yang cukup untuk tugas tertentu - uint8 akan cocok secara matematis di sini. Adalah logis untuk mengasumsikan bahwa semakin kecil objek yang kita simpan di blockchain dan semakin sedikit memori yang kita habiskan untuk eksekusi, semakin sedikit overhead yang kita miliki, semakin sedikit kita membayar. Tetapi dalam kebanyakan kasus, asumsi ini akan salah.
Untuk percobaan, kami mengambil kontrak paling sederhana dari apa yang ditawarkan
dokumentasi Solidity resmi dan mengumpulkannya dalam dua versi - menggunakan tipe variabel uint256 dan tipe 32 kali lebih kecil - uint8.
simpleStorage_uint256.sol pragma solidity ^0.4.0; contract SimpleStorage { //uint is alias for uint256 uint storedData; function set(uint x) public { storedData = x; } function get() public view returns (uint) { return storedData; } }
pragma solidity ^0.4.0; contract SimpleStorage { //uint is alias for uint256 uint storedData; function set(uint x) public { storedData = x; } function get() public view returns (uint) { return storedData; } }
simpleStorage_uint8.sol pragma solidity ^0.4.0; contract SimpleStorage { uint8 storedData; function set(uint8 x) public { storedData = x; } function get() public view returns (uint) { return storedData; } }
Mengukur “tabungan”
Jadi, kontrak dibuat, dimuat ke dalam Remix, digunakan, dan metode panggilan ke .set () dijalankan oleh transaksi. Apa yang kita lihat
Merekam tipe lama lebih mahal daripada yang pendek - 20464 versus 20205 unit gas! Bagaimana? Mengapa Mari kita cari tahu!

Simpan uint8 vs uint256
Menulis ke penyimpanan persisten adalah salah satu operasi termahal dalam protokol karena alasan yang jelas: pertama, merekam keadaan meningkatkan jumlah ruang disk yang diperlukan oleh node penuh. Ukuran penyimpanan ini terus meningkat, dan semakin banyak status disimpan di node, semakin lambat sinkronisasi terjadi, semakin tinggi persyaratan infrastruktur (ukuran partisi, jumlah iops). Pada waktu puncak, operasi IO disk lambat yang menentukan kinerja seluruh jaringan.
Adalah logis untuk mengharapkan bahwa penyimpanan uint8 akan berharga puluhan kali lebih murah daripada uint256. Namun, dalam debugger Anda dapat melihat bahwa kedua nilai tersebut terletak persis sama di slot penyimpanan sebagai nilai 256-bit.

Dan dalam kasus khusus ini, penggunaan uint8 tidak memberikan keuntungan apa pun dalam biaya penulisan ke penyimpanan.
Menangani uint8 vs uint256
Mungkin kita akan mendapat manfaat ketika bekerja dengan uint8 jika tidak selama penyimpanan, maka setidaknya saat memanipulasi data dalam memori? Berikut ini membandingkan instruksi untuk fungsi yang sama yang diperoleh untuk berbagai jenis variabel.

Anda dapat melihat bahwa operasi dengan uint8 memiliki lebih
banyak instruksi daripada uint256. Ini karena mesin mengubah nilai 8-bit ke kata 256-bit asli, dan sebagai hasilnya, kode dikelilingi oleh instruksi tambahan yang dibayar pengirim. Tidak hanya menulis, tetapi juga mengeksekusi kode dengan tipe uint8 dalam hal ini lebih mahal.
Di mana penggunaan tipe pendek dapat dibenarkan?
Tim kami telah terlibat dalam audit kontrak cerdas untuk waktu yang lama, dan sejauh ini belum ada kasus praktis tunggal di mana penggunaan tipe kecil dalam kode yang disediakan untuk audit akan mengarah pada penghematan. Sementara itu, dalam beberapa kasus yang sangat spesifik, penghematan secara teori dimungkinkan. Misalnya, jika kontrak Anda menyimpan sejumlah besar variabel atau struktur status kecil, maka mereka dapat dikemas ke dalam slot penyimpanan yang lebih sedikit.
Perbedaannya akan paling jelas dalam contoh berikut:
1. kontrak dengan 32 variabel uint256
simpleStorage_32x_uint256.sol pragma solidity ^0.4.0; contract SimpleStorage { uint storedData1; uint storedData2; uint storedData3; uint storedData4; uint storedData5; uint storedData6; uint storedData7; uint storedData8; uint storedData9; uint storedData10; uint storedData11; uint storedData12; uint storedData13; uint storedData14; uint storedData15; uint storedData16; uint storedData17; uint storedData18; uint storedData19; uint storedData20; uint storedData21; uint storedData22; uint storedData23; uint storedData24; uint storedData25; uint storedData26; uint storedData27; uint storedData28; uint storedData29; uint storedData30; uint storedData31; uint storedData32; function set(uint x) public { storedData1 = x; storedData2 = x; storedData3 = x; storedData4 = x; storedData5 = x; storedData6 = x; storedData7 = x; storedData8 = x; storedData9 = x; storedData10 = x; storedData11 = x; storedData12 = x; storedData13 = x; storedData14 = x; storedData15 = x; storedData16 = x; storedData17 = x; storedData18 = x; storedData19 = x; storedData20 = x; storedData21 = x; storedData22 = x; storedData23 = x; storedData24 = x; storedData25 = x; storedData26 = x; storedData27 = x; storedData28 = x; storedData29 = x; storedData30 = x; storedData31 = x; storedData32 = x; } function get() public view returns (uint) { return storedData1; } }
2. kontrak dengan 32 variabel uint8
simpleStorage_32x_uint8.sol pragma solidity ^0.4.0; contract SimpleStorage { uint8 storedData1; uint8 storedData2; uint8 storedData3; uint8 storedData4; uint8 storedData5; uint8 storedData6; uint8 storedData7; uint8 storedData8; uint8 storedData9; uint8 storedData10; uint8 storedData11; uint8 storedData12; uint8 storedData13; uint8 storedData14; uint8 storedData15; uint8 storedData16; uint8 storedData17; uint8 storedData18; uint8 storedData19; uint8 storedData20; uint8 storedData21; uint8 storedData22; uint8 storedData23; uint8 storedData24; uint8 storedData25; uint8 storedData26; uint8 storedData27; uint8 storedData28; uint8 storedData29; uint8 storedData30; uint8 storedData31; uint8 storedData32; function set(uint8 x) public { storedData1 = x; storedData2 = x; storedData3 = x; storedData4 = x; storedData5 = x; storedData6 = x; storedData7 = x; storedData8 = x; storedData9 = x; storedData10 = x; storedData11 = x; storedData12 = x; storedData13 = x; storedData14 = x; storedData15 = x; storedData16 = x; storedData17 = x; storedData18 = x; storedData19 = x; storedData20 = x; storedData21 = x; storedData22 = x; storedData23 = x; storedData24 = x; storedData25 = x; storedData26 = x; storedData27 = x; storedData28 = x; storedData29 = x; storedData30 = x; storedData31 = x; storedData32 = x; } function get() public view returns (uint) { return storedData1; } }
Penerapan kontrak pertama (32 uint256) akan lebih murah - hanya 89941 gas, tetapi .set () akan jauh lebih mahal karena Ini akan menempati 256 slot dalam penyimpanan, yang akan menelan biaya 640.639 gas untuk setiap panggilan. Kontrak kedua (32 uint8) akan menjadi dua setengah kali lebih mahal ketika menggunakan (221663 gas), tetapi setiap panggilan ke metode .set () akan jauh lebih murah, karena hanya mengubah satu sel panggung (185291 gas).
Haruskah optimasi seperti itu diterapkan?
Seberapa signifikan pengaruh optimasi tipe adalah titik diperdebatkan. Seperti yang Anda lihat, bahkan untuk case sintetis pilihan khusus, kami
tidak mendapatkan banyak perbedaan. Pilihan untuk menggunakan uint8 atau uint256 lebih merupakan ilustrasi fakta bahwa optimasi harus diterapkan secara bermakna (dengan pemahaman tentang alat, pembuatan profil), atau tidak memikirkannya sama sekali. Berikut ini beberapa pedoman umum:
- jika kontrak berisi banyak angka kecil atau struktur kompak dalam repositori, maka Anda bisa memikirkan pengoptimalan;
- jika Anda menggunakan tipe "disingkat" - ingat tentang kerentanan over-/ under-flow ;
- untuk variabel memori dan argumen fungsi yang tidak ditulis ke repositori, selalu lebih baik menggunakan tipe asli uint256 (atau alias uintnya). Misalnya, tidak masuk akal untuk mengatur daftar iterator ke uint8 - hilang saja;
- Yang sangat penting untuk pengemasan yang benar dalam slot penyimpanan untuk kompiler adalah urutan variabel dalam kontrak .
Referensi
Saya akan berakhir dengan saran yang tidak memiliki kontraindikasi: bereksperimen dengan alat pengembangan, mengetahui spesifikasi bahasa, perpustakaan, dan kerangka kerja. Berikut adalah tautan yang paling berguna, menurut saya, untuk mulai belajar tentang platform Ethereum:
- Lingkungan pengembangan kontrak Remix adalah IDE berbasis browser yang sangat fungsional;
- Spesifikasi bahasa Solidity , tautannya akan secara khusus ke bagian Tata Letak Variabel Negara;
- Repositori kontrak yang sangat menarik dari tim OpenZeppelin yang terkenal. Contoh penerapan token, kontrak crowdsale, dan yang paling penting - pustaka SafeMath , yang membantu bekerja dengan aman dengan tipe;
- Ethereum Yellow Paper , spesifikasi formal mesin virtual Ethereum;
- Ethereum White Paper , spesifikasi platform Ethereum, dokumen yang lebih umum dan tingkat tinggi dengan sejumlah besar tautan;
- Ethereum dalam 25 menit , pengantar teknis singkat namun luas untuk Ethereum dari pencipta platform, Vitalik Buterin;
- Etherscan blockchain explorer , jendela ke dunia eter nyata, peramban blok, transaksi, token, kontrak di jaringan utama. Di Etherscan, Anda akan menemukan penjelajah untuk jaringan uji Rinkeby, Ropsten, Kovan (jaringan dengan siaran gratis, dibangun di atas protokol konsensus yang berbeda).