【C++内存管理】内存池

1. C++17 memory_resource 内存池

C++17 引入了一个新的内存资源抽象层 - memory_resource。其主要目的是将内存的分配和回收与具体的数据结构解耦,从而允许开发人员使用不同的内存管理策略,提高内存管理的灵活性。memory_resource 是一个基类,可以用于创建和配置具有不同内存管理行为的内存池。

1.1 memory_resource 基类

memory_resource 是一个抽象基类,提供了三个纯虚函数,需要被子类实现:

  • do_allocate(size_t bytes, size_t alignment):根据指定的字节和对齐方式分配内存。
  • do_deallocate(void* p, size_t bytes, size_t alignment):释放之前分配的内存。
  • do_is_equal(const memory_resource& other) const noexcept:判断两个内存资源是否等效。

以下是 memory_resource 基类的一个简单实现:

class MyMemoryResource : public std::pmr::memory_resource
{
    
    
protected:
    void* do_allocate(size_t bytes, size_t alignment) override
    {
    
    
        return ::operator new(bytes);
    }

    void do_deallocate(void* p, size_t bytes, size_t alignment) override
    {
    
    
        ::operator delete(p);
    }

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

1.2 内存资源的使用

要使用 memory_resource,通常需要通过它来创建一些容器。例如,我们可以用 memory_resource 创建一个 std::pmr::vector

MyMemoryResource myMemoryResource;
std::pmr::vector<int> vec(&myMemoryResource);

这样,vec 就会使用 myMemoryResource 来进行内存的分配和回收。

1.3 全局和本地内存资源

C++ 提供了两种内存资源:全局内存资源和本地内存资源。全局内存资源是通过 std::pmr::get_default_resource() 获得的,所有没有指定内存资源的 std::pmr 对象都会使用它。本地内存资源是创建 std::pmr 对象时指定的,它会覆盖全局内存资源。

例如:

std::pmr::vector<int> vec; // 使用全局内存资源
std::pmr::vector<int> vec(&myMemoryResource); // 使用本地内存资源

1.4 其他内存资源类型

除了用户自定义的内存资源,C++ 还提供了几种预定义的内存资源:

  • std::pmr::new_delete_resource():用 newdelete 进行内存分配和回收。
  • std::pmr::null_memory_resource():任何尝试分配内存的操作都会抛出 std::bad_alloc 异常。
  • std::pmr::unsynchronized_pool_resourcestd::pmr::synchronized_pool_resource:内存池资源,分别为线程不安全和线程安全版本。

下面的章节将会继续介绍这两种内存池资源。

2. struct unsynchronized_pool_resource

unsynchronized_pool_resource 是 C++17 中 std::pmr::memory_resource 的一种实现。这种内存资源管理方式以内存池的形式存在,其主要的优势在于减少内存分配和回收的开销。这种资源被设计为线程不安全,所以对于多线程环境需要开发者自己进行管理和同步。

2.1 unsynchronized_pool_resource 的基本原理

unsynchronized_pool_resource 内部维护了一系列的内存池,每个内存池负责管理一定大小范围的内存块。当需要分配内存时,内存池资源会根据请求的大小选择一个适当的内存池,并从中分配一个内存块。如果对应的内存池没有可用的内存块,那么内存池资源会从上游内存资源那里获取更多的内存。

2.2 unsynchronized_pool_resource 的使用

要使用 unsynchronized_pool_resource,需要创建一个 unsynchronized_pool_resource 对象,并将其作为其他 std::pmr 对象的内存资源。以下是一个例子:

std::pmr::unsynchronized_pool_resource pool;
std::pmr::vector<int> vec(&pool);

在这个例子中,vec 将使用 pool 来进行内存的分配和回收。

2.3 unsynchronized_pool_resource 的配置

unsynchronized_pool_resource 具有一些可配置的参数,包括最大和最小块大小、最大块数等。这些参数可以通过 std::pmr::pool_options 结构体来设置,后面的章节将详细介绍这个结构体。

2.4 unsynchronized_pool_resource 的线程安全性

如其名所示,unsynchronized_pool_resource 是线程不安全的。如果在多线程环境中使用 unsynchronized_pool_resource,则必须确保每个线程都使用自己的 unsynchronized_pool_resource,或者通过某种同步机制(如互斥锁)来保护 unsynchronized_pool_resource

虽然这增加了使用 unsynchronized_pool_resource 的复杂性,但是由于避免了同步的开销,unsynchronized_pool_resource 在某些场景下可能会比同步的内存池更高效。

2.5 unsynchronized_pool_resource 与 new/delete 的对比

相比于使用 newdelete 进行内存管理,unsynchronized_pool_resource 主要的优势在于减少了内存分配和回收的开销。特别是对于频繁分配和回收小块内存的场景,使用 unsynchronized_pool_resource 可以显著提高性能。

此外,由于 unsynchronized_pool_resource 可以回收并重用内存块,因此它还可以减少内存碎片,从而提高内存利用率。

3. pool_options 内存池配置

pool_options 是 C++17 中用于配置 unsynchronized_pool_resourcesynchronized_pool_resource 的结构体。它提供了一些参数,可以用于调整内存池的行为,以适应不同的应用场景。

3.1 pool_options 的参数

pool_options 结构体包括以下几个参数:

