diff --git a/aidge_core/mem_info.py b/aidge_core/mem_info.py index c7ca85bbd73bd205850b19616e53fda210749a80..87f286bcbcde3bc656e7997e65ec0078437e72e9 100644 --- a/aidge_core/mem_info.py +++ b/aidge_core/mem_info.py @@ -5,6 +5,9 @@ from pathlib import Path import aidge_core from typing import Tuple, List +import matplotlib.pyplot as plt +import aidge_core.mem_info +import numpy as np # Default memory management, which can be used for development def compute_default_mem_info(scheduler: aidge_core.Scheduler) -> Tuple[int, List]: @@ -41,20 +44,72 @@ def compute_default_mem_info(scheduler: aidge_core.Scheduler) -> Tuple[int, List mem_info[node] = [] # No meminfo for producer return mem_size, mem_info +def log_meminfo(mem_manager:aidge_core.MemoryManager, path: Path, diplay_names:bool): + """Generate a graph representing the memory allocation of each ouputs. -def _gnuplot_installed(): - try: - # Run gnuplot with the --version flag and capture the output - subprocess.run(["gnuplot", "--version"]) - return True - except FileNotFoundError: - aidge_core.Log.warn("Gnuplot is not installed.") - return False - except subprocess.CalledProcessError: - aidge_core.Log.warn("Gnuplot command found but failed to run.") - return False + Block with the smae color correspond to the same memory plane. -def generate_optimized_memory_info(scheduler: aidge_core.Scheduler, stats_folder: Path = None, wrapping: bool = False) -> Tuple[int, List[dict]]: + :param mem_manager: Memory manager to log + :type mem_manager: aidge_core.memory_manager + :param path: Path where to save the figure + :type path: Path + :param diplay_names: If True Node names are diplayed alongside their block + :type diplay_names: bool + """ + + max_lifetime = mem_manager.get_max_lifetime() + + # peak_usage in kwords + peak_usage = mem_manager.get_peak_usage() / 1024 + + # Set figure size 1920x1080 px + plt.figure(figsize=(19.20, 10.80)) + # Same color for each planes + colors = plt.cm.viridis(np.linspace(0, 1, len(mem_manager.get_planes()) + 1)) + color_id = 1 + for node, planes in mem_manager.get_planes().items(): + for plane in planes: + cont_offset = plane.get_contiguous_offset() + cont_size = plane.get_contiguous_size() + allocated = plane.mem_space.allocated + released = plane.mem_space.released + is_released = released >= 0 and not plane.mem_space.dependencies + x_start = allocated + y_start = cont_offset / 1024.0 + y_end = (cont_offset + cont_size) / 1024.0 + x_end = max_lifetime if not is_released else released + + plt.fill_betweenx( + [y_start, y_end], + x_start, + x_end + 1, + color=colors[color_id % len(colors)] + ) + + if diplay_names: + # Rotation for lisibility! + plt.text(x_end,y_end, node.name(), rotation=45) + color_id += 1 + + plt.xlim(0, max_lifetime + 1) + plt.ylim(0, peak_usage) + plt.axhline(y=peak_usage, color='red', linestyle='--') + plt.text(0, peak_usage, f'Peak usage = {peak_usage} KWords', color='red') + plt.xlabel("Time") + plt.ylabel("Memory usage (KWords)") + plt.title("Memory Usage Over Time") + plt.grid(True) + ax = plt.gca() + ax.spines['top'].set_visible(False) + ax.spines['right'].set_visible(False) + folder_path = path.parent + folder_path.mkdir(parents=True, exist_ok=True) + plt.savefig(path) + plt.close() + aidge_core.Log.notice(f"Generated memory management info at: {path}") + + +def generate_optimized_memory_info(scheduler: aidge_core.Scheduler, stats_folder: Path = None, wrapping: bool = False, display_names: bool=True) -> Tuple[int, List[dict]]: """Generates optimized memory information for a computation graph managed by a scheduler. This function analyzes the memory usage of a computation graph, determining the memory peak @@ -70,6 +125,8 @@ def generate_optimized_memory_info(scheduler: aidge_core.Scheduler, stats_folder :param wrapping: Boolean flag to enable or disable wrap-around buffer optimization. Defaults to `False`. :type wrapping: bool, optional + :param diplay_names: If True Node names are diplayed in the memory plot alongside their block, defaults to False + :type diplay_names: bool, optional :return: A tuple containing the peak memory size and a list of memory information for each scheduled node. The memory information for each node includes details such as size, offset, stride, length, count, and optional wrap-around details. @@ -88,18 +145,8 @@ def generate_optimized_memory_info(scheduler: aidge_core.Scheduler, stats_folder nodes_at_input = [n[0] for n in scheduler.graph_view().inputs()] if stats_folder is not None: - if _gnuplot_installed(): - # Use gnuplot to generate the log - os.makedirs(str(Path(stats_folder) / "graph"), exist_ok=True) - mem_manager.log("memory_info") - os.chmod("memory_info_plot.gnu", 0o777) - os.system("./memory_info_plot.gnu") - shutil.move("memory_info", str(Path(stats_folder) / "graph" / "memory_info")) - shutil.move("memory_info_plot.png", str( - Path(stats_folder) / "graph" / "memory_info_plot.png")) - os.remove("memory_info_plot.gnu") - else: - aidge_core.Log.warn("Warning: gnuplot is not installed, could not generate stat folder.") + log_meminfo(mem_manager, Path(stats_folder) / "memory_info.png", display_names) + # In the export, we currently use an unified memory buffer whose size # is determined by the memory peak usage mem_size = mem_manager.get_peak_usage() diff --git a/pyproject.toml b/pyproject.toml index c5c8a0600dda804ce13dfb8d4c6874ae967e87e6..610b5f8c226fcf2f040a6d6c22cffcb0498a0f8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,8 @@ name="aidge_core" description="Core algorithms for operators and graph of the AIDGE framework" dependencies = [ "numpy>=1.21.6", - "Jinja2>=3.1.2" + "Jinja2>=3.1.2", + "matplotlib" ] requires-python = ">= 3.7" readme = "README.md" diff --git a/python_binding/scheduler/pybind_MemoryManager.cpp b/python_binding/scheduler/pybind_MemoryManager.cpp index 0f18db405bec0aee9637f2e5f2ecc7b71e502cc5..3fce92349f28e3c6a897356dee60359c1797d9ca 100644 --- a/python_binding/scheduler/pybind_MemoryManager.cpp +++ b/python_binding/scheduler/pybind_MemoryManager.cpp @@ -36,10 +36,10 @@ void init_MemoryManager(py::module& m) .def_readwrite("released", &MemoryManager::MemorySpace::released); py::class_<MemoryManager::MemoryPlane, std::shared_ptr<MemoryManager::MemoryPlane>>(m, "MemoryPlane") - .def(py::init<std::shared_ptr<MemoryManager::MemorySpace>, + .def(py::init<std::shared_ptr<MemoryManager::MemorySpace>, MemoryManager::Clock_T, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int>(), - py::arg("mem_space"), py::arg("clock"), py::arg("offset"), + py::arg("mem_space"), py::arg("clock"), py::arg("offset"), py::arg("size"), py::arg("stride"), py::arg("length"), py::arg("count")) .def_readwrite("mem_space", &MemoryManager::MemoryPlane::memSpace) .def_readwrite("allocated", &MemoryManager::MemoryPlane::allocated) @@ -101,7 +101,6 @@ void init_MemoryManager(py::module& m) .def("get_nb_planes", (unsigned int (MemoryManager::*)(std::shared_ptr<MemoryManager::MemorySpace>) const) &MemoryManager::getNbPlanes, py::arg("mem_space")) .def("get_current_tick", &MemoryManager::getCurrentTick) .def("tick", &MemoryManager::tick) - .def("log", &MemoryManager::log, py::arg("file_name")) ; } diff --git a/src/scheduler/MemoryManager.cpp b/src/scheduler/MemoryManager.cpp index ba805f919a607e0b2ae3272d173aa11360548fa7..05f461b82f16b6af4ed412b7336aa2328bcafbe1 100644 --- a/src/scheduler/MemoryManager.cpp +++ b/src/scheduler/MemoryManager.cpp @@ -634,152 +634,6 @@ void Aidge::MemoryManager::tick() ++mClock; } -void Aidge::MemoryManager::log(const std::string& fileName) const -{ - auto memData = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen(fileName.c_str(), "w"), &std::fclose); - - if (!memData) { - AIDGE_THROW_OR_ABORT(std::runtime_error, - "Could not create memory layout log file: {}", fileName); - } - - auto gnuplot = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen((fileName + "_plot.gnu").c_str(), "w"), &std::fclose); - - if (!gnuplot) { - AIDGE_THROW_OR_ABORT(std::runtime_error, - "Could not create memory layout log file: {}", (fileName + "_plot.gnu")); - } - - const Clock_T maxLifetime = getMaxLifetime(); - const unsigned int peakUsage = getPeakUsage(); - - fmt::print(gnuplot.get(), "#!/usr/bin/gnuplot\n"); - fmt::print(gnuplot.get(), "set term pngcairo size 1280,768 noenhanced\n"); - fmt::print(gnuplot.get(), "set output \"{}\"\n", fileName + "_plot.png"); - fmt::print(gnuplot.get(), "set xrange [{}:{}]\n", 0, maxLifetime + 1); - fmt::print(gnuplot.get(), "set yrange [{}:{}]\n", 0, 1.05 * (peakUsage / 1024.0)); - fmt::print(gnuplot.get(), "set xlabel \"Time\"\n"); - fmt::print(gnuplot.get(), "set ylabel \"Memory usage (KWords)\"\n"); - fmt::print(gnuplot.get(), "set grid\n"); - fmt::print(gnuplot.get(), "set xtics 1\n"); - fmt::print(gnuplot.get(), "unset key\n"); - fmt::print(gnuplot.get(), "set palette rgbformulae 30,31,32\n"); - fmt::print(gnuplot.get(), "unset colorbox\n"); - fmt::print(gnuplot.get(), "N={}\n", mMemPlanes.size() + 1); - - unsigned int objectId = 1; - unsigned int labelId = 1; - - for (std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> > - ::const_iterator it = mMemPlanes.begin(), itEnd = mMemPlanes.end(); - it != itEnd; ++it) - { - const std::string name = (*it).first->name(); - fmt::print(memData.get(), "{}\n", name); - - double minX = -1; - unsigned int maxY = 0; - - for (std::vector<MemoryPlane>::const_iterator itPlanes - = (*it).second.begin(), itPlanesBegin = (*it).second.begin(), - itPlanesEnd = (*it).second.end(); itPlanes != itPlanesEnd; - ++itPlanes) - { - const unsigned int contiguousOffset - = (*itPlanes).getContiguousOffset(); - const unsigned int contiguousSize = (*itPlanes).getContiguousSize(); - const unsigned int wrappedOffset = (*itPlanes).getWrappedOffset(); - const unsigned int wrappedSize = (*itPlanes).getWrappedSize(); - - const Clock_T allocated = (*itPlanes).allocated; - const Clock_T released = (*itPlanes).memSpace->released; - const bool isReleased = (released >= 0 - && (*itPlanes).memSpace->dependencies.empty()); - - fmt::print(memData.get(), " {} {} ({:#08x}U) -> {} ({:#08x}U)", - (itPlanes - itPlanesBegin), contiguousOffset, contiguousOffset, - (contiguousOffset + contiguousSize), (contiguousOffset + contiguousSize)); - - if (wrappedSize > 0) { - fmt::print(memData.get(), " + {} ({:#08x}U) -> {} ({:#08x}U)", - wrappedOffset, wrappedOffset, - (wrappedOffset + wrappedSize), (wrappedOffset + wrappedSize)); - } - - fmt::print(memData.get(), " [{}] @ {}", (*itPlanes).getSize(), allocated); - - if (isReleased) { - fmt::print(memData.get(), " to {}", released); - } - - fmt::print(memData.get(), "\n"); - - // Gnuplot - const double startX = allocated; - - if (startX < minX || minX < 0) { - minX = startX; - maxY = contiguousOffset + contiguousSize; - } - - if ((*itPlanes).size != (*itPlanes).stride) { - for (unsigned int offset = contiguousOffset; - offset < contiguousOffset + contiguousSize; - offset += (*itPlanes).stride) - { - fmt::print(gnuplot.get(), "set object {} rectangle from {},{} to {},{} fc palette frac ({} * 1./N)\n", - (allocated * 100 + objectId), startX, (offset / 1024.0), - (((isReleased) ? released : maxLifetime) + 1), - (std::min((offset + (*itPlanes).size), - contiguousOffset + contiguousSize) / 1024.0), - labelId); - ++objectId; - } - } - else { - fmt::print(gnuplot.get(), "set object {} rectangle from {},{} to {},{} fc palette frac ({} * 1./N)\n", - (allocated * 100 + objectId), startX, (contiguousOffset / 1024.0), - (((isReleased) ? released : maxLifetime) + 1), - ((contiguousOffset + contiguousSize) / 1024.0), - labelId); - ++objectId; - } - - if (wrappedSize > 0) { - fmt::print(gnuplot.get(), "set object {} rectangle from {},{} to {},{} fc palette frac ({} * 1./N)\n", - (allocated * 100 + objectId), startX, (wrappedOffset / 1024.0), - (((isReleased) ? released : maxLifetime) + 1), - ((wrappedOffset + contiguousSize) / 1024.0), - labelId); - ++objectId; - - fmt::print(gnuplot.get(), "set arrow from {},{} to {},{} nohead\n", - startX, (contiguousOffset / 1024.0), - (startX + 0.1), (contiguousOffset / 1024.0)); - - fmt::print(gnuplot.get(), "set arrow from {},{} to {},{} nohead\n", - (startX + 0.05), ((contiguousOffset + contiguousSize) / 1024.0), - (startX + 0.05), (wrappedOffset / 1024.0)); - } - } - - fmt::print(gnuplot.get(), "set label {} '{}' at {},{} rotate by 30 font \",8\" offset char 0.5,0.5\n", - labelId, name, minX, (maxY / 1024.0)); - ++labelId; - - fmt::print(memData.get(), "\n"); - } - - fmt::print(gnuplot.get(), "set arrow from 0,{} to {},{} nohead lc rgb \"red\"\n", - (peakUsage / 1024.0), (maxLifetime + 1), - (peakUsage / 1024.0)); - - fmt::print(gnuplot.get(), "set label {} 'Peak usage = {} KWords' at 0,{} textcolor rgb \"red\" offset char 0.5,0.5\n", - labelId, (peakUsage / 1024.0), (peakUsage / 1024.0)); - - fmt::print(gnuplot.get(), "plot 0\n"); -} - unsigned int Aidge::MemoryManager::onStack(unsigned int size) { unsigned int offset = 0; diff --git a/unit_tests/scheduler/Test_MemoryManager.cpp b/unit_tests/scheduler/Test_MemoryManager.cpp index a4941203644b7ba291682f3932926a36fa83b745..b6cedfac47d53e0f8ab464fad8a2f6cc6c8dcc15 100644 --- a/unit_tests/scheduler/Test_MemoryManager.cpp +++ b/unit_tests/scheduler/Test_MemoryManager.cpp @@ -136,7 +136,6 @@ TEST_CASE("allocate1", "[MemoryManager]") { REQUIRE(memManager.getPlanes(node4).back().memSpace->allocated == 3); REQUIRE(memManager.getPlanes(node4).back().memSpace->released == 4); - memManager.log("MemoryManager_allocate1.log"); } TEST_CASE("allocate2", "[MemoryManager]") { @@ -281,7 +280,6 @@ TEST_CASE("allocate2", "[MemoryManager]") { REQUIRE(memManager.getPlanes(node4).back().memSpace->allocated == 3); REQUIRE(memManager.getPlanes(node4).back().memSpace->released == 4); - memManager.log("MemoryManager_allocate2.log"); } TEST_CASE("allocate3", "[MemoryManager]") { @@ -438,7 +436,6 @@ TEST_CASE("allocate3", "[MemoryManager]") { REQUIRE(memManager.getPlanes(node4).back().memSpace->allocated == 0); REQUIRE(memManager.getPlanes(node4).back().memSpace->released == 4); - memManager.log("MemoryManager_allocate3.log"); } TEST_CASE("allocate3_wrapAround", "[MemoryManager]") { @@ -595,5 +592,4 @@ TEST_CASE("allocate3_wrapAround", "[MemoryManager]") { REQUIRE(memManager.getPlanes(node4).back().memSpace->allocated == 0); REQUIRE(memManager.getPlanes(node4).back().memSpace->released == 4); - memManager.log("MemoryManager_allocate3_wrapAround.log"); }