Hone logo
Hone
Problems

Rust Memory Profiler Challenge: Track Allocations and Deallocations

This challenge asks you to build a basic memory profiler in Rust. Memory profiling is crucial for identifying memory leaks, optimizing memory usage, and understanding the memory footprint of your applications. Your profiler will track memory allocations and deallocations, providing insights into where memory is being used.

Problem Description

You are tasked with creating a simple memory profiler library in Rust. The profiler should track memory allocations and deallocations performed by the program. It should provide a mechanism to report the total allocated memory, the number of allocations, and potentially a list of the largest allocations. The profiler should be lightweight and have minimal overhead on the program's execution.

Key Requirements:

  • Allocation Tracking: The profiler must track each memory allocation made by the program.
  • Deallocation Tracking: The profiler must track each memory deallocation.
  • Total Memory Usage: Provide a function to retrieve the total amount of memory currently allocated.
  • Allocation Count: Provide a function to retrieve the total number of allocations made.
  • Reporting: Provide a function to generate a report summarizing the memory usage statistics. This report should include:
    • Total allocated memory (in bytes).
    • Number of allocations.
  • Minimal Overhead: The profiler should introduce minimal performance overhead to the program. Avoid excessive logging or complex data structures that significantly impact execution speed.
  • Thread Safety: The profiler should be thread-safe, allowing it to be used in multi-threaded applications without data races.

Expected Behavior:

The profiler should operate silently in the background, tracking memory allocations and deallocations. The user should be able to query the profiler for statistics at any time. The profiler should not interfere with the program's normal execution unless explicitly queried for a report.

Edge Cases to Consider:

  • Multiple Threads: Ensure the profiler correctly tracks allocations and deallocations across multiple threads.
  • Large Allocations: Handle very large allocations without causing performance issues.
  • Frequent Allocations/Deallocations: The profiler should be efficient even with a high rate of allocations and deallocations.
  • Deallocating Memory Multiple Times: While Rust's ownership system generally prevents this, consider how your profiler would handle unexpected double-frees (though this is more of a robustness consideration than a core requirement).

Examples

Example 1:

Input: A program that allocates 100 bytes, then 200 bytes, then deallocates 100 bytes.
Output:
Total Allocated Memory: 300 bytes
Number of Allocations: 2

Explanation: The program initially allocates 100 bytes, then 200 more, resulting in a total of 300 bytes allocated. After deallocating 100 bytes, the total becomes 200. The number of allocations is 2 (100 + 200).

Example 2:

Input: A multi-threaded program where thread 1 allocates 50 bytes and thread 2 allocates 75 bytes.
Output:
Total Allocated Memory: 125 bytes
Number of Allocations: 2

Explanation: The profiler correctly aggregates allocations from different threads.

Example 3: (Edge Case - Frequent Allocations)

Input: A program that allocates and deallocates small chunks of memory (e.g., 10 bytes) repeatedly in a loop.
Output: (Should be consistent and not crash or significantly slow down the program)
Total Allocated Memory: (Varies depending on the loop's execution)
Number of Allocations: (High number, reflecting the loop's iterations)

Explanation: The profiler should handle frequent allocations and deallocations efficiently without introducing significant overhead.

Constraints

  • Memory Overhead: The profiler's own memory footprint should be minimal (ideally less than 1KB).
  • Performance Overhead: The profiler should introduce a performance overhead of no more than 5% on typical workloads. This is a qualitative constraint, and you'll need to consider how to minimize overhead in your design.
  • Input Format: The profiler should be usable as a library, with functions that can be called from any part of the program.
  • Rust Version: Target Rust 1.65 or later.
  • Thread Safety: The profiler must be thread-safe. Use appropriate synchronization primitives (e.g., Mutex, RwLock) to protect shared data.

Notes

  • You can use the std::alloc module for low-level memory allocation information, but you are not required to.
  • Consider using a Mutex or RwLock to protect the profiler's internal state from concurrent access.
  • Think about how to efficiently store and retrieve allocation information. A simple vector might be sufficient for this challenge, but consider more advanced data structures if you want to optimize for performance.
  • Focus on the core functionality of tracking allocations and deallocations and providing basic statistics. Advanced features like call stack tracing or detailed allocation analysis are beyond the scope of this challenge.
  • Testing is crucial. Write unit tests to verify that the profiler correctly tracks allocations and deallocations in various scenarios, including multi-threaded programs.
Loading editor...
rust