  • max_blocks_per_chunk:每个块中最多可以包含的块数量。这个参数用于限制内存池从上游内存资源一次性获取的内存量。如果设置为 0,则表示没有限制。

  • largest_required_pool_block:要求内存池能够处理的最大块大小。这个参数用于确定内存池应该管理哪些大小的内存块。

以下是一个例子:

std::pmr::pool_options options;
options.max_blocks_per_chunk = 1024;
options.largest_required_pool_block = 512;

3.2 pool_options 的使用

要使用 pool_options,需要在创建 unsynchronized_pool_resourcesynchronized_pool_resource 时将其作为参数传入。以下是一个例子:

std::pmr::pool_options options;
options.max_blocks_per_chunk = 1024;
options.largest_required_pool_block = 512;

std::pmr::unsynchronized_pool_resource pool(options);

在这个例子中,pool 将根据 options 中的参数来进行内存的管理。

3.3 pool_options 的影响

通过调整 pool_options 中的参数,可以影响内存池的行为,从而优化内存的使用。

例如,如果知道应用程序会频繁地分配和回收一定大小的内存块,可以通过设置 largest_required_pool_block 来让内存池专门处理这些内存块,从而提高内存的利用率。

同样,如果知道应用程序会分配大量的内存块,可以通过增加 max_blocks_per_chunk 来减少从上游内存资源获取内存的次数,从而减少内存管理的开销。

总的来说,pool_options 提供了一种灵活的方式来配置内存池,使其能够根据应用程序的需求提供最优的内存管理策略。

4. memory_resource

我们已经在第一部分对 memory_resource 做了基础的介绍。memory_resource 是 C++17 中提供的一种内存资源抽象,它是所有内存资源的基类。在这一部分,我们将深入探讨 memory_resource 的几个关键的方法。

4.1 memory_resource::allocate()

这个方法是用户用来从 memory_resource 请求内存的接口。它接收两个参数:需要的字节数和对齐要求。然后,它将调用 do_allocate 方法来分配内存。

4.2 memory_resource::deallocate()

这个方法用于将之前分配的内存归还给 memory_resource。它接收三个参数:要归还的内存指针、该内存块的大小和对齐要求。然后,它将调用 do_deallocate 方法来归还内存。

4.3 memory_resource::is_equal()

这个方法用于判断两个 memory_resource 是否相等。在默认情况下,两个 memory_resource 是相等的,当且仅当它们是同一个对象。

4.4 memory_resource::do_allocate(), memory_resource::do_deallocate(), memory_resource::do_is_equal()

这三个方法是 memory_resource 的纯虚函数,必须由子类实现。具体的内存分配和回收行为,以及两个内存资源的比较方式,取决于这三个方法的实现。

例如,unsynchronized_pool_resourcesynchronized_pool_resource 就是通过这三个方法实现了自己的内存管理策略。

4.5 memory_resource 的使用方式

memory_resource 通常不直接使用,而是作为创建其他 std::pmr 对象时的参数,例如 std::pmr::vectorstd::pmr::string

以下是一个例子:

std::pmr::synchronized_pool_resource pool;
std::pmr::vector<int> vec(&pool);

在这个例子中,vec 将使用 pool 来进行内存的分配和回收。

memory_resource 提供了一个通用的内存资源接口,让你可以根据应用程序的需求来自定义内存管理策略。通过使用 memory_resource,你可以创建出对内存使用更有效率的数据结构。

5. unsynchronized_pool_resource 线程不安全

unsynchronized_pool_resource 是一种为 C++17 标准库提供的内存资源类,用于从预分配的内存池中进行内存分配和回收。关键的一点是,unsynchronized_pool_resource 不提供线程安全的保证。这是它与 synchronized_pool_resource 的主要区别。我们接下来详细讨论这一点。

5.1 何为线程不安全

"线程不安全"的意思是,如果同一个 unsynchronized_pool_resource 实例被多个线程同时使用,没有其他的同步机制(例如互斥锁)来保护这个实例,那么可能会导致数据竞争和未定义的行为。也就是说,多个线程不能同时对一个 unsynchronized_pool_resource 实例进行读写。

5.2 unsynchronized_pool_resource 的线程不安全

由于 unsynchronized_pool_resource 不提供内部的同步机制,所以在多线程环境中使用 unsynchronized_pool_resource 需要特别小心。以下是两种常见的使用方式:

