mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
Adds a script for testing opcode timing and gas costs (#537)
* Adds a utility to test opcode timing and gas costs * Remove unnecessary dependencies * Adds a missing error code parameter
This commit is contained in:
parent
d8dd75ec83
commit
77204a549a
2 changed files with 176 additions and 0 deletions
|
@ -20,4 +20,9 @@ target_link_libraries(pack-viewer tl_api ton_crypto keys validator tddb)
|
|||
target_include_directories(pack-viewer PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/..)
|
||||
|
||||
add_executable(opcode-timing opcode-timing.cpp )
|
||||
target_link_libraries(opcode-timing ton_crypto)
|
||||
target_include_directories(pack-viewer PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/..)
|
||||
|
||||
install(TARGETS generate-random-id RUNTIME DESTINATION bin)
|
||||
|
|
171
utils/opcode-timing.cpp
Normal file
171
utils/opcode-timing.cpp
Normal file
|
@ -0,0 +1,171 @@
|
|||
#include <ctime>
|
||||
#include <iomanip>
|
||||
|
||||
#include "vm/vm.h"
|
||||
#include "vm/cp0.h"
|
||||
#include "vm/dict.h"
|
||||
#include "fift/utils.h"
|
||||
#include "common/bigint.hpp"
|
||||
|
||||
#include "td/utils/base64.h"
|
||||
#include "td/utils/tests.h"
|
||||
#include "td/utils/ScopeGuard.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
|
||||
td::Ref<vm::Cell> to_cell(const unsigned char *buff, int bits) {
|
||||
return vm::CellBuilder().store_bits(buff, bits, 0).finalize();
|
||||
}
|
||||
|
||||
long double timingBaseline;
|
||||
|
||||
typedef struct {
|
||||
long double mean;
|
||||
long double stddev;
|
||||
} stats;
|
||||
|
||||
struct runInfo {
|
||||
long double runtime;
|
||||
long long gasUsage;
|
||||
int vmReturnCode;
|
||||
|
||||
runInfo() : runtime(0.0), gasUsage(0), vmReturnCode(0) {}
|
||||
runInfo(long double runtime, long long gasUsage, int vmReturnCode) :
|
||||
runtime(runtime), gasUsage(gasUsage), vmReturnCode(vmReturnCode) {}
|
||||
|
||||
runInfo operator+(const runInfo& addend) const {
|
||||
return {runtime + addend.runtime, gasUsage + addend.gasUsage, vmReturnCode ? vmReturnCode : addend.vmReturnCode};
|
||||
}
|
||||
|
||||
runInfo& operator+=(const runInfo& addend) {
|
||||
runtime += addend.runtime;
|
||||
gasUsage += addend.gasUsage;
|
||||
if(!vmReturnCode && addend.vmReturnCode) {
|
||||
vmReturnCode = addend.vmReturnCode;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool errored() const {
|
||||
return vmReturnCode != 0;
|
||||
}
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
stats runtime;
|
||||
stats gasUsage;
|
||||
bool errored;
|
||||
} runtimeStats;
|
||||
|
||||
runInfo time_run_vm(td::Slice command) {
|
||||
unsigned char buff[128];
|
||||
const int bits = (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), command.begin(), command.end());
|
||||
CHECK(bits >= 0);
|
||||
|
||||
const auto cell = to_cell(buff, bits);
|
||||
|
||||
vm::init_op_cp0();
|
||||
vm::DictionaryBase::get_empty_dictionary();
|
||||
|
||||
class Logger : public td::LogInterface {
|
||||
public:
|
||||
void append(td::CSlice slice) override {
|
||||
res.append(slice.data(), slice.size());
|
||||
}
|
||||
std::string res;
|
||||
};
|
||||
static Logger logger;
|
||||
logger.res = "";
|
||||
td::set_log_fatal_error_callback([](td::CSlice message) { td::default_log_interface->append(logger.res); });
|
||||
vm::VmLog log{&logger, td::LogOptions::plain()};
|
||||
log.log_options.level = 4;
|
||||
log.log_options.fix_newlines = true;
|
||||
log.log_mask |= vm::VmLog::DumpStack;
|
||||
|
||||
vm::Stack stack;
|
||||
try {
|
||||
vm::GasLimits gas_limit(10000, 10000);
|
||||
|
||||
std::clock_t cStart = std::clock();
|
||||
int ret = vm::run_vm_code(vm::load_cell_slice_ref(cell), stack, 0 /*flags*/, nullptr /*data*/,
|
||||
std::move(log) /*VmLog*/, nullptr, &gas_limit);
|
||||
std::clock_t cEnd = std::clock();
|
||||
const auto time = (1000.0 * static_cast<long double>(cEnd - cStart) / CLOCKS_PER_SEC) - timingBaseline;
|
||||
return {time >= 0 ? time : 0, gas_limit.gas_consumed(), ret};
|
||||
} catch (...) {
|
||||
LOG(FATAL) << "catch unhandled exception";
|
||||
return {-1, -1, 1};
|
||||
}
|
||||
}
|
||||
|
||||
runtimeStats averageRuntime(td::Slice command) {
|
||||
const size_t samples = 5000;
|
||||
runInfo total;
|
||||
std::vector<runInfo> values;
|
||||
values.reserve(samples);
|
||||
for(size_t i=0; i<samples; ++i) {
|
||||
const auto value = time_run_vm(command);
|
||||
values.push_back(value);
|
||||
total += value;
|
||||
}
|
||||
const auto runtimeMean = total.runtime / static_cast<long double>(samples);
|
||||
const auto gasMean = static_cast<long double>(total.gasUsage) / static_cast<long double>(samples);
|
||||
long double runtimeDiffSum = 0.0;
|
||||
long double gasDiffSum = 0.0;
|
||||
bool errored = false;
|
||||
for(const auto value : values) {
|
||||
const auto runtime = value.runtime - runtimeMean;
|
||||
const auto gasUsage = static_cast<long double>(value.gasUsage) - gasMean;
|
||||
runtimeDiffSum += runtime * runtime;
|
||||
gasDiffSum += gasUsage * gasUsage;
|
||||
errored = errored || value.errored();
|
||||
}
|
||||
return {
|
||||
{runtimeMean, sqrt(runtimeDiffSum / static_cast<long double>(samples))},
|
||||
{gasMean, sqrt(gasDiffSum / static_cast<long double>(samples))},
|
||||
errored
|
||||
};
|
||||
}
|
||||
|
||||
runtimeStats timeInstruction(const std::string& setupCode, const std::string& toMeasure) {
|
||||
const auto setupCodeTime = averageRuntime(setupCode);
|
||||
const auto totalCodeTime = averageRuntime(setupCode + toMeasure);
|
||||
return {
|
||||
{totalCodeTime.runtime.mean - setupCodeTime.runtime.mean, totalCodeTime.runtime.stddev},
|
||||
{totalCodeTime.gasUsage.mean - setupCodeTime.gasUsage.mean, totalCodeTime.gasUsage.stddev},
|
||||
false
|
||||
};
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if(argc != 2 && argc != 3) {
|
||||
std::cerr <<
|
||||
"This utility compares the timing of VM execution against the gas used.\n"
|
||||
"It can be used to discover opcodes or opcode sequences that consume an "
|
||||
"inordinate amount of computational resources relative to their gas cost.\n"
|
||||
"\n"
|
||||
"The utility expects two command line arguments, each a hex string: \n"
|
||||
"The TVM code used to set up the stack and VM state followed by the TVM code to measure.\n"
|
||||
"For example, to test the DIVMODC opcode:\n"
|
||||
"\t$ " << argv[0] << " 80FF801C A90E 2>/dev/null\n"
|
||||
"\tOPCODE,runtime mean,runtime stddev,gas mean,gas stddev\n"
|
||||
"\tA90E,0.0066416,0.00233496,26,0\n"
|
||||
"\n"
|
||||
"Usage: " << argv[0] <<
|
||||
" [TVM_SETUP_BYTECODE_HEX] TVM_BYTECODE_HEX" << std::endl << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::cout << "OPCODE,runtime mean,runtime stddev,gas mean,gas stddev" << std::endl;
|
||||
timingBaseline = averageRuntime("").runtime.mean;
|
||||
std::string setup, code;
|
||||
if(argc == 2) {
|
||||
setup = "";
|
||||
code = argv[1];
|
||||
} else {
|
||||
setup = argv[1];
|
||||
code = argv[2];
|
||||
}
|
||||
const auto time = timeInstruction(setup, code);
|
||||
std::cout << code << "," << time.runtime.mean << "," << time.runtime.stddev << "," <<
|
||||
time.gasUsage.mean << "," << time.gasUsage.stddev << std::endl;
|
||||
return 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue