为什么对堆栈中的内存使用自定义动态内存分配?
Why use custom dynamic memory allocation over memory from stack?
上下文:我正在做一个项目,客户需要我们使用自定义动态内存分配而不是从堆栈分配对象。请注意,相关对象的大小在编译期间已知,甚至不需要动态分配。这让我想知道,
在哪些情况下,对象的自定义动态内存分配比从堆栈分配对象更好? (在编译期间知道大小)
一个例子。如果 Dog
是 class,那么他们希望我们做
而不是仅仅声明 Dog puppy;
Dog* puppy = nullptr;
custom_alloc(puppy);
new(puppy) Dog(); // the constructor
// do stuff
puppy->~Dog(); // the destructor
custom_free(puppy)
真正的custom_alloc
功能我们是不知道的。要使程序 运行,给定的 custom_alloc
函数将是 malloc
的包装器。 custom_free
将是 free
的包装器
我不喜欢这种方法,想知道这种方法什么时候真正有用,或者他们这样做真正想解决什么问题。
可能原因:
筹码量有限;虽然典型的线程库为每个线程的堆栈分配 1-10 MB,但对于预计同时启动数百或数千个线程的应用程序(例如高流量网络服务器;Microsoft IIS 过去使用256 KB 限制,对于 64 位设置仅将其提高到 512 KB)。
您可能希望在函数 returned 之后保留一个对象(不使用全局变量)。虽然 NRVO and/or 移动语义确实意味着按值 return 对象通常相对便宜,但当 NRVO 不适用时,复制单个指针比其他任何东西都便宜。
Auditing/tracing:他们可能希望使用针对特定类型的自定义函数来跟踪内存分配模式
持久存储:分配器可能由内存映射文件支持;对于结构化数据,该文件可以作为长期存储的两倍
性能:Custom allocators (e.g. Intel's TBB) have been known to dramatically reduce runtime in certain circumstances。这更像是使用自定义分配器而不是默认分配器的理由;自定义分配器通常不会击败堆栈存储(除了 really 小众情况,在这些情况下,可以通过从堆栈中删除大对象并将它们放入自己的专用存储来改善内存局部性)。
(可能是个糟糕的主意)避免异常处理清理开销。如果您的 类 是 RAII,则必须生成代码以在出现异常时沿着各种代码路径清理它们。原始指针不会生成任何此类代码。当然,如果你自己不采取措施对异常进行清理,这意味着内存泄漏,但在极少数情况下(例如,当你希望程序完全退出时,你希望 OS 处理内存清理)这可能会提供一个小的 "benefit".
以上的组合:他们可能希望能够通过链接不同的运行时库来在跟踪分配器和性能分配器之间进行交换以提供 custom_alloc
综上所述,他们这样做的方法非常糟糕;需要手动放置 new
并且析构函数调用令人不快(std::unique_ptr
/std::shared_ptr
可以通过提供为您完成这项工作的自定义删除仿函数来提供一些帮助,但即便如此也很丑陋)。通常,如果您需要自定义分配器,您会为 operator new
/operator delete
定义适当的重载。这样,避免堆栈分配(无论出于何种原因)就不会那么令人不快了;您只需将逻辑堆栈分配的变量替换为 std::unique_ptr
s(通过 std::make_unique
创建),您的代码仍然相当简单。
上下文:我正在做一个项目,客户需要我们使用自定义动态内存分配而不是从堆栈分配对象。请注意,相关对象的大小在编译期间已知,甚至不需要动态分配。这让我想知道,
在哪些情况下,对象的自定义动态内存分配比从堆栈分配对象更好? (在编译期间知道大小)
一个例子。如果 Dog
是 class,那么他们希望我们做
Dog puppy;
Dog* puppy = nullptr;
custom_alloc(puppy);
new(puppy) Dog(); // the constructor
// do stuff
puppy->~Dog(); // the destructor
custom_free(puppy)
真正的custom_alloc
功能我们是不知道的。要使程序 运行,给定的 custom_alloc
函数将是 malloc
的包装器。 custom_free
将是 free
我不喜欢这种方法,想知道这种方法什么时候真正有用,或者他们这样做真正想解决什么问题。
可能原因:
筹码量有限;虽然典型的线程库为每个线程的堆栈分配 1-10 MB,但对于预计同时启动数百或数千个线程的应用程序(例如高流量网络服务器;Microsoft IIS 过去使用256 KB 限制,对于 64 位设置仅将其提高到 512 KB)。
您可能希望在函数 returned 之后保留一个对象(不使用全局变量)。虽然 NRVO and/or 移动语义确实意味着按值 return 对象通常相对便宜,但当 NRVO 不适用时,复制单个指针比其他任何东西都便宜。
Auditing/tracing:他们可能希望使用针对特定类型的自定义函数来跟踪内存分配模式
持久存储:分配器可能由内存映射文件支持;对于结构化数据,该文件可以作为长期存储的两倍
性能:Custom allocators (e.g. Intel's TBB) have been known to dramatically reduce runtime in certain circumstances。这更像是使用自定义分配器而不是默认分配器的理由;自定义分配器通常不会击败堆栈存储(除了 really 小众情况,在这些情况下,可以通过从堆栈中删除大对象并将它们放入自己的专用存储来改善内存局部性)。
(可能是个糟糕的主意)避免异常处理清理开销。如果您的 类 是 RAII,则必须生成代码以在出现异常时沿着各种代码路径清理它们。原始指针不会生成任何此类代码。当然,如果你自己不采取措施对异常进行清理,这意味着内存泄漏,但在极少数情况下(例如,当你希望程序完全退出时,你希望 OS 处理内存清理)这可能会提供一个小的 "benefit".
以上的组合:他们可能希望能够通过链接不同的运行时库来在跟踪分配器和性能分配器之间进行交换以提供
custom_alloc
综上所述,他们这样做的方法非常糟糕;需要手动放置 new
并且析构函数调用令人不快(std::unique_ptr
/std::shared_ptr
可以通过提供为您完成这项工作的自定义删除仿函数来提供一些帮助,但即便如此也很丑陋)。通常,如果您需要自定义分配器,您会为 operator new
/operator delete
定义适当的重载。这样,避免堆栈分配(无论出于何种原因)就不会那么令人不快了;您只需将逻辑堆栈分配的变量替换为 std::unique_ptr
s(通过 std::make_unique
创建),您的代码仍然相当简单。