C++17单调缓冲区和非同步内存池的组合

xtupzzrd  于 2023-10-20  发布在  其他
关注(0)|答案(1)|浏览(100)

bounty已结束。回答此问题可获得+100声望奖励。赏金宽限期22小时后结束getsoubl希望引起更多的注意这个问题。

我对C++17中引入的单调缓冲区和非同步内存池的组合有疑问。下面是一段代码(源代码C++ Weekly - Ep 248

#include <spdlog/spdlog.h>

#include <array>
#include <cassert>
#include <iostream>
#include <memory_resource>
#include <string>
#include <vector>

// Prints if new/delete gets used.
class print_alloc : public std::pmr::memory_resource {
 public:
  print_alloc(std::string name, std::pmr::memory_resource* upstream)
      : m_name(std::move(name)), m_upstream(upstream) {
    assert(upstream);
  }

 private:
  std::string m_name;
  std::pmr::memory_resource* m_upstream;

  void* do_allocate(std::size_t bytes, std::size_t alignment) override {
    spdlog::trace("[{} (alloc)] Size: {} Alignment: {} ...", m_name, bytes,
                  alignment);
    auto result = m_upstream->allocate(bytes, alignment);
    spdlog::trace("[{} (alloc)] ... Address: {}", m_name, result);
    return result;
  }

  std::string format_destroyed_bytes(std::byte* p, const std::size_t size) {
    std::string result = "";
    bool in_string = false;

    auto format_char = [](bool& in_string, const char c, const char next) {
      auto format_byte = [](const char byte) {
        return fmt::format(" {:02x}", static_cast<unsigned char>(byte));
      };

      if (std::isprint(static_cast<int>(c))) {
        if (!in_string) {
          if (std::isprint(static_cast<int>(next))) {
            in_string = true;
            return fmt::format(" \"{}", c);
          } else {
            return format_byte(c);
          }
        } else {
          return std::string(1, c);
        }
      } else {
        if (in_string) {
          in_string = false;
          return '"' + format_byte(c);
        }
        return format_byte(c);
      }
    };

    std::size_t pos = 0;
    for (; pos < std::min(size - 1, static_cast<std::size_t>(32)); ++pos) {
      result += format_char(in_string, static_cast<char>(p[pos]),
                            static_cast<char>(p[pos + 1]));
    }
    result += format_char(in_string, static_cast<char>(p[pos]), 0);
    if (in_string) {
      result += '"';
    }
    if (pos < (size - 1)) {
      result += " <truncated...>";
    }
    return result;
  }

  void do_deallocate(void* p, std::size_t bytes,
                     std::size_t alignment) override {
    spdlog::trace(
        "[{} (dealloc)] Address: {} Dealloc Size: {} Alignment: {} Data: {}",
        m_name, p, bytes, alignment,
        format_destroyed_bytes(static_cast<std::byte*>(p), bytes));
    m_upstream->deallocate(p, bytes, alignment);
  }

  bool do_is_equal(
      const std::pmr::memory_resource& other) const noexcept override {
    return this == &other;
  }
};

template <typename Container, typename... Values>
auto create_container(auto* resource, Values&&... values) {
  Container result{resource};
  result.reserve(sizeof...(values));
  (result.emplace_back(std::forward<Values>(values)), ...);
  return result;
};

int main() {
  spdlog::set_level(spdlog::level::trace);

  print_alloc default_alloc{"Rogue PMR Allocation!",
                            std::pmr::null_memory_resource()};
  std::pmr::set_default_resource(&default_alloc);

  print_alloc oom{"Out of Memory", std::pmr::null_memory_resource()};

  std::array<std::uint8_t, 32768> buffer{};
  std::pmr::monotonic_buffer_resource underlying_bytes(buffer.data(),
                                                       buffer.size(), &oom);

  print_alloc monotonic{"Monotonic Array", &underlying_bytes};

  std::pmr::unsynchronized_pool_resource unsync_pool(&monotonic);

  print_alloc pool("Pool", &unsync_pool);

  for (int i = 0; i < 10; ++i) {
    spdlog::debug("Starting Loop Iteration");
    auto vec = create_container<std::pmr::vector<std::pmr::string>>(
        &pool, "Hello", "World", "Hello Long String", "Another Long String");
    spdlog::trace("Emplacing Long String");
    vec.emplace_back("a different long string");
    spdlog::trace("Emplacing Long String");
    vec.emplace_back("a different long string 1");
    spdlog::trace("Emplacing Long String");
    vec.emplace_back("a different long string");
    spdlog::trace("Emplacing Long String");
    vec.emplace_back("a different long string");
    spdlog::trace("Emplacing Short String");
    vec.emplace_back("bob");
    spdlog::trace("Emplacing Short String");
    vec.emplace_back("was");
    spdlog::trace("Erasing First Element");
    vec.erase(vec.begin());
    spdlog::trace("Erasing First Element");
    vec.erase(vec.begin());
    spdlog::trace("Erasing First Element");
    vec.erase(vec.begin());
    spdlog::debug("Finishing Loop Iteration");
    // vec.push_back("Hello Long World");
  }

  spdlog::debug("Exiting Main");
}

Demo
基于单调缓冲区的implementation,在释放期间不做任何事情。缓冲区只增长。
检查打印输出,如果这个程序,在每次迭代中,使用相同的内存区域。这不符合单调缓冲区的实现。我错过了什么。在每次迭代中,从单调缓冲区中检索的分配块被重用

k3fezbri

k3fezbri1#

检查打印输出,如果这个程序,在每次迭代中,使用相同的内存区域。这不符合单调缓冲区的实现。我错过了什么。在每次迭代中,从单调缓冲区中检索的分配块被重用
你所说的是unsynchronized_pool_resourcemonotonic_buffer_resources组合的预期结果。你说monotonic_buffer_resources应该分配新的内存,永远不要重用以前分配的内存-它!您所看到的行为是unsynchorinized_pool_resource的结果,unsynchorinized_pool_resource是一种旨在从其底层资源(在本例中为monotonic_buffer_resources)中池化和重用内存的资源。当内存被释放时,池资源保留该内存以供将来分配,而不是立即将其返回给其资源。
monotonic_buffer_resource仍然表现单调-unsynchronized_pool_resource的池化行为显示了这些迭代中的内存重用。
结合这两个允许非常有效的内存使用。您的程序可以从monotonic_buffer_resource获得快速(和可预测)的内存分配,并从unsynchronized_pool_resource获得内存重用的效率。
希望这能让你明白。

演示

这里有一个简单的例子,利用这两个:

#include <iostream>
#include <memory_resource>

int main() {
    char buffer[1024];
    std::pmr::monotonic_buffer_resource monotonicResource(buffer, sizeof(buffer));

    // Use an unsynchronized_pool_resource on top of the monotonic_buffer_resource
    std::pmr::unsynchronized_pool_resource poolResource(&monotonicResource);

    // Allocate and deallocate memory using the unsynchronized_pool_resource
    for (int i = 0; i < 5; ++i) {
        std::cout << "Iteration " << i << ":\n";

        // Allocate memory
        void* ptr = poolResource.allocate(200);  // request 200 bytes from the pool
        std::cout << "Allocated at: " << ptr << "\n";

        // Deallocate memory (returning it to the pool! Not the monotonic resource)
        poolResource.deallocate(ptr, 200);

        std::cout << "Deallocated.\n\n";
    }

    return 0;
}

当运行时,会看到这样的输出:

Iteration 0:
Allocated at: 00000244C378AB00
Deallocated

Iteration 1:
Allocated at: 00000244C378AB00
Deallocated

Iteration 2:
Allocated at: 00000244C378AB00
Deallocated

Iteration 3:
Allocated at: 00000244C378AB00
Deallocated

Iteration 4:
Allocated at: 00000244C378AB00
Deallocated

这里我们可以看到如果我们 * 只 * 使用monotonic_buffer_resource的情况:

#include <iostream>
#include <memory_resource>

int main() {
    char buffer[1024]; 
    std::pmr::monotonic_buffer_resource monotonicResource(buffer, sizeof(buffer));

    // Allocate memory directly from the monotonic_buffer_resource
    for (int i = 0; i < 5; ++i) {
        std::cout << "Iteration " << i << ":\n";
        
        // Allocate memory directly from the motonic resource
        void* ptr = monotonicResource.allocate(200);  // request 200 bytes
        std::cout << "Allocated at: " << ptr << "\n\n";
    }

    return 0;
}

输出是你所假设的,你在每次迭代中看到一个不同的内存地址-因为monotonic_buffer_resource从它的缓冲区分配新的内存块,从不重用以前分配的内存。

Iteration 0:
Allocated at: 000000394CEFF270

Iteration 1:
Allocated at: 000000394CEFF338

Iteration 2:
Allocated at: 000000394CEFF400

Iteration 3:
Allocated at: 000000394CEFF4C8

Iteration 4:
Allocated at: 000000394CEFF590

相关问题