From 0950ec496f978f370865c9dcc51cf15cf3ef918a Mon Sep 17 00:00:00 2001 From: Robin Davies Date: Mon, 14 Oct 2024 19:55:28 -0400 Subject: [PATCH] pipedalProfilePlugin - report lv1 cache misses. --- .vscode/launch.json | 32 +++++++ install.sh | 3 + src/ArmPerformanceCounters.hpp | 147 +++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 19 +++-- src/profilePluginMain.cpp | 89 ++++++++++++++++---- 5 files changed, 268 insertions(+), 22 deletions(-) create mode 100644 src/ArmPerformanceCounters.hpp diff --git a/.vscode/launch.json b/.vscode/launch.json index 7b833ab..98ec492 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -205,6 +205,38 @@ } ] }, + { + "name": "(gdb) pipedalProfilePlugin", + "type": "cppdbg", + "request": "launch", + // Resolved by CMake Tools: + "program": "${command:cmake.launchTargetPath}", + + "args": [ "--no-profile", "-w", "TooB NAM"], + + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + "name": "PATH", + "value": "$PATH:${command:cmake.launchTargetDirectory}" + }, + { + "name": "OTHER_VALUE", + "value": "Something something" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, + { "name": "(gdb) processCopyrights Launch", diff --git a/install.sh b/install.sh index 620618d..8c249ea 100755 --- a/install.sh +++ b/install.sh @@ -3,3 +3,6 @@ cmake --install build --prefix /usr --config Release -v # Done as an install action by the debian package. sudo /usr/bin/pipedalconfig --install +# copy pipedalPluginProfile as well. +sudo cp build/src/pipedalProfilePlugin /usr/bin/pipedalProfilePlugin +chmod +X /usr/bin/pipedalProfilePlugin diff --git a/src/ArmPerformanceCounters.hpp b/src/ArmPerformanceCounters.hpp new file mode 100644 index 0000000..8437dc8 --- /dev/null +++ b/src/ArmPerformanceCounters.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace pipedal +{ + class CacheMissCounter + { + private: + int fd_l1; + int fd_l2; + bool affinity_set = false; + cpu_set_t original_cpu_set; + + static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid, + int cpu, int group_fd, unsigned long flags) + { + return syscall(__NR_perf_event_open, hw_event, pid, cpu, group_fd, flags); + } + + void setup_counter(int &fd, uint64_t config, const std::string &name) + { + struct perf_event_attr pe; + memset(&pe, 0, sizeof(struct perf_event_attr)); + pe.type = PERF_TYPE_HW_CACHE; + pe.size = sizeof(struct perf_event_attr); + pe.config = config; + pe.disabled = 1; + pe.exclude_kernel = 1; + pe.exclude_hv = 1; + + fd = perf_event_open(&pe, 0, -1, -1, 0); + } + + public: + CacheMissCounter() : fd_l1(-1), fd_l2(-1) + { + uint64_t l1_config = (PERF_COUNT_HW_CACHE_L1D << 0) | + (PERF_COUNT_HW_CACHE_OP_READ << 8) | + (PERF_COUNT_HW_CACHE_RESULT_MISS << 16); + uint64_t l2_config = (PERF_COUNT_HW_CACHE_LL << 0) | + (PERF_COUNT_HW_CACHE_OP_READ << 8) | + (PERF_COUNT_HW_CACHE_RESULT_MISS << 16); + + setup_counter(fd_l1, l1_config, "L1"); + setup_counter(fd_l2, l2_config, "L2"); + + int32_t cpu = 1; + if (cpu >= 0) + { + if (pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &original_cpu_set) != 0) + { + throw std::runtime_error("Failed to get thread affinity"); + } + + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + CPU_SET(cpu, &cpu_set); + if (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpu_set) != 0) + { + throw std::runtime_error("Failed to set thread affinity"); + } + affinity_set = true; + } + } + + ~CacheMissCounter() + { + if (fd_l1 != -1) + close(fd_l1); + if (fd_l2 != -1) + close(fd_l2); + if (affinity_set) + { + pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &original_cpu_set); + } + } + + void start() + { + ioctl(fd_l1, PERF_EVENT_IOC_RESET, 0); + ioctl(fd_l2, PERF_EVENT_IOC_RESET, 0); + ioctl(fd_l1, PERF_EVENT_IOC_ENABLE, 0); + ioctl(fd_l2, PERF_EVENT_IOC_ENABLE, 0); + } + + void stop() + { + ioctl(fd_l1, PERF_EVENT_IOC_DISABLE, 0); + ioctl(fd_l2, PERF_EVENT_IOC_DISABLE, 0); + } + + long long get_l1_misses() + { + long long count; + read(fd_l1, &count, sizeof(long long)); + return count; + } + + long long get_l2_misses() + { + if (fd_l2 == -1) + { + return -1; + } + long long count; + read(fd_l2, &count, sizeof(long long)); + return count; + } + }; + + // // Example usage + // int main() { + // try { + // CacheMissCounter counter; + + // counter.start(); + + // // Your code to measure goes here + // // For example, let's do some memory operations + // volatile int* array = new int[1000000]; + // for (int i = 0; i < 1000000; i++) { + // array[i] = i; + // } + // delete[] array; + + // counter.stop(); + + // std::cout << "L1 cache misses: " << counter.get_l1_misses() << std::endl; + // std::cout << "L2 cache misses: " << counter.get_l2_misses() << std::endl; + + // } catch (const std::exception& e) { + // std::cerr << "Error: " << e.what() << std::endl; + // return 1; + // } + + // return 0; + // } +} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f6c17cd..d753aaf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -134,6 +134,9 @@ if (ENABLE_BACKTRACE) endif() + + + # message(STATUS "CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") # set(CMAKE_ENABLE_EXPORTS 1) @@ -351,15 +354,19 @@ set_target_properties(pipedaltest PROPERTIES EXCLUDE_FROM_ALL true) if (NOT GITHUB_ACTIONS) # Google perftools are not available on Github action servers. - add_executable(profilePlugin - profilePluginMain.cpp - ) - target_link_libraries(profilePlugin PRIVATE profiler ${PIPEDAL_LIBS}) - target_include_directories(profilePlugin PRIVATE ${PIPEDAL_INCLUDES} + add_executable(pipedalProfilePlugin + profilePluginMain.cpp ArmPerformanceCounters.hpp ) + target_link_libraries(pipedalProfilePlugin PRIVATE profiler ${PIPEDAL_LIBS}) + + target_include_directories(pipedalProfilePlugin PRIVATE ${PIPEDAL_INCLUDES} + ) + if (${DEBIAN_ARCHITECTURE} MATCHES arm64) + target_compile_definitions(pipedalProfilePlugin PRIVATE "PIPEDAL_AARCH64") + endif() + - set_target_properties(profilePlugin PROPERTIES EXCLUDE_FROM_ALL true) endif() add_executable(jsonTest diff --git a/src/profilePluginMain.cpp b/src/profilePluginMain.cpp index e75eff4..d3a012c 100644 --- a/src/profilePluginMain.cpp +++ b/src/profilePluginMain.cpp @@ -6,9 +6,11 @@ #include "CommandLineParser.hpp" #include #include +#include "ss.hpp" // apt install google-perftools #include +#include "ArmPerformanceCounters.hpp" using namespace pipedal; using namespace std; @@ -79,6 +81,7 @@ private: struct ProfileOptions { std::string presetName; + std::string presetFileName; float benchmark_seconds = 20; std::string outputFilename = "/tmp/profilePlugin.perf"; bool noProfile = false; @@ -88,6 +91,11 @@ struct ProfileOptions void profilePlugin(const ProfileOptions &profileOptions) { + +#ifdef PIPEDAL_AARCH64 + CacheMissCounter cacheMissCounter; +#endif + size_t nFrames = profileOptions.frameSize; Lv2Log::log_level(LogLevel::Info); @@ -113,25 +121,54 @@ void profilePlugin(const ProfileOptions &profileOptions) // model.Load(); don't start audio. /* *** Load the preset. */ - PresetIndex presetIndex; - model.GetPresets(&presetIndex); - int64_t presetIndexId = 0; - bool presetIndexFound = false; - for (const auto &preset : presetIndex.presets()) + bool presetValid = false; + + if (profileOptions.presetName.length() != 0) { - if (preset.name() == profileOptions.presetName) + PresetIndex presetIndex; + model.GetPresets(&presetIndex); + + int64_t presetIndexId = 0; + bool presetIndexFound = false; + for (const auto &preset : presetIndex.presets()) { - presetIndexId = preset.instanceId(); - presetIndexFound = true; - break; + if (preset.name() == profileOptions.presetName) + { + presetIndexId = preset.instanceId(); + presetIndexFound = true; + break; + } + } + if (!presetIndexFound) + { + throw std::runtime_error("Preset not found."); + } + model.LoadPreset(-1, presetIndexId); + } + else if (profileOptions.presetFileName.length() != 0) + { + std::ifstream f(profileOptions.presetFileName); + if (!f.is_open()) + { + throw std::runtime_error(SS("Unable to load preset file " << profileOptions.presetFileName << ".")); + } + try + { + json_reader reader(f); + Pedalboard pedalboard; + reader.read(&pedalboard); + model.SetPedalboard(-1, pedalboard); + } + catch (const std::exception &e) + { + throw std::runtime_error(SS("Invalid file format: " << profileOptions.presetFileName << ".")); } } - if (!presetIndexFound) + else { - throw std::runtime_error("Preset not found."); + throw std::runtime_error("You must specify either a preset name or a preset file."); } - model.LoadPreset(-1, presetIndexId); auto pedalboard = model.GetCurrentPedalboardCopy(); cout << "uri: " << pedalboard.items()[0].uri() << endl; @@ -153,7 +190,6 @@ void profilePlugin(const ProfileOptions &profileOptions) RingBufferSink ringBufferSink(writerRingbuffer); - // run once to get memory allocations in NAM and ML out of the way. lv2Pedalboard->Run(inputBuffers, outputBuffers, nFrames, &ringBufferWriter); @@ -184,6 +220,10 @@ void profilePlugin(const ProfileOptions &profileOptions) { ProfilerStart(profileOptions.outputFilename.c_str()); } +#ifdef PIPEDAL_AARCH64 + cacheMissCounter.start(); +#endif + auto startTime = std::chrono::system_clock::now(); size_t repetitions = static_cast(48000 * profileOptions.benchmark_seconds / nFrames); @@ -193,12 +233,21 @@ void profilePlugin(const ProfileOptions &profileOptions) } auto elapsedTime = std::chrono::high_resolution_clock::now() - startTime; + +#ifdef PIPEDAL_AARCH64 + cacheMissCounter.stop(); +#endif if (!profileOptions.noProfile) { ProfilerStop(); } std::chrono::milliseconds us = std::chrono::duration_cast(elapsedTime); std::cout << "Ellapsed time: " << (us.count() / 1000.0) << "s" << endl; +#ifdef PIPEDAL_AARCH64 + std::cout << "L1 Cache Misses: " << cacheMissCounter.get_l1_misses() << endl; + // non-functional on Raspberry PI OS. + // std::cout << "L2 Cache Misses: " << cacheMissCounter.get_l2_misses() << endl; +#endif ringBufferSink.Close(); @@ -212,8 +261,10 @@ int main(int argc, char **argv) ProfileOptions profileOptions; bool help = false; CommandLineParser commandLineParser; + std::string presetFileName; commandLineParser.AddOption("w", "wait-for-work", &profileOptions.waitForWork); commandLineParser.AddOption("", "no-profile", &profileOptions.noProfile); + commandLineParser.AddOption("p", "preset-file", &presetFileName); commandLineParser.AddOption("o", "output", &profileOptions.outputFilename); commandLineParser.AddOption("s", "seconds", &profileOptions.benchmark_seconds); commandLineParser.AddOption("h", "help", &help); @@ -225,7 +276,7 @@ int main(int argc, char **argv) cout << "profilePlugin - Generate profiling data for LV2 Plugins" << endl; cout << "Copyright (c) 2024 Robin E. R. Davies" << endl; cout << endl; - cout << "Syntax: profilePlugin preset_name [options...]" << endl; + cout << "Syntax: profilePlugin [preset_name] [options...]" << endl; cout << " where preset_name is the name of a PiPedal preset." << endl; cout << endl; cout << " A google-perf profile capture will be written to " << endl; @@ -234,6 +285,8 @@ int main(int argc, char **argv) cout << "Options:" << endl; cout << " --no-profile:" << endl; cout << " do NOT generate a perf file." << endl; + cout << " -p, --preset-file filename:" << endl; + cout << " Load the specified preset file. " << endl; cout << " -o, --output filename:" << endl; cout << " The file to which profiler data is written. " << endl; cout << " Defaults to /tmp/profilePlugin.perf" << endl; @@ -241,12 +294,16 @@ int main(int argc, char **argv) cout << " Assume that the plugin will load data on the LV2 scheduler thread." << endl; cout << " -s, --seconds time_in_seconds: " << endl; cout << " The number of seconds of audio to process." << endl; - cout << " -h, --help: display this message." << endl; + cout << " -h, --help: display this message." << endl; cout << endl; return help ? EXIT_SUCCESS : EXIT_FAILURE; } - profileOptions.presetName = commandLineParser.Arguments()[0]; + if (commandLineParser.Arguments().size() == 1) + { + profileOptions.presetName = commandLineParser.Arguments()[0]; + } + profileOptions.presetFileName = presetFileName; profilePlugin(profileOptions); }