[bind10-dev] use of boost offset_ptr for in-memory data structure
JINMEI Tatuya / 神明達哉
jinmei at isc.org
Mon Jun 11 08:47:08 UTC 2012
Although this is not a topic for the soonest development goals,
assuming we'll work on the "in-memory scalability" feature not far
from now...
I played with the boost offset_ptr
http://www.boost.org/doc/libs/1_35_0/doc/html/interprocess/offset_ptr.html
and found that it can be used in a pretty intuitive way (i.e., mostly
just like bare pointers). So, even if our initial version doesn't use
sexy shared-memory type of trick, I propose we use it in the internal
representation from the beginning.
Here's (a snippet of) some quick example. A simple single-linked list
entry can be defined as follows:
struct Entry;
typedef offset_ptr<Entry> EntryPtr;
struct Entry {
EntryPtr next;
const int value;
};
and we can traverse a list of these entries like this:
void
traverseList(EntryPtr head) {
for (EntryPtr ep = head; ep; ep = ep->next) {
cout << ep->value << endl;
}
}
In fact, this is compatible with an implementation that uses the bare
pointers. Memory allocation and deallocation will be less
straightforward than this, but I think it's acceptable with some
proper wrappers. Then, when we are ready, it can be more easily
switched to a shared-memory or memory-mapped based implementation.
One possibly controversial point is that the issues discussed here:
http://stackoverflow.com/questions/3196395/is-boostinterprocess-ready-for-prime-time?answertab=votes#tab-top
personally we can live with it if we carefully add assertion checks in
the code.
I've copied fully workable example code below. It supports three
different modes:
- If compiled with USE_SHMEM, it uses shared memory and offset_ptr to
deal with relative pointer values.
- If compiled with USE_MMAP, it uses shared memory and offset_ptr to
deal with relative pointer values (and the resulting mapped file is
"loadable")
- If compiled with USE_BAREPTR, it doesn't use offset_ptr at all.
This shows we can initially use bare pointers while keeping the code
compatible with offset_ptr versions.
- By default, it uses offset_ptr but no shared-memory type of trick is
used. This is another possibility for the initial implementation.
---
JINMEI, Tatuya
#include <boost/interprocess/offset_ptr.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
using namespace boost::interprocess;
struct Entry;
#ifdef USE_BAREPTR
typedef Entry* EntryPtr;
#else
typedef offset_ptr<Entry> EntryPtr;
#endif
struct Entry {
EntryPtr next;
const int value;
Entry(int val) : next(NULL), value(val) {}
template <typename SegmentType>
static Entry* allocate(SegmentType& segment, int i);
template <typename SegmentType>
static void deallocate(SegmentType& segment, EntryPtr ep);
};
#ifndef USE_BAREPTR
template <typename SegmentType>
void
Entry::deallocate(SegmentType& segment, EntryPtr ep) {
segment.destroy_ptr(ep.get());
}
#endif
template <typename SegmentType>
void
addListEntry(SegmentType& segment, EntryPtr* list_head, int i) {
EntryPtr* prev = list_head;
while (*prev) {
prev = &(*prev)->next;
}
*prev = Entry::allocate(segment, i);
}
template <typename SegmentType>
void
removeListEntry(SegmentType& segment, EntryPtr* list_head, int i) {
EntryPtr* prev = list_head;
for (EntryPtr ep = *list_head; ep; ep = ep->next) {
if (ep->value == i) {
*prev = ep->next;
Entry::deallocate(segment, ep);
return;
}
prev = &(*prev)->next;
}
}
// 'head' can be of '(const) EntryPtr&', too.
void
traverseList(EntryPtr head) {
for (EntryPtr ep = head; ep; ep = ep->next) {
cout << ep->value << endl;
}
}
#ifdef USE_SHMEM
template <>
Entry*
Entry::allocate<managed_shared_memory>(managed_shared_memory& segment, int i)
{
return (segment.construct<Entry>(anonymous_instance)(i));
}
int
main(int argc, char* argv[]) {
if (argc == 1) {
// parent process: create a shared memory region and build
// a linked list there.
shared_memory_object::remove("TestSharedMemory");
managed_shared_memory segment(open_or_create, "TestSharedMemory",
4096);
EntryPtr* list_head =
segment.construct<EntryPtr>("head")(static_cast<Entry*>(NULL));
for (int i = 0; i < 10; ++i) {
addListEntry(segment, list_head, i);
}
system((string(argv[0]) + " child").c_str());
} else {
// child process: open the created shared memory and iterate over
// the linked list stored there.
managed_shared_memory segment(open_only, "TestSharedMemory");
EntryPtr* list_head = segment.find<EntryPtr>("head").first;
assert(list_head != NULL);
traverseList(*list_head);
for (int i = 0; i < 10; ++i) {
removeListEntry(segment, list_head, i);
}
segment.destroy_ptr(list_head);
assert(segment.all_memory_deallocated());
}
}
#elif defined USE_MMAP
template <>
Entry*
Entry::allocate<managed_mapped_file>(managed_mapped_file& segment, int i) {
return (segment.construct<Entry>(anonymous_instance)(i));
}
int
main(int argc, char**) {
if (true) {
file_mapping::remove("TestMappedFile");
managed_mapped_file segment(open_or_create, "TestMappedFile", 4096);
EntryPtr* list_head =
segment.construct<EntryPtr>("head")(static_cast<Entry*>(NULL));
for (int i = 0; i < 10; ++i) {
addListEntry(segment, list_head, i);
}
segment.flush();
}
// (re)map the file to memory, and examine it.
managed_mapped_file segment = managed_mapped_file(open_only,
"TestMappedFile");
EntryPtr* list_head = segment.find<EntryPtr>("head").first;
assert(list_head != NULL);
traverseList(*list_head);
for (int i = 0; i < 10; ++i) {
removeListEntry(segment, list_head, i);
}
segment.destroy_ptr(list_head);
assert(segment.all_memory_deallocated());
}
#else // normal memory mode
class NormalMemorySegment {
public:
NormalMemorySegment() : allocated_(0) {}
template <typename PointedType>
void destroy_ptr(PointedType* ptr) {
assert(allocated_ >= sizeof(PointedType));
delete ptr;
allocated_ -= sizeof(PointedType);
}
bool all_memory_deallocated() const { return (allocated_ == 0); }
void addAllocated(size_t allocated_size) {
allocated_ += allocated_size;
}
private:
size_t allocated_;
};
template <>
Entry*
Entry::allocate<NormalMemorySegment>(NormalMemorySegment& segment, int i) {
Entry* ptr = new Entry(i);
segment.addAllocated(sizeof(Entry));
return (ptr);
}
#ifdef USE_BAREPTR
template <>
void
Entry::deallocate<NormalMemorySegment>(NormalMemorySegment& segment,
EntryPtr ep)
{
segment.destroy_ptr(ep);
}
#endif
int
main() {
NormalMemorySegment segment;
EntryPtr* list_head = new EntryPtr(NULL);
for (int i = 0; i < 10; ++i) {
addListEntry(segment, list_head, i);
}
traverseList(*list_head);
for (int i = 0; i < 10; ++i) {
removeListEntry(segment, list_head, i);
}
delete list_head;
assert(segment.all_memory_deallocated());
}
#endif
More information about the bind10-dev
mailing list