为什么在 boost 进程间共享内存中分配的对象占用的内存比需要的多?
Why does an object allocated in boost interprocess shared memory take up more memory than required?
对于以下使用 Boost 进程间共享内存的程序,
#include <iostream>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/list.hpp>
#include <iostream>
#define SHARED_MEMORY_NAME "SO12439099-MySharedMemory"
#define DATAOUTPUT "OutputFromObject"
#define INITIAL_MEM 650000
#define STATE_MATRIX_SIZE 4
using namespace std;
namespace bip = boost::interprocess;
class SharedObject
{
public:
unsigned int tNumber;
bool pRcvdFlag;
bool sRcvdFlag;
unsigned long lTimeStamp;
};
typedef bip::allocator<SharedObject, bip::managed_shared_memory::segment_manager> ShmemAllocator;
typedef bip::list<SharedObject, ShmemAllocator> SharedMemData;
int main()
{
bip::managed_shared_memory* seg;
SharedMemData *sharedMemOutputList;
bip::shared_memory_object::remove(DATAOUTPUT);
seg = new bip::managed_shared_memory(bip::create_only, DATAOUTPUT, INITIAL_MEM);
const ShmemAllocator alloc_inst(seg->get_segment_manager());
sharedMemOutputList = seg->construct<SharedMemData>("TrackOutput")(alloc_inst);
std::size_t beforeAllocation = seg->get_free_memory();
std::cout<<"\nBefore allocation = "<< beforeAllocation <<"\n";
SharedObject temp;
sharedMemOutputList->push_back(temp);
std::size_t afterAllocation = seg->get_free_memory();
std::cout<<"After allocation = "<< afterAllocation <<"\n";
std::cout<<"Difference = "<< beforeAllocation - afterAllocation <<"\n";
std::cout<<"Size of SharedObject = "<< sizeof(SharedObject) <<"\n";
std::cout<<"Size of SharedObject's temp instance = "<< sizeof(temp) <<"\n";
seg->destroy<SharedMemData>("TrackOutput");
delete seg;
}//main
输出为:
Before allocation = 649680
After allocation = 649632
Difference = 48
Size of SharedObject = 16
Size of SharedObject's temp instance = 16
如果SharedObject
和它的实例的大小是16字节,那么分配的差异怎么会是48呢?即使 padding 已经自动完成,占 3 倍的大小仍然太多(对于较大的结构,它达到 1.33 倍的大小)。
因此,我无法可靠地分配和动态增长共享内存。如果 SharedObject
包含一个动态增长的列表,那可能会进一步增加 space 分配的不确定性。
如何安全地处理这些情况?
ps:要 运行 程序,您必须 link pthread
库以及 librt.so
。
更新:
这是我为多个 运行 设置 tabulated 值时得到的内存使用模式(memory increase
列基本上是 memory used
列的当前行减去上一行的memory used column
):
╔═════════════╦════════════════╦═════════════════╗
║ memory used ║ structure size ║ memory increase ║
╠═════════════╬════════════════╬═════════════════╣
║ 48 ║ 1 ║ ║
║ 48 ║ 4 ║ 0 ║
║ 48 ║ 8 ║ 0 ║
║ 48 ║ 16 ║ 0 ║
║ 64 ║ 32 ║ 16 ║
║ 64 ║ 40 ║ 0 ║
║ 80 ║ 48 ║ 16 ║
║ 96 ║ 64 ║ 32 ║
║ 160 ║ 128 ║ 64 ║
║ 288 ║ 256 ║ 128 ║
║ 416 ║ 384 ║ 128 ║
║ 544 ║ 512 ║ 128 ║
║ 800 ║ 768 ║ 256 ║
║ 1056 ║ 1024 ║ 256 ║
╚═════════════╩════════════════╩═════════════════╝
重要:以上table仅适用于共享内存list
。对于 vector
,(使用的内存,结构大小)值为 = (48, 1), (48, 8), (48, 16), (48, 32), (80, 64), (80 , 72), (112, 96), (128, 120), (176, 168), (272, 264), (544, 528).
所以其他容器需要不同的内存计算公式。
在此处查看分析和比较:Bad alloc is thrown
长话短说:基于节点的容器在这里不是很好。
考虑使用池分配器 - 是的,以这种方式堆叠分配器有点笨拙,但它确实消除了节点分配的大量开销。
您还可以考虑使用顺序存储,然后使用 Boost Intrusive List 容器 "on top of that"。 Boost Intrusive 与 interprocess::offset_ptr
一起使用,因此您可以将其与托管内存段一起使用。
请记住,任何通用分配机制都有一个有效负载,用于存储有关如何释放该内存、如何将该缓冲区与相邻缓冲区合并等信息。这发生在您的系统 malloc(通常为 8-每个分配 16 个额外字节加上额外对齐)。共享内存中的内存分配器有4-8字节的开销(32位系统,64位系统8-16)
那么库在调用"destroy_ptr(ptr)"时需要存储对象的个数,以便调用析构函数(可以分配数组,所以需要知道要调用多少个析构函数)。并且您已经进行了命名分配,因此库需要将该字符串存储在共享内存和一些元数据中以找到它(指向该字符串的指针,也许这是一个 "named allocation" 而不是 "anonymous" 或“实例分配”。
所以 16 字节数据 + 内存分配器中的 8 字节 + 存储指针的 8 字节 + 名称的元数据 + 字符串中的 12 字节 "TrackOutput" (包括 null-end)加上对齐到 8 字节,你得到 48 个字节。
每次分配的开销几乎不变。因此,常数因子 1.33 仅适用于小额分配。如果你分配一个字节,你会得到更糟糕的因素,就像你从堆中分配一个字符一样。
如果没有可用内存来存储新对象,库会抛出异常,您可以捕获它并尝试创建新的托管共享内存。请注意,内存会因分配和重新分配而变得碎片化,因此即使共享内存中有空闲字节,托管共享内存也无法满足您的请求,因为没有足够大的连续内存来满足它。共享内存无法自动扩展,因此碎片问题比堆内存大得多。
您不能动态增加共享内存,因为其他进程可能会连接到它并且会在尝试访问未映射的页面时崩溃。唯一的选择是预先分配一个足以添加常量填充因子的共享内存,或者分配一个新的托管共享内存并通知所有其他readers/writers(可能使用原始共享内存中的预分配结构)新元素将进入新的托管共享内存。
对于以下使用 Boost 进程间共享内存的程序,
#include <iostream>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/list.hpp>
#include <iostream>
#define SHARED_MEMORY_NAME "SO12439099-MySharedMemory"
#define DATAOUTPUT "OutputFromObject"
#define INITIAL_MEM 650000
#define STATE_MATRIX_SIZE 4
using namespace std;
namespace bip = boost::interprocess;
class SharedObject
{
public:
unsigned int tNumber;
bool pRcvdFlag;
bool sRcvdFlag;
unsigned long lTimeStamp;
};
typedef bip::allocator<SharedObject, bip::managed_shared_memory::segment_manager> ShmemAllocator;
typedef bip::list<SharedObject, ShmemAllocator> SharedMemData;
int main()
{
bip::managed_shared_memory* seg;
SharedMemData *sharedMemOutputList;
bip::shared_memory_object::remove(DATAOUTPUT);
seg = new bip::managed_shared_memory(bip::create_only, DATAOUTPUT, INITIAL_MEM);
const ShmemAllocator alloc_inst(seg->get_segment_manager());
sharedMemOutputList = seg->construct<SharedMemData>("TrackOutput")(alloc_inst);
std::size_t beforeAllocation = seg->get_free_memory();
std::cout<<"\nBefore allocation = "<< beforeAllocation <<"\n";
SharedObject temp;
sharedMemOutputList->push_back(temp);
std::size_t afterAllocation = seg->get_free_memory();
std::cout<<"After allocation = "<< afterAllocation <<"\n";
std::cout<<"Difference = "<< beforeAllocation - afterAllocation <<"\n";
std::cout<<"Size of SharedObject = "<< sizeof(SharedObject) <<"\n";
std::cout<<"Size of SharedObject's temp instance = "<< sizeof(temp) <<"\n";
seg->destroy<SharedMemData>("TrackOutput");
delete seg;
}//main
输出为:
Before allocation = 649680
After allocation = 649632
Difference = 48
Size of SharedObject = 16
Size of SharedObject's temp instance = 16
如果SharedObject
和它的实例的大小是16字节,那么分配的差异怎么会是48呢?即使 padding 已经自动完成,占 3 倍的大小仍然太多(对于较大的结构,它达到 1.33 倍的大小)。
因此,我无法可靠地分配和动态增长共享内存。如果 SharedObject
包含一个动态增长的列表,那可能会进一步增加 space 分配的不确定性。
如何安全地处理这些情况?
ps:要 运行 程序,您必须 link pthread
库以及 librt.so
。
更新:
这是我为多个 运行 设置 tabulated 值时得到的内存使用模式(memory increase
列基本上是 memory used
列的当前行减去上一行的memory used column
):
╔═════════════╦════════════════╦═════════════════╗
║ memory used ║ structure size ║ memory increase ║
╠═════════════╬════════════════╬═════════════════╣
║ 48 ║ 1 ║ ║
║ 48 ║ 4 ║ 0 ║
║ 48 ║ 8 ║ 0 ║
║ 48 ║ 16 ║ 0 ║
║ 64 ║ 32 ║ 16 ║
║ 64 ║ 40 ║ 0 ║
║ 80 ║ 48 ║ 16 ║
║ 96 ║ 64 ║ 32 ║
║ 160 ║ 128 ║ 64 ║
║ 288 ║ 256 ║ 128 ║
║ 416 ║ 384 ║ 128 ║
║ 544 ║ 512 ║ 128 ║
║ 800 ║ 768 ║ 256 ║
║ 1056 ║ 1024 ║ 256 ║
╚═════════════╩════════════════╩═════════════════╝
重要:以上table仅适用于共享内存list
。对于 vector
,(使用的内存,结构大小)值为 = (48, 1), (48, 8), (48, 16), (48, 32), (80, 64), (80 , 72), (112, 96), (128, 120), (176, 168), (272, 264), (544, 528).
所以其他容器需要不同的内存计算公式。
在此处查看分析和比较:Bad alloc is thrown
长话短说:基于节点的容器在这里不是很好。
考虑使用池分配器 - 是的,以这种方式堆叠分配器有点笨拙,但它确实消除了节点分配的大量开销。
您还可以考虑使用顺序存储,然后使用 Boost Intrusive List 容器 "on top of that"。 Boost Intrusive 与 interprocess::offset_ptr
一起使用,因此您可以将其与托管内存段一起使用。
请记住,任何通用分配机制都有一个有效负载,用于存储有关如何释放该内存、如何将该缓冲区与相邻缓冲区合并等信息。这发生在您的系统 malloc(通常为 8-每个分配 16 个额外字节加上额外对齐)。共享内存中的内存分配器有4-8字节的开销(32位系统,64位系统8-16)
那么库在调用"destroy_ptr(ptr)"时需要存储对象的个数,以便调用析构函数(可以分配数组,所以需要知道要调用多少个析构函数)。并且您已经进行了命名分配,因此库需要将该字符串存储在共享内存和一些元数据中以找到它(指向该字符串的指针,也许这是一个 "named allocation" 而不是 "anonymous" 或“实例分配”。
所以 16 字节数据 + 内存分配器中的 8 字节 + 存储指针的 8 字节 + 名称的元数据 + 字符串中的 12 字节 "TrackOutput" (包括 null-end)加上对齐到 8 字节,你得到 48 个字节。
每次分配的开销几乎不变。因此,常数因子 1.33 仅适用于小额分配。如果你分配一个字节,你会得到更糟糕的因素,就像你从堆中分配一个字符一样。
如果没有可用内存来存储新对象,库会抛出异常,您可以捕获它并尝试创建新的托管共享内存。请注意,内存会因分配和重新分配而变得碎片化,因此即使共享内存中有空闲字节,托管共享内存也无法满足您的请求,因为没有足够大的连续内存来满足它。共享内存无法自动扩展,因此碎片问题比堆内存大得多。
您不能动态增加共享内存,因为其他进程可能会连接到它并且会在尝试访问未映射的页面时崩溃。唯一的选择是预先分配一个足以添加常量填充因子的共享内存,或者分配一个新的托管共享内存并通知所有其他readers/writers(可能使用原始共享内存中的预分配结构)新元素将进入新的托管共享内存。