  • 每个线程都使用自己的 unsynchronized_pool_resource 实例。这种方式的好处是,由于没有竞争,每个线程的内存操作都可以尽可能快。但是,这种方式可能会导致内存的使用不够高效,因为每个线程都需要预分配自己的内存池。

  • 使用一个共享的 unsynchronized_pool_resource 实例,并使用外部的同步机制(例如互斥锁)来保护这个实例。这种方式的好处是,可以更高效地利用内存,因为所有的线程都从同一个内存池中获取和回收内存。但是,这种方式可能会导致性能下降,因为线程需要等待其他线程释放锁。

5.3 如何处理 unsynchronized_pool_resource 的线程不安全

处理 unsynchronized_pool_resource 的线程不安全的最佳方式取决于应用程序的具体需求。如果性能是最重要的因素,并且可以接受更高的内存使用率,那么每个线程都使用自己的 unsynchronized_pool_resource 实例可能是最好的选择。如果内存效率是最重要的因素,并且可以接受更低的性能,那么使用一个共享的 unsynchronized_pool_resource 实例和外部的同步机制可能是最好的选择。

整体上,unsynchronized_pool_resource 提供了一种高效的内存管理方式,但是在多线程环境中使用时需要注意线程安全问题。通过正确地使用 unsynchronized_pool_resource,你可以根据应用程序的需求来平衡内存效率和性能。

6. synchronized_pool_resource 线程安全

synchronized_pool_resource 是 C++17 中提供的一种内存资源类,和 unsynchronized_pool_resource 类似,都是为了提供高效的内存分配和回收服务。但是,synchronized_pool_resource 的主要区别在于它是线程安全的。

6.1 什么是线程安全

在多线程编程中,如果一个函数、函数库或者类可以在多线程环境下被同时调用,而不产生错误的结果或者未定义行为,那么我们就称之为线程安全的。简单来说,线程安全就是在多线程环境下保证结果的正确性和状态的一致性。

6.2 synchronized_pool_resource 的线程安全

synchronized_pool_resource 是线程安全的,它提供了内部同步机制来确保多个线程可以同时对它进行内存分配和回收操作。也就是说,多个线程可以共享一个 synchronized_pool_resource 实例,而无需担心数据竞争或者未定义行为。

这个特性让 synchronized_pool_resource 成为在多线程环境下处理内存管理的理想选择。

6.3 如何使用 synchronized_pool_resource

使用 synchronized_pool_resource 非常简单,和 unsynchronized_pool_resource 的用法类似。你可以在创建 synchronized_pool_resource 的实例时,传入一个 pool_options 参数来配置内存池,然后使用 allocate()deallocate() 方法来分配和回收内存。

以下是一个例子:

std::pmr::pool_options options;
options.max_blocks_per_chunk = 1024;
options.largest_required_pool_block = 512;

std::pmr::synchronized_pool_resource pool(options);

// 从 pool 中分配内存
void* p = pool.allocate(100);

// 将内存归还给 pool
pool.deallocate(p, 100);

在这个例子中,即使有多个线程同时执行上述代码,synchronized_pool_resource 也能保证每次 allocate()deallocate() 操作的正确性。

synchronized_pool_resource 提供了一种线程安全、高效的内存管理方式。通过使用 synchronized_pool_resource,你可以在多线程环境下进行安全、快速的内存分配和回收,从而提高应用程序的性能。

猜你喜欢

转载自blog.csdn.net/weixin_52665939/article/details/131602376