ด้วยการปฏิบัติตามแนวทางปฏิบัติเหล่านี้นักพัฒนาสามารถลดการใช้ก๊าซในสัญญาอัจฉริยะลดต้นทุนการทําธุรกรรมและสร้างแอปพลิเคชันที่มีประสิทธิภาพและใช้งานง่ายขึ้น
ค่าธรรมเนียมแก๊สบน Ethereum mainnet เป็นปัญหาใหญ่เสมอ โดยเฉพาะในช่วงเวลาของการแย่งชิงเครือข่าย ในช่วงเวลาสูงสุดผู้ใช้งานต้องชำระค่าธรรมเนียมการทำธุรกรรมที่สูงมาก ดังนั้นการปรับปรุงค่าธรรมเนียมแก๊สในช่วงการพัฒนาสัญญาอัจฉริยะเป็นสิ่งสำคัญ การปรับปรุงค่าธรรมเนียมแก๊สไม่เพียงช่วยลดค่าธรรมเนียมการทำธุรกรรมได้อย่างมีประสิทธิภาพ แต่ยังช่วยปรับปรุงประสิทธิภาพการทำธุรกรรมเพื่อให้ผู้ใช้ได้รับประสบการณ์บล็อกเชนที่มีความคุ้มค่าและมีประสิทธิภาพมากขึ้น
บทความนี้จะอธิบายกลไกค่าธรรมเนียมแก๊สของเครื่องมือเสมือนจริง Ethereum (EVM) แนวคิดหลักที่เกี่ยวข้องกับการปรับปรุงค่าธรรมเนียมแก๊ส และแนวทางการปรับปรุงค่าธรรมเนียมแก๊สเมื่อพัฒนาสัญญาอัจฉริยะ หวังว่าเนื้อหานี้จะสร้างแรงบันดาลใจและช่วยเหลือนักพัฒนา พร้อมทั้งช่วยให้ผู้ใช้ทั่วไปเข้าใจระบบค่าธรรมเนียมแก๊สของ EVM และแก้ไขความท้าทายภายในระบบบล็อกเชน
ในเครือข่ายที่เข้ากันได้กับ EVM, "แก๊ส" หมายถึงหน่วยที่ใช้ในการวัดพลังคำนวณที่จำเป็นในการดำเนินการที่เฉพาะเจาะจง
แผนภาพด้านล่างแสดงโครงสร้างของ EVM ในแผนภาพ การใช้ Gas ถูกแบ่งออกเป็นสามส่วน: การดำเนินการปฏิบัติ, การเรียกข้อความภายนอก, และการอ่าน/เขียนหน่วยความจำและพื้นที่
แหล่งที่มา: เว็บไซต์อย่างเป็นทางการของ Ethereum[1]
ตั้งแต่เริ่มใช้งาน EIP-1559 (London Hard Fork) ค่าธรรมเนียมแก๊สถูกคำนวณโดยใช้สูตรต่อไปนี้:
ค่าธรรมเนียมแก๊ส = หน่วยแก๊สที่ใช้ * (ค่าธรรมเนียมหลัก + ค่าธรรมเนียมลำดับความสำคัญ)
ค่าธรรมเนียมพื้นฐานถูกเผาในขณะที่ค่าธรรมเนียมลําดับความสําคัญทําหน้าที่เป็นแรงจูงใจในการกระตุ้นให้ผู้ตรวจสอบความถูกต้องรวมธุรกรรมไว้ในบล็อกเชน การตั้งค่าค่าธรรมเนียมลําดับความสําคัญที่สูงขึ้นเมื่อส่งธุรกรรมจะเพิ่มโอกาสที่ธุรกรรมจะรวมอยู่ในบล็อกถัดไป สิ่งนี้คล้ายกับ "เคล็ดลับ" ที่ผู้ใช้จ่ายให้กับผู้ตรวจสอบความถูกต้อง
เมื่อคอมไพล์สัญญาอัจฉริยะด้วย Solidity สัญญาจะถูกแปลงเป็นชุดของ "operation codes" หรือ opcodes
ทุกคำสั่ง (เช่น การสร้างสัญญา, ทำการเรียกข้อความ, เข้าถึงการเก็บข้อมูลบัญชี, และดำเนินการบนเครื่องจำลอง) มีค่าใช้จ่ายแก๊สที่เกี่ยวข้องที่ได้รับการเอกสารเอกสารใน Ethereum Yellow Paper[2]
หลังจากการแก้ไข EIP หลายรอบ ค่าแก๊สของบาง opcodes ได้รับการปรับเปลี่ยน ซึ่งอาจแตกต่างจากค่าในกระดาษสีเหลือง สำหรับข้อมูลเพิ่มเติมเกี่ยวกับค่า opcodes ล่าสุด โปรดอ้างอิงที่แหล่งที่มานี้ [3]
แนวคิดหลักของการปรับปรุงแก๊สคือการจัดลำดับการดำเนินการที่มีความประหยัดต้นทุนบนโซ่บล็อก EVM และหลีกเลี่ยงการดำเนินการที่เกิดค่าแก๊สสูง
ใน EVM การดำเนินการต่อไปนี้เป็นราคาถูกสุด:
การดำเนินงานที่มีค่าใช้จ่ายสูง ประกอบด้วย:
โดยอิงจากแนวคิดพื้นฐานดังกล่าว เราได้รวบรวมรายการหลักการแก้ปัญหาค่า Gas ที่ดีที่สุดสำหรับชุมชนนักพัฒนา โดยการปฏิบัติตามหลักการเหล่านี้ นักพัฒนาสามารถลดการใช้ Gas ของสัญญาอัจฉริยะลง ลดค่าธรรมเนียมการทำธุรกรรม และสร้างแอปพลิเคชันที่มีประสิทธิภาพและใช้งานง่ายมากขึ้นได้
ใน Solidity Storage เป็นทรัพยากรที่จำกัด และการบริโภคแก๊สของมันสูงมากกว่าหน่วยความจำ ทุกครั้งที่สัญญาอัจฉริยะอ่านหรือเขียนไปยังการเก็บรักษา จะต้องเสียค่าแก๊สสูง
ตามนิยามใน Ethereum Yellow Paper ค่าใช้จ่ายของการดำเนินการเก็บข้อมูลสูงกว่าการดำเนินการหน่วยความจำมากกว่า 100 เท่า ตัวอย่างเช่น โอ๊ปโค้ดเช่น sload และ sstore มีค่าใช้จ่ายอย่างน้อย 100 หน่วยแก๊สในกรณีที่ดีที่สุดในขณะที่การดำเนินการหน่วยความจำเช่น mload และ mstore ใช้เพียง 3 หน่วยแก๊สเท่านั้น
วิธีการ ที่ จำกัด การใช้พื้นที่จัดเก็บ รวมถึง:
จำนวนช่องจัดเก็บที่ใช้ในสัญญาอัจฉริยะและวิธีที่นักพัฒนาแสดงข้อมูลสามารถมีผลต่อการใช้แก๊สได้อย่างมีนัยสำคัญ
คอมไพล์เลอร์ Solidity จัดแพ็คตัวแปรที่เก็บในหน่วยความจำต่อเนื่อง ในกระบวนการคอมไพล์โดยใช้ช่องเก็บข้อมูลขนาด 32 ไบต์เป็นหน่วยพื้นฐานสำหรับการเก็บข้อมูลตัวแปร การแพ็คตัวแปรหมายถึงการจัดเรียงตัวแปรให้อยู่ในรูปแบบที่อนุญาตให้ตัวแปรหลายตัวอยู่ในช่องเก็บเดียว
ด้านซ้ายคือการปฏิบัติที่มีประสิทธิภาพน้อยที่สุดซึ่งใช้บริการห้องเก็บของ 3 ช่อง และด้านขวาคือการปฏิบัติที่มีประสิทธิภาพมากกว่า
โดยการทำการปรับเปลี่ยนนี้, นักพัฒนาสามารถประหยัดแก๊สหน่วย 20,000 หน่วย (เนื่องจากการเก็บข้อมูลในช่องเก็บข้อมูลที่ไม่ได้ใช้จะมีค่าใช้จ่ายแก๊ส 20,000 หน่วย), แต่ตอนนี้เพียงสองช่องเก็บข้อมูลเท่านั้นที่จำเป็น
เนื่องจากแต่ละช่องจัดเก็บใช้แก๊ส การบรรจุตัวแปรช่วยเพิ่มประสิทธิภาพการใช้แก๊สโดยลดจำนวนช่องจัดเก็บที่ต้องการ
ตัวแปรสามารถแสดงได้โดยใช้ประเภทข้อมูลที่แตกต่างกัน แต่ต้นทุนการดำเนินการจะแตกต่างกันขึ้นอยู่กับประเภท การเลือกใช้ประเภทข้อมูลที่เหมาะสมช่วยให้ใช้แก๊สได้อย่างมีประสิทธิภาพ
ตัวอย่างเช่นใน Solidity จะสามารถแบ่งส่วนตัวเลขเป็นขนาดต่าง ๆ ได้ เช่น uint8, uint16, uint32 เป็นต้น เนื่องจาก EVM ทำงานในหน่วยข้อมูล 256 บิต การใช้ uint8 หมายถึง EVM ต้องแปลงมันเป็น uint256 ก่อน และการแปลงนี้ทำให้เกิดค่า Gas เพิ่มเติม
เราสามารถเปรียบเทียบค่า Gas ของ uint8 และ uint256 โดยใช้โค้ดในแผนภาพ ฟังก์ชัน UseUint() ใช้หน่วย Gas 120,382 หน่วย ในขณะที่ฟังก์ชัน UseUInt8() ใช้หน่วย Gas 166,111 หน่วย
นอกจากนี้ใช้ uint256 เป็นราคาถูกกว่า uint8 แต่หากเราใช้การปรับแต่งการบรรจุตัวแปรที่แนะนำไว้ก่อนหน้านี้จะทำให้เกิดความแตกต่าง หากนักพัฒนาสามารถบรรจุตัวแปร uint8 สี่ตัวลงในสตอเรจเดียวกันตำแหน่งเดียวกัน ต้นทุนรวมของการวนซ้ำที่จะต้องตรวจสอบค่าทั้งหมดจะต่ำกว่าการใช้ตัวแปร uint256 สี่ตัว ในกรณีนี้สัญญาอัจฉริยะสามารถอ่านและเขียนสตอเรจเดียวครั้งเดียวและโหลดตัวแปร uint8 สี่ตัวลงในหน่วยความจำ/การเก็บข้อมูลในการดำเนินการเดียวกัน
หากข้อมูลสามารถจำกัดได้เป็น 32 ไบต์ แนะนำให้ใช้ชนิดข้อมูล bytes32 แทน bytes หรือ strings โดยทั่วไปตัวแปรขนาดคงที่ใช้เก็บแก๊สน้อยกว่าตัวแปรขนาดเปลี่ยนไปได้ หากความยาวของไบต์สามารถจำกัดได้ ลองเลือกความยาวที่เล็กที่สุดจาก bytes1 ถึง bytes32
ใน Solidity รายการข้อมูลสามารถแสดงได้โดยใช้ประเภทข้อมูลสองประเภท: อาร์เรย์และแมปปิง แต่ละประเภทมีไวยากรณ์และโครงสร้างที่แตกต่างกัน
การแมปข้อมูลทั่วไปจะมีประสิทธิภาพและมีความคุ้มค่าทางเศรษฐกิจมากกว่าในส่วนใหญ่ของกรณี ในขณะที่อาร์เรย์สามารถทำงานได้และรองรับการแพ็คข้อมูลประเภทต่างๆ ดังนั้น แนะนำให้ใช้การแมปข้อมูลเมื่อจัดการรายการข้อมูล ยกเว้นกรณีที่จำเป็นต้องวนซ้ำหรือการบริโภคแก๊สสามารถจัดการได้ด้วยการแพ็คข้อมูลประเภท
ตัวแปรที่ถูกประกาศในพารามิเตอร์ของฟังก์ชันสามารถเก็บไว้ใน calldata หรือ memory ได้ ความแตกต่างหลักคือ memory สามารถถูกแก้ไขโดยฟังก์ชันในขณะที่ calldata ไม่เปลี่ยนแปลง
จำข้อนี้ให้มีความจำไว้: หากพารามิเตอร์ของฟังก์ชันเป็นแบบอ่านได้อย่างเดียว ควรใช้ calldata แทน memory นี้จะช่วยลดการคัดลอกที่ไม่จำเป็นจาก function calldata ไปยัง memory
ตัวอย่างที่ 1: ใช้หน่วยความจำ
เมื่อใช้คำสำคัญ memory ค่าของอาร์เรย์จะถูกคัดลอกจากข้อมูลที่ถูกเข้ารหัสไปยังหน่วยความจำระหว่างการถอดรหัส ABI ค่าใช้จ่ายในการทำงานของบล็อกโค้ดนี้คือ 3,694 หน่วยแก๊ส
ตัวอย่างที่ 2: การใช้ calldata
เมื่ออ่านค่าโดยตรงจาก calldata การดำเนินการหน่วยความจำชั่วคราวถูกข้าม การปรับปรุงนี้ลดค่าการดำเนินงานเหลือเพียง 2,413 หน่วยแก๊ส ทำให้ประสิทธิภาพของแก๊สเพิ่มขึ้น 35%
ตัวแปรคงที่ / แปลงไม่ได้จัดเก็บในการจัดเก็บข้อมูลของสัญญา ตัวแปรเหล่านี้จะถูกคำนวณในเวลาคอมไพล์และจัดเก็บใน bytecode ของสัญญา ดังนั้นค่าในการเข้าถึงของพวกเขาจะต่ำกว่าตัวแปรการเก็บข้อมูล แนะนำให้ใช้คีย์เวิร์ด Constant หรือ Immutable เมื่อเป็นไปได้
เมื่อนักพัฒนาสามารถมั่นใจได้ว่าการดำเนินการทางคณิตศาสตร์จะไม่เกิดการเกินหรือขาดลอย พวกเขาสามารถใช้คำสั่ง unchecked ที่นำเสนอใน Solidity v0.8.0 เพื่อหลีกเลี่ยงการตรวจสอบการเกินหรือขาดลอยที่ไม่จำเป็น ซึ่งจะช่วยประหยัดค่าแก๊สได้อย่างมาก
ในภาพแผนภาพด้านล่าง, เงื่อนไขที่ถูก จำกัดเงื่อนไข i
นอกจากนี้เสียทั้งนี้ รุ่นคอมไพเลอร์ 0.8.0 และสูงกว่าไม่ต้องใช้ SafeMath library อีกต่อไป เนื่องจากคอมไพเลอร์เองตอนนี้มีการป้องกันการล้นและการล้มเหลวที่มีอยู่แล้วในตัว
รหัสของตัวดัดแปลงถูกฝังอยู่ในฟังก์ชันที่เขาแก้ไข ทุกครั้งที่ใช้ตัวดัดแปลงรหัสจะทำซ้ำซึ่งเพิ่มขนาด bytecode และเพิ่มการบริโภคแก๊ส นี่คือวิธีหนึ่งในการปรับปรุงต้นทุนแก๊สของตัวดัดแปลง:
ก่อนการปรับปรุง:
หลังจากการปรับปรุง:
ในตัวอย่างนี้ โดยการทำการระบายโค้ดเข้าไปในฟังก์ชันภายใน _checkOwner() ซึ่งสามารถนำกลับมาใช้ในตัวดัดแปลงได้ ขนาดของ bytecode ถูกลดลงและต้นทุน Gas ลดลง
สำหรับตัวดำเนินการ || (OR) และ && (AND) การดำเนินการตรรกะจะถูกประเมินด้วยการ short-circuiting ซึ่งหมายถึงหากเงื่อนไขแรกเพียงพอที่จะกำหนดผลลัพธ์ของนิพจน์ตรรกะ เงื่อนไขที่สองจะไม่ถูกประเมิน
เพื่อเพิ่มประสิทธิภาพในการใช้แก๊ส ควรวางเงื่อนไขที่มีค่าการคำนวณต่ำกว่าไว้ก่อน โดยที่คำนวณที่มีค่าสูงอาจถูกระบายไป
หากมีฟังก์ชันหรือตัวแปรที่ไม่ได้ใช้ในสัญญา แนะนำให้ลบออก เป็นวิธีที่เร็วที่สุดในการลดต้นทุนในการเปิดใช้สัญญาและให้ขนาดของสัญญาเล็ก
นี่คือคำแนะนำที่เป็นประโยชน์:
ใช้อัลกอริทึมที่มีประสิทธิภาพสูงสุดสำหรับการคำนวณ หากสัญญาใช้ผลลัพธ์การคำนวณบางอย่างโดยตรง ควรลบการคำนวณที่ไม่จำเป็น ในทางปฏิบัติ ควรลบการคำนวณที่ไม่ได้ใช้งานออกไป เมื่อใช้ Ethereum นักพัฒนาสามารถรับรางวัลแก๊สโดยการปล่อยพื้นที่การเก็บข้อมูล หากตัวแปรไม่ได้ใช้งานอีกต่อไป ควรลบโดยใช้คำสั่ง delete หรือตั้งค่าเป็นค่าเริ่มต้นของมัน
การปรับปรุงลูป: หลีกเลี่ยงการดำเนินการลูปที่มีค่าใช้จ่ายสูง พยายามรวมลูปและย้ายการคำนวณที่ซ้ำกันออกจากเนื้อหาของลูป
สัญญาที่ถูกคอมไพล์ล่วงหน้าให้บริการฟังก์ชันห้องสมุดที่ซับซ้อน เช่นการเข้ารหัสลับและการดำเนินการแฮซชิ่ง โดยเนื่องจากโค้ดไม่ได้รันบน EVM แต่ทำงานในเครื่องลูกข่ายของลูกค้า จึงต้องใช้แก๊สน้อยกว่า การใช้สัญญาที่ถูกคอมไพล์ล่วงหน้าสามารถประหยัดแก๊สได้โดยลดภาระงานการคำนวณที่ต้องการในการดำเนินการสัญญาอัจฉริยะ
ตัวอย่างของสัญญาที่ถูกคอมไพล์ล่วงหน้ารวมถึง Elliptic Curve Digital Signature Algorithm (ECDSA) และ SHA2-256 hashing algorithm โดยการใช้สัญญาที่ถูกคอมไพล์ล่วงหน้าเหล่านี้ในสัญญาอัจฉริยะ นักพัฒนาสามารถลดค่า Gas และเพิ่มประสิทธิภาพของแอปพลิเคชัน
สำหรับรายการสมบูรณ์ของสัญญาที่ได้รับการเตรียมไว้ล่วงหน้าที่รองรับโดยเครือข่าย Ethereum โปรดอ้างอิงที่ลิงก์นี้ [4]
Inline assembly ช่วยให้นักพัฒนาเขียนโค้ดระดับต่ำที่มีประสิทธิภาพซึ่งสามารถถูกดำเนินการโดย EVM โดยตรงโดยไม่ต้องใช้ Solidity opcodes ที่แพง การประกอบแบบอินไลน์ยังช่วยให้มีการควบคุมที่แม่นยำกว่าเรื่องการใช้พื้นที่หน่วยความจำและพื้นที่เก็บข้อมูลที่ลดต้นทุน Gas ได้อีกด้วย นอกจากนี้ inline assembly สามารถทำงานบางอย่างที่ซับซ้อนซึ่งยากที่จะปฏิบัติด้วย Solidity เพียงอย่างเดียว มอบความยืดหยุ่นมากขึ้นสำหรับการปรับปรุงการใช้ Gas
นี่คือตัวอย่างการใช้การประชุมแบบอินไลน์เพื่อประหยัดแก๊ส:
ดังที่เห็นในตัวอย่างข้างต้น กรณีที่สองที่ใช้การประชุมแบบอินไลน์มีประสิทธิภาพแก๊สสูงกว่ากรณีมาตรฐาน
อย่างไรก็ตามการใช้งานตัวแอสเซมบลีแบบอินไลน์ยังสามารถเสี่ยงต่อความเสี่ยงและมีโอกาสเกิดข้อผิดพลาดได้ ดังนั้นควรใช้ด้วยความระมัดระวังและแนะนำให้ใช้เฉพาะสำหรับนักพัฒนาที่มีประสบการณ์เท่านั้น
Layer 2 solutions สามารถลดปริมาณข้อมูลที่ต้องเก็บรักษาและคำนวณบน Ethereum mainnet ลงได้
Layer 2 solutions like rollups, sidechains, and state channels offload transaction processing from the main Ethereum chain, enabling faster and cheaper transactions.
โดยการรวมการทำธุรกรรมจำนวนมากเข้าด้วยกัน สิ่งที่ทำให้จำนวนการทำธุรกรรมบนเชื่อมต่อลดลง ซึ่งในลำดับต่อไปจะลดค่าธรรมเนียมแก๊ส การใช้ Layer 2 solutions ยังเสริมสร้างความยืดหยุ่นของ Ethereum ทำให้ผู้ใช้และแอปพลิเคชันมากขึ้นสามารถเข้าร่วมในเครือข่ายได้มากขึ้นโดยไม่เกิดคองเจสชันจากการเรียกใช้เกินไป
มีเครื่องมือการปรับปรุงหลายรูปแบบที่ใช้ได้ เช่นตัวปรับปรุง solc, ตัวปรับปรุงการสร้างของ Truffle และคอมไพเลอร์ Solidity ของ Remix
เครื่องมือเหล่านี้สามารถช่วยลดขนาดไบต์โค้ดลบโค้ดที่ไม่ได้ใช้และลดจํานวนการดําเนินการที่จําเป็นในการดําเนินการสัญญาอัจฉริยะ เมื่อรวมกับไลบรารีการเพิ่มประสิทธิภาพก๊าซอื่น ๆ เช่น "solmate" นักพัฒนาสามารถลดต้นทุนก๊าซได้อย่างมีประสิทธิภาพและปรับปรุงประสิทธิภาพของสัญญาอัจฉริยะ
การปรับปรุงการใช้แก๊สเป็นขั้นตอนที่สำคัญสำหรับนักพัฒนา เนื่องจากมันไม่เพียงทำให้ค่าธุรกรรมลดลงเท่านั้น แต่ยังเพิ่มประสิทธิภาพของสัญญาอัจฉริยะบนเครือข่ายที่เข้ากันได้กับ EVM โดยการจัดลำดับการดำเนินงานที่ประหยัดต้นทุน ลดการใช้พื้นที่จัดเก็บ ใช้งานชุดคำสั่งอินไลน์ และปฏิบัติที่ดีอื่น ๆ ตามที่ได้รับการอภิปรายในบทความนี้ นักพัฒนาสามารถลดการใช้แก๊สของสัญญาได้อย่างมีประสิทธิภาพ
อย่างไรก็ตาม สำคัญที่จะทราบว่า ระหว่างกระบวนการปรับปรุง นักพัฒนาต้องระมัดระวังเพื่อหลีกเลี่ยงการเปิดเผยช่องโหว่ด้านความปลอดภัย ในกระบวนการปรับปรุงรหัสและลดการใช้ แก๊ส ควรไม่มีการเสี่ยงเสียความปลอดภัยที่เป็นธรรม
[1]https://ethereum.org/th/developers/docs/gas/
[2] https://ethereum.github.io/yellowpaper/paper.pdf
[3]https://www.evm.codes/
[4]https://www.evm.codes/precompiled
ด้วยการปฏิบัติตามแนวทางปฏิบัติเหล่านี้นักพัฒนาสามารถลดการใช้ก๊าซในสัญญาอัจฉริยะลดต้นทุนการทําธุรกรรมและสร้างแอปพลิเคชันที่มีประสิทธิภาพและใช้งานง่ายขึ้น
ค่าธรรมเนียมแก๊สบน Ethereum mainnet เป็นปัญหาใหญ่เสมอ โดยเฉพาะในช่วงเวลาของการแย่งชิงเครือข่าย ในช่วงเวลาสูงสุดผู้ใช้งานต้องชำระค่าธรรมเนียมการทำธุรกรรมที่สูงมาก ดังนั้นการปรับปรุงค่าธรรมเนียมแก๊สในช่วงการพัฒนาสัญญาอัจฉริยะเป็นสิ่งสำคัญ การปรับปรุงค่าธรรมเนียมแก๊สไม่เพียงช่วยลดค่าธรรมเนียมการทำธุรกรรมได้อย่างมีประสิทธิภาพ แต่ยังช่วยปรับปรุงประสิทธิภาพการทำธุรกรรมเพื่อให้ผู้ใช้ได้รับประสบการณ์บล็อกเชนที่มีความคุ้มค่าและมีประสิทธิภาพมากขึ้น
บทความนี้จะอธิบายกลไกค่าธรรมเนียมแก๊สของเครื่องมือเสมือนจริง Ethereum (EVM) แนวคิดหลักที่เกี่ยวข้องกับการปรับปรุงค่าธรรมเนียมแก๊ส และแนวทางการปรับปรุงค่าธรรมเนียมแก๊สเมื่อพัฒนาสัญญาอัจฉริยะ หวังว่าเนื้อหานี้จะสร้างแรงบันดาลใจและช่วยเหลือนักพัฒนา พร้อมทั้งช่วยให้ผู้ใช้ทั่วไปเข้าใจระบบค่าธรรมเนียมแก๊สของ EVM และแก้ไขความท้าทายภายในระบบบล็อกเชน
ในเครือข่ายที่เข้ากันได้กับ EVM, "แก๊ส" หมายถึงหน่วยที่ใช้ในการวัดพลังคำนวณที่จำเป็นในการดำเนินการที่เฉพาะเจาะจง
แผนภาพด้านล่างแสดงโครงสร้างของ EVM ในแผนภาพ การใช้ Gas ถูกแบ่งออกเป็นสามส่วน: การดำเนินการปฏิบัติ, การเรียกข้อความภายนอก, และการอ่าน/เขียนหน่วยความจำและพื้นที่
แหล่งที่มา: เว็บไซต์อย่างเป็นทางการของ Ethereum[1]
ตั้งแต่เริ่มใช้งาน EIP-1559 (London Hard Fork) ค่าธรรมเนียมแก๊สถูกคำนวณโดยใช้สูตรต่อไปนี้:
ค่าธรรมเนียมแก๊ส = หน่วยแก๊สที่ใช้ * (ค่าธรรมเนียมหลัก + ค่าธรรมเนียมลำดับความสำคัญ)
ค่าธรรมเนียมพื้นฐานถูกเผาในขณะที่ค่าธรรมเนียมลําดับความสําคัญทําหน้าที่เป็นแรงจูงใจในการกระตุ้นให้ผู้ตรวจสอบความถูกต้องรวมธุรกรรมไว้ในบล็อกเชน การตั้งค่าค่าธรรมเนียมลําดับความสําคัญที่สูงขึ้นเมื่อส่งธุรกรรมจะเพิ่มโอกาสที่ธุรกรรมจะรวมอยู่ในบล็อกถัดไป สิ่งนี้คล้ายกับ "เคล็ดลับ" ที่ผู้ใช้จ่ายให้กับผู้ตรวจสอบความถูกต้อง
เมื่อคอมไพล์สัญญาอัจฉริยะด้วย Solidity สัญญาจะถูกแปลงเป็นชุดของ "operation codes" หรือ opcodes
ทุกคำสั่ง (เช่น การสร้างสัญญา, ทำการเรียกข้อความ, เข้าถึงการเก็บข้อมูลบัญชี, และดำเนินการบนเครื่องจำลอง) มีค่าใช้จ่ายแก๊สที่เกี่ยวข้องที่ได้รับการเอกสารเอกสารใน Ethereum Yellow Paper[2]
หลังจากการแก้ไข EIP หลายรอบ ค่าแก๊สของบาง opcodes ได้รับการปรับเปลี่ยน ซึ่งอาจแตกต่างจากค่าในกระดาษสีเหลือง สำหรับข้อมูลเพิ่มเติมเกี่ยวกับค่า opcodes ล่าสุด โปรดอ้างอิงที่แหล่งที่มานี้ [3]
แนวคิดหลักของการปรับปรุงแก๊สคือการจัดลำดับการดำเนินการที่มีความประหยัดต้นทุนบนโซ่บล็อก EVM และหลีกเลี่ยงการดำเนินการที่เกิดค่าแก๊สสูง
ใน EVM การดำเนินการต่อไปนี้เป็นราคาถูกสุด:
การดำเนินงานที่มีค่าใช้จ่ายสูง ประกอบด้วย:
โดยอิงจากแนวคิดพื้นฐานดังกล่าว เราได้รวบรวมรายการหลักการแก้ปัญหาค่า Gas ที่ดีที่สุดสำหรับชุมชนนักพัฒนา โดยการปฏิบัติตามหลักการเหล่านี้ นักพัฒนาสามารถลดการใช้ Gas ของสัญญาอัจฉริยะลง ลดค่าธรรมเนียมการทำธุรกรรม และสร้างแอปพลิเคชันที่มีประสิทธิภาพและใช้งานง่ายมากขึ้นได้
ใน Solidity Storage เป็นทรัพยากรที่จำกัด และการบริโภคแก๊สของมันสูงมากกว่าหน่วยความจำ ทุกครั้งที่สัญญาอัจฉริยะอ่านหรือเขียนไปยังการเก็บรักษา จะต้องเสียค่าแก๊สสูง
ตามนิยามใน Ethereum Yellow Paper ค่าใช้จ่ายของการดำเนินการเก็บข้อมูลสูงกว่าการดำเนินการหน่วยความจำมากกว่า 100 เท่า ตัวอย่างเช่น โอ๊ปโค้ดเช่น sload และ sstore มีค่าใช้จ่ายอย่างน้อย 100 หน่วยแก๊สในกรณีที่ดีที่สุดในขณะที่การดำเนินการหน่วยความจำเช่น mload และ mstore ใช้เพียง 3 หน่วยแก๊สเท่านั้น
วิธีการ ที่ จำกัด การใช้พื้นที่จัดเก็บ รวมถึง:
จำนวนช่องจัดเก็บที่ใช้ในสัญญาอัจฉริยะและวิธีที่นักพัฒนาแสดงข้อมูลสามารถมีผลต่อการใช้แก๊สได้อย่างมีนัยสำคัญ
คอมไพล์เลอร์ Solidity จัดแพ็คตัวแปรที่เก็บในหน่วยความจำต่อเนื่อง ในกระบวนการคอมไพล์โดยใช้ช่องเก็บข้อมูลขนาด 32 ไบต์เป็นหน่วยพื้นฐานสำหรับการเก็บข้อมูลตัวแปร การแพ็คตัวแปรหมายถึงการจัดเรียงตัวแปรให้อยู่ในรูปแบบที่อนุญาตให้ตัวแปรหลายตัวอยู่ในช่องเก็บเดียว
ด้านซ้ายคือการปฏิบัติที่มีประสิทธิภาพน้อยที่สุดซึ่งใช้บริการห้องเก็บของ 3 ช่อง และด้านขวาคือการปฏิบัติที่มีประสิทธิภาพมากกว่า
โดยการทำการปรับเปลี่ยนนี้, นักพัฒนาสามารถประหยัดแก๊สหน่วย 20,000 หน่วย (เนื่องจากการเก็บข้อมูลในช่องเก็บข้อมูลที่ไม่ได้ใช้จะมีค่าใช้จ่ายแก๊ส 20,000 หน่วย), แต่ตอนนี้เพียงสองช่องเก็บข้อมูลเท่านั้นที่จำเป็น
เนื่องจากแต่ละช่องจัดเก็บใช้แก๊ส การบรรจุตัวแปรช่วยเพิ่มประสิทธิภาพการใช้แก๊สโดยลดจำนวนช่องจัดเก็บที่ต้องการ
ตัวแปรสามารถแสดงได้โดยใช้ประเภทข้อมูลที่แตกต่างกัน แต่ต้นทุนการดำเนินการจะแตกต่างกันขึ้นอยู่กับประเภท การเลือกใช้ประเภทข้อมูลที่เหมาะสมช่วยให้ใช้แก๊สได้อย่างมีประสิทธิภาพ
ตัวอย่างเช่นใน Solidity จะสามารถแบ่งส่วนตัวเลขเป็นขนาดต่าง ๆ ได้ เช่น uint8, uint16, uint32 เป็นต้น เนื่องจาก EVM ทำงานในหน่วยข้อมูล 256 บิต การใช้ uint8 หมายถึง EVM ต้องแปลงมันเป็น uint256 ก่อน และการแปลงนี้ทำให้เกิดค่า Gas เพิ่มเติม
เราสามารถเปรียบเทียบค่า Gas ของ uint8 และ uint256 โดยใช้โค้ดในแผนภาพ ฟังก์ชัน UseUint() ใช้หน่วย Gas 120,382 หน่วย ในขณะที่ฟังก์ชัน UseUInt8() ใช้หน่วย Gas 166,111 หน่วย
นอกจากนี้ใช้ uint256 เป็นราคาถูกกว่า uint8 แต่หากเราใช้การปรับแต่งการบรรจุตัวแปรที่แนะนำไว้ก่อนหน้านี้จะทำให้เกิดความแตกต่าง หากนักพัฒนาสามารถบรรจุตัวแปร uint8 สี่ตัวลงในสตอเรจเดียวกันตำแหน่งเดียวกัน ต้นทุนรวมของการวนซ้ำที่จะต้องตรวจสอบค่าทั้งหมดจะต่ำกว่าการใช้ตัวแปร uint256 สี่ตัว ในกรณีนี้สัญญาอัจฉริยะสามารถอ่านและเขียนสตอเรจเดียวครั้งเดียวและโหลดตัวแปร uint8 สี่ตัวลงในหน่วยความจำ/การเก็บข้อมูลในการดำเนินการเดียวกัน
หากข้อมูลสามารถจำกัดได้เป็น 32 ไบต์ แนะนำให้ใช้ชนิดข้อมูล bytes32 แทน bytes หรือ strings โดยทั่วไปตัวแปรขนาดคงที่ใช้เก็บแก๊สน้อยกว่าตัวแปรขนาดเปลี่ยนไปได้ หากความยาวของไบต์สามารถจำกัดได้ ลองเลือกความยาวที่เล็กที่สุดจาก bytes1 ถึง bytes32
ใน Solidity รายการข้อมูลสามารถแสดงได้โดยใช้ประเภทข้อมูลสองประเภท: อาร์เรย์และแมปปิง แต่ละประเภทมีไวยากรณ์และโครงสร้างที่แตกต่างกัน
การแมปข้อมูลทั่วไปจะมีประสิทธิภาพและมีความคุ้มค่าทางเศรษฐกิจมากกว่าในส่วนใหญ่ของกรณี ในขณะที่อาร์เรย์สามารถทำงานได้และรองรับการแพ็คข้อมูลประเภทต่างๆ ดังนั้น แนะนำให้ใช้การแมปข้อมูลเมื่อจัดการรายการข้อมูล ยกเว้นกรณีที่จำเป็นต้องวนซ้ำหรือการบริโภคแก๊สสามารถจัดการได้ด้วยการแพ็คข้อมูลประเภท
ตัวแปรที่ถูกประกาศในพารามิเตอร์ของฟังก์ชันสามารถเก็บไว้ใน calldata หรือ memory ได้ ความแตกต่างหลักคือ memory สามารถถูกแก้ไขโดยฟังก์ชันในขณะที่ calldata ไม่เปลี่ยนแปลง
จำข้อนี้ให้มีความจำไว้: หากพารามิเตอร์ของฟังก์ชันเป็นแบบอ่านได้อย่างเดียว ควรใช้ calldata แทน memory นี้จะช่วยลดการคัดลอกที่ไม่จำเป็นจาก function calldata ไปยัง memory
ตัวอย่างที่ 1: ใช้หน่วยความจำ
เมื่อใช้คำสำคัญ memory ค่าของอาร์เรย์จะถูกคัดลอกจากข้อมูลที่ถูกเข้ารหัสไปยังหน่วยความจำระหว่างการถอดรหัส ABI ค่าใช้จ่ายในการทำงานของบล็อกโค้ดนี้คือ 3,694 หน่วยแก๊ส
ตัวอย่างที่ 2: การใช้ calldata
เมื่ออ่านค่าโดยตรงจาก calldata การดำเนินการหน่วยความจำชั่วคราวถูกข้าม การปรับปรุงนี้ลดค่าการดำเนินงานเหลือเพียง 2,413 หน่วยแก๊ส ทำให้ประสิทธิภาพของแก๊สเพิ่มขึ้น 35%
ตัวแปรคงที่ / แปลงไม่ได้จัดเก็บในการจัดเก็บข้อมูลของสัญญา ตัวแปรเหล่านี้จะถูกคำนวณในเวลาคอมไพล์และจัดเก็บใน bytecode ของสัญญา ดังนั้นค่าในการเข้าถึงของพวกเขาจะต่ำกว่าตัวแปรการเก็บข้อมูล แนะนำให้ใช้คีย์เวิร์ด Constant หรือ Immutable เมื่อเป็นไปได้
เมื่อนักพัฒนาสามารถมั่นใจได้ว่าการดำเนินการทางคณิตศาสตร์จะไม่เกิดการเกินหรือขาดลอย พวกเขาสามารถใช้คำสั่ง unchecked ที่นำเสนอใน Solidity v0.8.0 เพื่อหลีกเลี่ยงการตรวจสอบการเกินหรือขาดลอยที่ไม่จำเป็น ซึ่งจะช่วยประหยัดค่าแก๊สได้อย่างมาก
ในภาพแผนภาพด้านล่าง, เงื่อนไขที่ถูก จำกัดเงื่อนไข i
นอกจากนี้เสียทั้งนี้ รุ่นคอมไพเลอร์ 0.8.0 และสูงกว่าไม่ต้องใช้ SafeMath library อีกต่อไป เนื่องจากคอมไพเลอร์เองตอนนี้มีการป้องกันการล้นและการล้มเหลวที่มีอยู่แล้วในตัว
รหัสของตัวดัดแปลงถูกฝังอยู่ในฟังก์ชันที่เขาแก้ไข ทุกครั้งที่ใช้ตัวดัดแปลงรหัสจะทำซ้ำซึ่งเพิ่มขนาด bytecode และเพิ่มการบริโภคแก๊ส นี่คือวิธีหนึ่งในการปรับปรุงต้นทุนแก๊สของตัวดัดแปลง:
ก่อนการปรับปรุง:
หลังจากการปรับปรุง:
ในตัวอย่างนี้ โดยการทำการระบายโค้ดเข้าไปในฟังก์ชันภายใน _checkOwner() ซึ่งสามารถนำกลับมาใช้ในตัวดัดแปลงได้ ขนาดของ bytecode ถูกลดลงและต้นทุน Gas ลดลง
สำหรับตัวดำเนินการ || (OR) และ && (AND) การดำเนินการตรรกะจะถูกประเมินด้วยการ short-circuiting ซึ่งหมายถึงหากเงื่อนไขแรกเพียงพอที่จะกำหนดผลลัพธ์ของนิพจน์ตรรกะ เงื่อนไขที่สองจะไม่ถูกประเมิน
เพื่อเพิ่มประสิทธิภาพในการใช้แก๊ส ควรวางเงื่อนไขที่มีค่าการคำนวณต่ำกว่าไว้ก่อน โดยที่คำนวณที่มีค่าสูงอาจถูกระบายไป
หากมีฟังก์ชันหรือตัวแปรที่ไม่ได้ใช้ในสัญญา แนะนำให้ลบออก เป็นวิธีที่เร็วที่สุดในการลดต้นทุนในการเปิดใช้สัญญาและให้ขนาดของสัญญาเล็ก
นี่คือคำแนะนำที่เป็นประโยชน์:
ใช้อัลกอริทึมที่มีประสิทธิภาพสูงสุดสำหรับการคำนวณ หากสัญญาใช้ผลลัพธ์การคำนวณบางอย่างโดยตรง ควรลบการคำนวณที่ไม่จำเป็น ในทางปฏิบัติ ควรลบการคำนวณที่ไม่ได้ใช้งานออกไป เมื่อใช้ Ethereum นักพัฒนาสามารถรับรางวัลแก๊สโดยการปล่อยพื้นที่การเก็บข้อมูล หากตัวแปรไม่ได้ใช้งานอีกต่อไป ควรลบโดยใช้คำสั่ง delete หรือตั้งค่าเป็นค่าเริ่มต้นของมัน
การปรับปรุงลูป: หลีกเลี่ยงการดำเนินการลูปที่มีค่าใช้จ่ายสูง พยายามรวมลูปและย้ายการคำนวณที่ซ้ำกันออกจากเนื้อหาของลูป
สัญญาที่ถูกคอมไพล์ล่วงหน้าให้บริการฟังก์ชันห้องสมุดที่ซับซ้อน เช่นการเข้ารหัสลับและการดำเนินการแฮซชิ่ง โดยเนื่องจากโค้ดไม่ได้รันบน EVM แต่ทำงานในเครื่องลูกข่ายของลูกค้า จึงต้องใช้แก๊สน้อยกว่า การใช้สัญญาที่ถูกคอมไพล์ล่วงหน้าสามารถประหยัดแก๊สได้โดยลดภาระงานการคำนวณที่ต้องการในการดำเนินการสัญญาอัจฉริยะ
ตัวอย่างของสัญญาที่ถูกคอมไพล์ล่วงหน้ารวมถึง Elliptic Curve Digital Signature Algorithm (ECDSA) และ SHA2-256 hashing algorithm โดยการใช้สัญญาที่ถูกคอมไพล์ล่วงหน้าเหล่านี้ในสัญญาอัจฉริยะ นักพัฒนาสามารถลดค่า Gas และเพิ่มประสิทธิภาพของแอปพลิเคชัน
สำหรับรายการสมบูรณ์ของสัญญาที่ได้รับการเตรียมไว้ล่วงหน้าที่รองรับโดยเครือข่าย Ethereum โปรดอ้างอิงที่ลิงก์นี้ [4]
Inline assembly ช่วยให้นักพัฒนาเขียนโค้ดระดับต่ำที่มีประสิทธิภาพซึ่งสามารถถูกดำเนินการโดย EVM โดยตรงโดยไม่ต้องใช้ Solidity opcodes ที่แพง การประกอบแบบอินไลน์ยังช่วยให้มีการควบคุมที่แม่นยำกว่าเรื่องการใช้พื้นที่หน่วยความจำและพื้นที่เก็บข้อมูลที่ลดต้นทุน Gas ได้อีกด้วย นอกจากนี้ inline assembly สามารถทำงานบางอย่างที่ซับซ้อนซึ่งยากที่จะปฏิบัติด้วย Solidity เพียงอย่างเดียว มอบความยืดหยุ่นมากขึ้นสำหรับการปรับปรุงการใช้ Gas
นี่คือตัวอย่างการใช้การประชุมแบบอินไลน์เพื่อประหยัดแก๊ส:
ดังที่เห็นในตัวอย่างข้างต้น กรณีที่สองที่ใช้การประชุมแบบอินไลน์มีประสิทธิภาพแก๊สสูงกว่ากรณีมาตรฐาน
อย่างไรก็ตามการใช้งานตัวแอสเซมบลีแบบอินไลน์ยังสามารถเสี่ยงต่อความเสี่ยงและมีโอกาสเกิดข้อผิดพลาดได้ ดังนั้นควรใช้ด้วยความระมัดระวังและแนะนำให้ใช้เฉพาะสำหรับนักพัฒนาที่มีประสบการณ์เท่านั้น
Layer 2 solutions สามารถลดปริมาณข้อมูลที่ต้องเก็บรักษาและคำนวณบน Ethereum mainnet ลงได้
Layer 2 solutions like rollups, sidechains, and state channels offload transaction processing from the main Ethereum chain, enabling faster and cheaper transactions.
โดยการรวมการทำธุรกรรมจำนวนมากเข้าด้วยกัน สิ่งที่ทำให้จำนวนการทำธุรกรรมบนเชื่อมต่อลดลง ซึ่งในลำดับต่อไปจะลดค่าธรรมเนียมแก๊ส การใช้ Layer 2 solutions ยังเสริมสร้างความยืดหยุ่นของ Ethereum ทำให้ผู้ใช้และแอปพลิเคชันมากขึ้นสามารถเข้าร่วมในเครือข่ายได้มากขึ้นโดยไม่เกิดคองเจสชันจากการเรียกใช้เกินไป
มีเครื่องมือการปรับปรุงหลายรูปแบบที่ใช้ได้ เช่นตัวปรับปรุง solc, ตัวปรับปรุงการสร้างของ Truffle และคอมไพเลอร์ Solidity ของ Remix
เครื่องมือเหล่านี้สามารถช่วยลดขนาดไบต์โค้ดลบโค้ดที่ไม่ได้ใช้และลดจํานวนการดําเนินการที่จําเป็นในการดําเนินการสัญญาอัจฉริยะ เมื่อรวมกับไลบรารีการเพิ่มประสิทธิภาพก๊าซอื่น ๆ เช่น "solmate" นักพัฒนาสามารถลดต้นทุนก๊าซได้อย่างมีประสิทธิภาพและปรับปรุงประสิทธิภาพของสัญญาอัจฉริยะ
การปรับปรุงการใช้แก๊สเป็นขั้นตอนที่สำคัญสำหรับนักพัฒนา เนื่องจากมันไม่เพียงทำให้ค่าธุรกรรมลดลงเท่านั้น แต่ยังเพิ่มประสิทธิภาพของสัญญาอัจฉริยะบนเครือข่ายที่เข้ากันได้กับ EVM โดยการจัดลำดับการดำเนินงานที่ประหยัดต้นทุน ลดการใช้พื้นที่จัดเก็บ ใช้งานชุดคำสั่งอินไลน์ และปฏิบัติที่ดีอื่น ๆ ตามที่ได้รับการอภิปรายในบทความนี้ นักพัฒนาสามารถลดการใช้แก๊สของสัญญาได้อย่างมีประสิทธิภาพ
อย่างไรก็ตาม สำคัญที่จะทราบว่า ระหว่างกระบวนการปรับปรุง นักพัฒนาต้องระมัดระวังเพื่อหลีกเลี่ยงการเปิดเผยช่องโหว่ด้านความปลอดภัย ในกระบวนการปรับปรุงรหัสและลดการใช้ แก๊ส ควรไม่มีการเสี่ยงเสียความปลอดภัยที่เป็นธรรม
[1]https://ethereum.org/th/developers/docs/gas/
[2] https://ethereum.github.io/yellowpaper/paper.pdf
[3]https://www.evm.codes/
[4]https://www.evm.codes/precompiled