Описание теста
Мы хотели выяснить, как повлияет загрузка на один смарт-контракт миллиона транзакций и большого объёма данных.
Замерялись такие параметры:
- стоимость одной транзакции в kgas;
- длительность создания одного блока;
- размер одного блока;
Параметры блокчейна
В нашем распоряжении имелся приватный PoA-блокчейн с двумя авторизованными пишущими узлами и одним «пассивным», с которого и отправлялись транзакции.
Все три узла были запущены на идентичных серверах:
- Процессор: 2 Intel Xeon E5-2670 2,60 ГГц.
- ОЗУ: 8 Гб.
- ОС: Windows Server 2012 R2 Datacenter (64-битная).
Описание смарт-контрактов
Было создано два смарт-контракта. Один имел в качестве поля mapping bytes32 => bytes32, другой — одномерный массив bytes32. Каждый из контрактов содержал функцию, принимающую в качестве параметра значение bytes32 и сохраняющую это значение как элемент соответствующего mapping'a или массива (по сути, значение сохранялось в хранилище блокчейна).
Краткое описание контракта с mapping'ом
contract TesterMapping {
mapping (bytes32 => bytes32) StoragedData;
function Storing(bytes32 data) {
if(StoragedData[data]!= "Y")
StoragedData[data] = "Y";
}
}
Краткое описание контракта с массивом
Con tract TesterArray {
bytes32[] StoragedArray;
function Storing(bytes32 data) {
for(uint256 i = 0; i < StoragedArray.length; i++) {
if(StoragedArray[i] == data)
return;
}
StoragedArray.push(data);
}
}
Ход и результаты тестирования
Сперва тестировался смарт-контракт с полем-mapping'ом. Раз в секунду на него посылалась транзакция со случайным 32-байтовым значением. Из-за технических проблем тестирование заняло несколько больше времени, чем планировалось, и миллионная транзакция была отправлена спустя три недели после отправки первой. Затем был протестирован смарт-контракт с массивом.
Mapping—контракт
В течение всего эксперимента ни стоимость транзакции, ни размер блока не колебались на сколько-нибудь значимые величины. Каждая транзакция обрабатывалась практически мгновенно, как первая, так и миллионная. Длительность создания блока периодически менялась от 1 до 9 секунд, но всегда симметрично заданному в генезис-блоке значению в 5 сек (если между созданием блока n и n+1 проходила одна секунда, то блок n+2 появлялся через 9 сек), то есть средняя длительность создания блока оставалось равной 5 секундам. Однако какой-то закономерности в возникновении этих флуктуаций замечено не было и, возможно, это было связано с работой нашей сети или вспомогательным программным обеспечением серверов (антивирусы и прочее).
Контракт с массивом
В данном варианте из-за наличия цикла перебора массива (для поиска совпадающих значений) стоимость одной транзакции выросла с 40 тысяч KGas до более чем 2 миллионов KGas уже за первые две тысячи транзакций. При этом продолжительность обработки одной транзакции уже за первые несколько сотен транзакций стала больше длительности создания одного блока. Из-за этого размер одного блока за приблизительно 500 транзакций упал до минимального и больше не увеличивался, так как на один блок стала приходиться в лучшем случае одна транзакция. Длительность обработки очень быстро стала настолько большой, что после отправки всего трёх тысяч транзакций «разгребание» получившейся очереди заняло около четырёх часов, при этом работа сети была бы парализована (конечно, если у отправителя в «боевой» сети хватило бы средств оплачивать такое количество столь дорогих транзакций).
Выводы
- Размер mapping'а не влияет на быстродействие работы с ним или на стоимость транзакции (как минимум, до 1 миллиона элементов).
- Виртуальная машина Solidity крайне неэффективна при работе с итерационными циклами.
- Для работы с большим количеством записей лучше использовать mapping.
К сожалению, мы не нашли способа определить, какой объем данных в хранилище занимают данные конкретного смарт-контракта при использовании mapping-массива. Возможно, кто-нибудь из читателей сможет подсказать свой вариант.
Комментарии (9)
Tsvetik
23.04.2018 18:34+2Какой объем хранилища занимает маппинг надо смотреть в исходниках клиента, который вы используете geth или parity. Конкретно изучать trie деревья, кодирование в RLP и код записи и чтения базы данных.
Laney1
23.04.2018 19:13если не ошибаюсь, где-то в официальных доках Ethereum прямо говорится о квадратичной сложности создания массивов и рекомендуется их избегать (в смысле, длинных массивов)
Homakov
23.04.2018 20:27Виртуальная машина Solidity крайне неэффективна — на этом можно закончить. Вообще любой юзкейс отлично кодируется внутри кода блокчейна, смысла в виртуальной машине кодить что либо внутри своего приватном permissioned чейна ноль. Оверхед у ВМ адовый же.
decomeron
23.04.2018 21:15Возможно, кто-нибудь из читателей сможет подсказать свой вариант.
Миллион дадите?
BoogerWooger
24.04.2018 12:01Странный тест — поведение маппингов и массивов подробно описано в доке. Зачем статью из этого делать — неясно
MadJackal
24.04.2018 23:13Ну, собственно тесты и предназначены для того, чтобы проверить соответствие того, что написано в доке тому, что получилось в жизни. Ведь оно не всегда совпадает...
Coffin
Когда уже это исправят?
safari OSX — take.ms/rWNTA
chrome OSX — take.ms/1mbM8