diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 008db485..5bbb271e 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -22,4 +22,19 @@ if(BLAZE_COMPILER AND BLAZE_EVALUATOR) PRIVATE sourcemeta::blaze::compiler) target_link_libraries(sourcemeta_blaze_contrib_trace PRIVATE sourcemeta::blaze::evaluator) + + if (UNIX) + add_executable(sourcemeta_blaze_contrib_perf perf.cc) + noa_add_default_options(PRIVATE sourcemeta_blaze_contrib_perf) + target_link_libraries(sourcemeta_blaze_contrib_perf + PRIVATE sourcemeta::jsontoolkit::json) + target_link_libraries(sourcemeta_blaze_contrib_perf + PRIVATE sourcemeta::jsontoolkit::jsonl) + target_link_libraries(sourcemeta_blaze_contrib_perf + PRIVATE sourcemeta::jsontoolkit::jsonschema) + target_link_libraries(sourcemeta_blaze_contrib_perf + PRIVATE sourcemeta::blaze::compiler) + target_link_libraries(sourcemeta_blaze_contrib_perf + PRIVATE sourcemeta::blaze::evaluator) + endif() endif() diff --git a/contrib/perf.cc b/contrib/perf.cc new file mode 100644 index 00000000..ea0ab177 --- /dev/null +++ b/contrib/perf.cc @@ -0,0 +1,211 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include // std::sort, std::max +#include // EXIT_SUCCESS, EXIT_FAILURE +#include // std::filesystem::path +#include // std::cerr, std::cout +#include // std::map +#include // std::pair +#include // std::vector + +static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, + int cpu, long group_fd, unsigned long flags) { + long fd = syscall(SYS_perf_event_open, hw_event, pid, cpu, group_fd, flags); + if (fd == -1) { + fprintf(stderr, "Error creating event"); + exit(EXIT_FAILURE); + } + + return fd; +} + +struct record_t { + uint64_t value; + uint64_t id; +}; + +struct read_format { + uint64_t nr; + record_t values[6]; +}; + +class PerfEvents { +private: + struct perf_event_attr pea; + unsigned long branch_predict_id; + long branch_predict_fd; + unsigned long branch_miss_id; + long branch_miss_fd; + unsigned long cache_access_id; + long cache_access_fd; + unsigned long cache_miss_id; + long cache_miss_fd; + unsigned long cycles_id; + long cycles_fd; + unsigned long instr_id; + long instr_fd; + + void newHWEvent(struct perf_event_attr *newEvent, unsigned long type, + long *fd, long group_fd, unsigned long *id) { + memset(newEvent, 0, sizeof(struct perf_event_attr)); + newEvent->type = PERF_TYPE_HARDWARE; + newEvent->size = sizeof(struct perf_event_attr); + newEvent->config = type; + newEvent->disabled = 1; + newEvent->exclude_kernel = 1; + newEvent->exclude_hv = 1; + newEvent->read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID; + *fd = perf_event_open(newEvent, 0, -1, group_fd, 0); + ioctl(static_cast(*fd), PERF_EVENT_IOC_ID, id); + } + +public: + PerfEvents() { + newHWEvent(&pea, PERF_COUNT_HW_BRANCH_INSTRUCTIONS, &branch_predict_fd, -1, + &branch_predict_id); + newHWEvent(&pea, PERF_COUNT_HW_BRANCH_MISSES, &branch_miss_fd, + branch_predict_fd, &branch_miss_id); + newHWEvent(&pea, PERF_COUNT_HW_CACHE_REFERENCES, &cache_access_fd, + branch_predict_fd, &cache_access_id); + newHWEvent(&pea, PERF_COUNT_HW_CACHE_MISSES, &cache_miss_fd, + branch_predict_fd, &cache_miss_id); + newHWEvent(&pea, PERF_COUNT_HW_CPU_CYCLES, &cycles_fd, branch_predict_fd, + &cycles_id); + newHWEvent(&pea, PERF_COUNT_HW_INSTRUCTIONS, &instr_fd, branch_predict_fd, + &instr_id); + } + + void start() { + ioctl(static_cast(branch_predict_fd), PERF_EVENT_IOC_RESET, + PERF_IOC_FLAG_GROUP); + ioctl(static_cast(branch_predict_fd), PERF_EVENT_IOC_ENABLE, + PERF_IOC_FLAG_GROUP); + } + + void stop() { + ioctl(static_cast(branch_predict_fd), PERF_EVENT_IOC_DISABLE, + PERF_IOC_FLAG_GROUP); + } + + void print() { + char buf[4096]; + struct read_format *rf = (struct read_format *)buf; + unsigned long branch_predicts = 0; + unsigned long branch_misses = 0; + unsigned long cache_accesses = 0; + unsigned long cache_misses = 0; + unsigned long cycles = 0; + unsigned long instructions = 0; + + read(static_cast(branch_predict_fd), buf, sizeof(buf)); + for (unsigned long i = 0; i < rf->nr; i++) { + if (rf->values[i].id == branch_predict_id) { + branch_predicts = rf->values[i].value; + } else if (rf->values[i].id == branch_miss_id) { + branch_misses = rf->values[i].value; + } else if (rf->values[i].id == cache_access_id) { + cache_accesses = rf->values[i].value; + } else if (rf->values[i].id == cache_miss_id) { + cache_misses = rf->values[i].value; + } else if (rf->values[i].id == cycles_id) { + cycles = rf->values[i].value; + } else if (rf->values[i].id == instr_id) { + instructions = rf->values[i].value; + } + } + + std::cerr << " Cycles: " << cycles << "\n"; + std::cerr << " Instructions: " << instructions << "\n"; + std::cerr << " Cache misses: " << cache_misses << "/" << cache_accesses + << " = " + << (static_cast(cache_misses) / + static_cast(cache_accesses) * 100) + << "%\n"; + std::cerr << "Branch misses: " << branch_misses << "/" << branch_predicts + << " = " + << (static_cast(branch_misses) / + static_cast(branch_predicts) * 100) + << "%\n"; + } + + ~PerfEvents() { + close(static_cast(branch_miss_fd)); + close(static_cast(cache_access_fd)); + close(static_cast(cache_miss_fd)); + close(static_cast(cycles_fd)); + close(static_cast(instr_fd)); + close(static_cast(branch_predict_fd)); + } +}; + +bool validate_all(auto &context, const auto &instances, + const auto &schema_template) { + for (std::size_t num = 0; num < instances.size(); num++) { + context.prepare(instances[num]); + const auto result{sourcemeta::blaze::evaluate(schema_template, context)}; + if (!result) { + std::cerr << "Error validating instance " << num << "\n"; + return false; + } + } + + return true; +} + +auto main(int argc, char **argv) noexcept -> int { + if (argc < 3) { + std::cerr << "Usage: " << argv[0] << " \n"; + return EXIT_FAILURE; + } + + const auto schema{sourcemeta::jsontoolkit::from_file(argv[1])}; + const std::filesystem::path instance_path{argv[2]}; + auto stream{sourcemeta::jsontoolkit::read_file(instance_path)}; + std::vector instances; + for (const auto &instance : sourcemeta::jsontoolkit::JSONL{stream}) { + instances.push_back(instance); + } + + const auto schema_template{sourcemeta::blaze::compile( + schema, sourcemeta::jsontoolkit::default_schema_walker, + sourcemeta::jsontoolkit::official_resolver, + sourcemeta::blaze::default_schema_compiler, + sourcemeta::blaze::Mode::FastValidation)}; + + sourcemeta::blaze::EvaluationContext context; + PerfEvents pe; + + pe.start(); + if (!validate_all(context, instances, schema_template)) { + return EXIT_FAILURE; + } + pe.stop(); + + std::cerr << "Cold\n==============================\n"; + pe.print(); + std::cerr << "\n"; + + for (int i = 0; i < 100; i++) { + validate_all(context, instances, schema_template); + } + + pe.start(); + validate_all(context, instances, schema_template); + pe.stop(); + + std::cerr << "Warm\n==============================\n"; + pe.print(); + std::cerr << "\n"; + + return EXIT_SUCCESS; +}