Как работает базовый газ

В транзакциях Aptos по умолчанию взимается базовая плата за газ, независимо от рыночных условий. Для каждой транзакции эта сумма "базового газа" основывается на трех условиях:

  1. Инструкции.

  2. Хранение.

  3. Полезная нагрузка.

Чем больше вызовов функций, условных операторов ветвления и т.д. требуется для выполнения транзакции, тем больше газа на инструкции. Аналогично, чем больше чтений из глобального хранилища и записей в него требует транзакция, тем больше газа для хранения она будет стоить. Наконец, чем больше байт в полезной нагрузке транзакции, тем больше она будет стоить.

Как объясняется в разделе "Принципы оптимизации", газ из хранилищ оказывает самое большое влияние на базовый газ.

Инструктивный газ

Основные параметры газа команд определяются в файле instr.rs и включают следующие типы команд:

Без операции

ПараметрЗначение

nop

Отсутствие операции

Управляющий поток

ПараметрЗначение

ret

Возврат

abort

Прервать

br_true

Выполнение условной истинной ветки

br_false

Выполнение условной ложной ветки

branch

Ветка

Стек

ПараметрЗначение

pop

Вытащить из стека

ld_u8

Загрузить u8

ld_u64

Загрузить u64

ld_u128

Загрузить u128

ld_true

Загрузить true

ld_false

Загрузить false

ld_const_base

Базовые затраты на постоянную нагрузку

ld_const_per_byte

Побайтовая стоимость загрузки константы

Локальный масштаб

ПараметрЗначение

imm_borrow_loc

Неотъемлемый заем

mut_borrow_loc

Взаимно занимать

imm_borrow_field

Безвозвратно занять поле

mut_borrow_field

Взаимное заимствование поля

imm_borrow_field_generic

mut_borrow_field_generic

copy_loc_base

Базовая стоимость копирования

copy_loc_per_abs_val_unit

move_loc_base

Move

st_loc_base

Вызов

ПараметрЗначение

call_base

Базовая стоимость вызова функции

call_per_arg

Стоимость одного аргумента функции

call_generic_base

call_generic_per_ty_arg

Стоимость за аргумент типа

call_generic_per_arg

Структуры

ПараметрЗначение

pack_base

Базовая стоимость пакета struct

pack_per_field

Стоимость пакета struct, за область

pack_generic_base

pack_generic_per_field

unpack_base

Базовая стоимость распаковки struct

unpack_per_field

Стоимость распаковки struct, за область

unpack_generic_base

unpack_generic_per_field

Рекомендации

ПараметрЗначение

read_ref_base

Базовая стоимость чтения из ссылки

read_ref_per_abs_val_unit

write_ref_base

Базовая стоимость записи в базу данных

freeze_ref

Заморозить ссылку

Кастинг

ПараметрЗначение

cast_u8

Каст на u8

cast_u64

Каст на u64

cast_u128

Каст на u128

Арифметика

ПараметрЗначение

add

Добавить

sub

Вычесть

mul

Умножить

mod_

Модуль

div

Разделить

Побитовая

ПараметрЗначение

bit_or

OR: |

bit_and

AND: &

xor

XOR: ^

shl

Сдвиг влево: <<

shr

Сдвиг вправо: >>

Логика

ПараметрЗначение

or

OR: ||

and

AND: &&

not

NOT: !

Сравнение

ПараметрЗначение

lt

Меньше, чем: <

gt

Больше, чем: >

le

Меньше или равно: <=

ge

Больше или равно:>=

eq_base

Базовая стоимость равенства: ==

eq_per_abs_val_unit

neq_base

Базовая стоимость не равна: !=

neq_per_abs_val_unit

Глобальное хранилище

ПараметрЗначение

imm_borrow_global_base

Базовая стоимость неизменного займа:borrow_global<T>()

imm_borrow_global_generic_base

mut_borrow_global_base

Базовая стоимость взаимного заимствования: borrow_global_mut<T>()

mut_borrow_global_generic_base

exists_base

Базовая стоимость проверки существования: exists<T>()

exists_generic_base

move_from_base

Базовая стоимость для move: move_from<T>()

move_from_generic_base

move_to_base

Базовая стоимость move: move_to<T>()

move_to_generic_base

Векторы

ПараметрЗначение

vec_len_base

Длина вектора

vec_imm_borrow_base

Неизменно заимствовать элемент

vec_mut_borrow_base

Взаимное заимствование элемента

vec_push_back_base

Отталкивание

vec_pop_back_base

Сзади

vec_swap_base

Поменяйте элементы местами

vec_pack_base

Базовая стоимость упаковки вектора

vec_pack_per_elem

Стоимость пакета вектора на один элемент

vec_unpack_base

Базовая стоимость распаковки вектора

vec_unpack_per_expected_elem

Базовая стоимость распаковки вектора на элемент

Дополнительные параметры газа для хранилища определены в table.rs, move_stdlib.rs и других различных исходных файлах в aptos-gas/src/.

Газ для хранилища

Определение газа для хранилищ содержится в файле storage_gas.move, который сопровождается полным и внутренне связанным файлом DocGen storage_gas.md.

Вкратце:

  1. В initialize(), base_8192_exponential_curve() используется для создания экспоненциальной кривой, при которой затраты на единицу и байт быстро увеличиваются по мере приближения использования к верхней границе.

  2. Параметры перенастраиваются каждую эпоху через функцию on_reconfig(), основываясь на коэффициентах использования по элементам и байтам.

  3. Измененные параметры сохраняются в StorageGas, который содержит следующие поля:

ПолеЗначение

per_item_read

Стоимость чтения элемента из глобального хранилища

per_item_create

Стоимость создания элемента в глобальном хранилище

per_item_write

Стоимость перезаписи элемента в глобальном хранилище

per_byte_read

Стоимость чтения байта из глобального хранилища

per_byte_create

Стоимость создания байта в глобальном хранилище

per_byte_write

Стоимость перезаписи байта в глобальном хранилище

Здесь элемент - это либо ресурс, имеющий атрибут key , либо запись в таблице, и, что примечательно, стоимость за байт оценивается по всему размеру элемента. Как указано в файле storage_gas.md, например, если операция изменяет поле u8 в ресурсе, который имеет пять других полей u128, стоимость записи за байт будет составлять (5 * 128) / 8 + 1 = 81(5∗128)/8+1=81 байт.

Векторы

Аналогичным образом байтовые сборы начисляются на векторы, которые потребляют i=0n1ei+b(n)∑ i=0 n−1 ​ e i ​ +b(n) байт, где:

  • nn - количество элементов в векторе

  • eie i - размер элемента ii

  • b(n)b(n) это "базовый размер", который является функцией от nn

Более подробную информацию о размере базы вектора (технически ULEB128) см. в спецификации последовательности BCS, которая на практике обычно занимает всего один байт, так что вектор из 100 элементов u8 занимает 100 + 1 = 101100+1=101 байт. Следовательно, согласно описанной выше методологии поэлементного чтения, чтение последнего элемента такого вектора рассматривается как чтение 101 байта.

Газ полезной нагрузки

Газ полезной нагрузки определяется в transaction.rs, который включает газ для хранилищ с несколькими параметрами, связанными с полезной нагрузкой и ценообразованием:

ПараметрЗначение

min_transaction_gas_units

Минимальное количество единиц внутреннего газа для транзакции, взимаемое в начале исполнения

large_transaction_cutoff

Размер, в байтах, при превышении которого за транзакции будет взиматься дополнительная сумма за байт

intrinsic_gas_per_byte

Внутренние блоки газа, взимаемые за байт для полезной нагрузки свышеlarge_transaction_cutoff

maximum_number_of_gas_units

Верхний предел внешних единиц газа для транзакции

min_price_per_gas_unit

Минимальная цена газа, допустимая для транзакции

max_price_per_gas_unit

Максимальная цена газа, допустимая для транзакции

max_transaction_size_in_bytes

Максимальный размер полезной нагрузки транзакции в байтах

gas_unit_scaling_factor

Коэффициент пересчета между единицами внутреннего газа и единицами внешнего газа

Здесь "единицы внутреннего газа" определяются как константы в исходных файлах, таких как instr.rs и storage_gas.move, которые являются более детализированными, чем "единицы внешнего газа", на коэффициент gas_unit_scaling_factor: чтобы преобразовать единицы внутреннего газа в единицы внешнего газа, разделите на gas_unit_scaling_factor. Затем, чтобы перевести единицы внешнего газа в октаты, умножьте на "цену газа", которая обозначает количество октат за единицу внешнего газа.

Основы оптимизации

Единицы измерения и ценовые константы

На момент написания этой статьи min_price_per_gas_unit в transaction.rs определяется как aptos_global_constants::GAS_UNIT_PRICE (которая сама определяется как 100), а другие заслуживающие внимания константы transaction.rs следующие:

ПостояннаяЗначение

min_price_per_gas_unit

100

max_price_per_gas_unit

10,000

gas_unit_scaling_factor

10,000

Значение этих констант см. в разделе Газ полезной нагрузки.

Газ для хранилища

На момент написания этой статьи функция initialize()устанавливает следующие минимальные объемы газа для хранилища:

Стиль данныхОперацияСимволМинимальное количество внутреннего газа

За единицу

Читать

ririr_iri​

300,000

За единицу

Создавать

cicic_ici​

5,000,000

За единицу

Писать

wiwiw_iwi​

300,000

За байт

Читать

rbrbr_brb​

300

За байт

Создавать

cbcbc_bcb​

5,000

За байт

Писать

wbwbw_bwb​

5,000

Максимальные суммы в 100 раз больше минимальных, что означает, что при коэффициенте использования 40% или меньше, общие затраты на газ будут примерно в 1-1,5 раза больше минимальных сумм (смотрите base_8192_exponential_curve()для вспомогательных расчетов). Следовательно, в пересчете на окты, начальные затраты на газ в сети можно оценить следующим образом (разделить внутренний газ на масштабный коэффициент, затем умножить на минимальную цену газа):

ОперацияОперацияМинимальные октавы

Считывание по каждому пункту

ririr_iri​

3000

Создание по каждому пункту

cicic_ici​

50,000

Запись по каждому пункту

wiwiw_iwi​

3000

Побайтовое считывание

rbrbr_brb​

3

Побайтовое создание

cbcbc_bcb​

50

Побайтовая запись

wbwbw_bwb​

50

Здесь самой дорогой операцией на элемент является создание нового элемента (через move_to() или добавление в таблицу), что стоит почти в 17 раз больше, чем чтение или перезапись старого элемента: ci=16.6ri=16.6wic i ​ =16. 6 r i ​ =16. 6 w i​. Дополнительно:

  • Запись стоит столько же, сколько и чтение, в расчете на каждый элемент:​wi=riw_i =r i ​

  • Однако в расчете на байт запись стоит столько же, сколько и создание: wb=cbw b ​ =c b

  • Запись и создание каждого байта обходятся почти в 17 раз дороже, чем чтение каждого байта: wb=cb=16.6rbw b ​ =c b ​ =16. 6 r b

  • Поэлементное чтение стоит в 1000 раз больше, чем побайтовое: ​ri=1000rbr i ​ =1000r b

  • Создание каждого элемента стоит в 1000 раз больше, чем создание каждого байта: ci=1000cbc i ​ =1000c b

  • Запись по элементам стоит в 60 раз дороже, чем запись по байтам: wi=60wbw i ​ =60w b

Таким образом, операции на элемент стоят в 1000 раз больше, чем операции на байт, как для чтения, так и для создания, но только в 60 раз больше для записи.

Таким образом, в отсутствие законного экономического стимула к деаллокации из глобального хранилища (через move_from() или путем удаления из таблицы), наиболее эффективная стратегия оптимизации газа в хранилище выглядит следующим образом:

  1. Сведите к минимуму создание каждого элемента

  2. Отслеживайте неиспользуемые элементы и по возможности перезаписывайте их, а не создавайте новые.

  3. Ограничьте запись каждого элемента как можно меньшим количеством элементов

  4. Считывайте, а не записывайте, когда это возможно

  5. Сведите к минимуму количество байтов во всех операциях, особенно при записи.

Инструкции газа

На момент написания этой статьи все операции с газом умножаются на коэффициент EXECUTION_GAS_MULTIPLIER, определенный в файле gas_meter.rs,, который установлен на 20. Следовательно, следующие репрезентативные операции предполагают следующие затраты на газ (разделите внутренний газ на масштабный коэффициент, затем умножьте на минимальную цену газа):

ОперацияМинимальные октавы

Блок добавления/заимствования/удаления таблицы

240

Вызов функции

200

Постоянная нагрузка

130

Глобальные займы

100

Запрос на чтение/запись

40

Загрузка u128 в стек

16

Работа блока таблицы на один байт

2

(Обратите внимание, что газ на байты в блоке таблицы не учитывает газ для хранения, который оценивается отдельно).

Для сравнения, чтение 100-байтового элемента стоит ri+100rb=3000+1003=3300r i ​ +100∗r b ​ =3000+100∗3=3300 окта в минимум, что примерно в 16,5 раз больше, чем вызов функции, и в целом затраты на газ инструкций в значительной степени доминируют над затратами на газ хранилища.

Примечательно, однако, что технически все еще существует стимул для уменьшения количества вызовов функций в программе, но усилия инженеров более эффективно направлены на написание модульного, разложенного кода, направленного на снижение затрат на газ для хранения данных, а не на попытки написать повторяющиеся блоки кода с меньшим количеством вложенных функций (почти во всех случаях).

В экстремальных случаях возможно, что газ инструкций значительно превосходит газ хранилища, например, если математической функции, состоящей из циклов, требуется 10 000 итераций для сходимости; но это опять же крайний случай, и для большинства приложений газ хранилища оказывает большее влияние на базовый газ, чем газ инструкций.

Газ полезной нагрузки

На момент написания этой статьи transaction.rs определяет минимальное количество внутреннего газа на транзакцию в 1 500 000 внутренних единиц (минимум 15 000 окта), которое увеличивается на 2 000 внутренних единиц газа (минимум 20 окта) на байт для полезной нагрузки размером более 600 байт, а максимальное количество байт в транзакции установлено на уровне 65536. Таким образом, на практике газ полезной нагрузки вряд ли будет представлять проблему.

Last updated