4

Inside a boost::interprocess::managed_shared_memory, I am trying to create boost::unordered_map inside another boost::unordered_map as value, having key as std::string for both maps. This Map in Map inside a shared memory segment gets accessed by two different processes fetch values from both outer & inner maps.

Below is my implementation & want to know if this is possible/right way or any other better way possible?

boost::interprocess::managed_shared_memory segment(boost::interprocess::open_or_create, "BOOST_SHM", 65536);

    typedef std::string   KeyType;
    typedef std::string   ValueType;
    typedef std::pair<const KeyType, ValueType> MapType;
    typedef boost::interprocess::allocator<MapType, boost::interprocess::managed_shared_memory::segment_manager> ShmemAllocator;
    typedef boost::unordered_map<KeyType, ValueType, boost::hash<KeyType>, std::equal_to<KeyType>, ShmemAllocator> InMap;
    ShmemAllocator alloc_inst(segment.get_segment_manager());
    InMap *inside_map = segment.construct<InMap>("SHM_IN_MAP")(3, boost::hash<KeyType>(), std::equal_to<KeyType>(), alloc_inst);


    typedef std::pair<const KeyType, MapType> MIMType;
    typedef boost::interprocess::allocator<MIMType, boost::interprocess::managed_shared_memory::segment_manager> MIMShmemAllocator;
    typedef boost::unordered_map<KeyType, MapType, boost::hash<KeyType>, std::equal_to<KeyType>, MIMShmemAllocator> OutMap;
    //MIMShmemAllocator alloc_inst(segment.get_segment_manager());   /*Commented due to Error*/
    OutMap *outside_map = segment.construct<OutMap>("SHM_OUT_MAP")(3, boost::hash<KeyType>(), std::equal_to<KeyType>(), alloc_inst);

Other details:

gcc version 4.8.3 20140911 (Red Hat 4.8.3-9) (GCC) on CentOS 7, BOOST_LIB_VERSION "1_58"

sehe
  • 374,641
  • 47
  • 450
  • 633
DragonX
  • 371
  • 2
  • 6
  • 18
  • @sehe Hi, could you elaborate a bit, Some pointers would be really helpful. Also I am not familiar with livecoding.tv/sehe (shows offline every time) – DragonX Nov 25 '15 at 08:53
  • Of course, I will post the result of the work. Shame you cannot see the stream then – sehe Nov 25 '15 at 08:59

1 Answers1

4

Ok.

So there were a few basic errors, and possibly some confusion.

Next, there are some power tricks that make using nested containers with custom (stateful) allocators much more convenient.

Here's the roll-up of all three hints in a working sample that hopefully helps!


  1. Your strings must use shared memory allocators too

    Otherwise the data would be illegal to use in another process. Using the strings would result in Undefined Behaviour.

    At the very least, make your strings use the shared memory allocator:

    namespace Shared {
        using Segment   = bip::managed_shared_memory;
    
        template <typename T>
        using Alloc     = bip::allocator<T, Segment::segment_manager>;
    
        using String    = boost::container::basic_string<char, std::char_traits<char>, Alloc<char> >;
        using KeyType   = String;
        using ValueType = String;
    }
    
  2. The map allocators were overspecified. The actual node-types wrapping the pair<K const, v> elements in a map are implementation defined anyways. So how do maps know how to allocate these nodes?

    They rebind allocators: see rebind in the docs here

    So, you can just pass Alloc<void>. Or the same allocator as for the Shared::String. The map will figure it out:

    typedef boost::unordered_map<KeyType, ValueType, boost::hash<KeyType>, std::equal_to<KeyType>, Alloc<void> > InMap;
    typedef boost::unordered_map<KeyType, InMap,     boost::hash<KeyType>, std::equal_to<KeyType>, Alloc<void> > OutMap;
    
  3. Now for the power tips.

    Passing stateful allocators all the freaking time is annoying. It makes code a mess. Luckily, c++11 (and Boost Containers for c++03) has you covered:

    • scoped_allocator_adaptor<T...>
    • allocator_type
    • uses_allocator<T> trait

    These helpers can make your life a lot easier. They do this by passing the allocator down to element type constructors when applicable. Automatically. Again, implicit conversions from rebound allocator types make things work.

    So, you can actually just construct one outer map with the correct allocator (make it Scoped) and one key, and from there you don't even have to keep specifying allocators.

Here's a full demo:

Live On Coliru

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <boost/unordered_map.hpp>
#include <iostream>

namespace bip = boost::interprocess;

namespace Shared {
    using Segment = bip::managed_shared_memory;

    template <typename T>
    using Alloc   = bip::allocator<T, Segment::segment_manager>;
    using Scoped  = boost::container::scoped_allocator_adaptor<Alloc<char> >;

    using String  = boost::container::basic_string<char, std::char_traits<char>, Scoped>;
    using KeyType = String;

    typedef boost::unordered_map<KeyType, String, boost::hash<KeyType>, std::equal_to<KeyType>, Scoped> InMap;
    typedef boost::unordered_map<KeyType, InMap,  boost::hash<KeyType>, std::equal_to<KeyType>, Scoped> OutMap;
}

int main() {
    srand(time(NULL));

    Shared::Segment segment(bip::open_or_create, "BOOST_SHM", 65536);
    auto* mgr = segment.get_segment_manager();

    Shared::OutMap *p_outside_map = segment.find_or_construct<Shared::OutMap> ("SHM_OUT_MAP") (mgr);
    auto& outside_map = *p_outside_map;

    Shared::String sskey(mgr); // reduce shared allocations as they are costly (in terms of fragmentation/overhead)

    char outer_keys[3], inner_keys[3];
    std::generate_n(outer_keys, 3, [] { return rand()%26+'a'; });
    std::generate_n(inner_keys, 3, [] { return rand()%26+'a'; });

    for (auto key : outer_keys) {
        sskey = key;
        auto& inner = outside_map[sskey];

        for (auto more : inner_keys) {
            inner[sskey + "_" + more] += "value";
        }
    }

    for (auto const& oe : outside_map) {
        for (auto const& ie : oe.second) {
            std::cout << "outside_map[" << oe.first << "][" << ie.first << "] == " << ie.second << "\n";
        }
    }
}

Actually, to make it work on Coliru, we need to use a mapped file instead:

Live On Coliru

Run it a few times:

outside_map[s][s_t] == value
outside_map[s][s_r] == value
outside_map[s][s_c] == value
outside_map[f][f_t] == value
outside_map[f][f_r] == value
outside_map[f][f_c] == value
outside_map[o][o_t] == value
outside_map[o][o_r] == value
outside_map[o][o_c] == value

Second run:

outside_map[a][a_d] == value
outside_map[a][a_c] == value
outside_map[a][a_g] == value
outside_map[r][r_d] == value
outside_map[r][r_c] == value
outside_map[r][r_g] == value
outside_map[g][g_d] == value
outside_map[g][g_c] == value
outside_map[g][g_g] == value
outside_map[s][s_t] == value
outside_map[s][s_r] == value
outside_map[s][s_c] == value
outside_map[f][f_t] == value
outside_map[f][f_r] == value
outside_map[f][f_c] == value
outside_map[o][o_t] == value
outside_map[o][o_r] == value
outside_map[o][o_c] == value

Note how each run successfully appends value to 9 keys in 3 inner maps.

sehe
  • 374,641
  • 47
  • 450
  • 633
  • I had fun creating this sample. In case you want to review the recorded live stream: https://www.livecoding.tv/video/interprocess-nested-maps-scoped-allocators/ (there's some distraction and construction noises, but you know... Stuff happens :)) – sehe Nov 25 '15 at 10:17
  • PS. Note that `sskey + "_" + more` is quite inefficient. It would be nicer to reuse the same `Shared::String` instance all the time. Could you let me know whether this answer was helpful to you? – sehe Nov 25 '15 at 12:08
  • in this some implementations are new to me. I need to go through that & put it into actual project as per the requirement. Then I can be sure about it. For now the sample is working fine :) – DragonX Nov 25 '15 at 13:39
  • Hi, https://www.livecoding.tv/pastebin/xByA can you please tell me whether its the right approach? Also I have few doubts like whether "[]" is overloaded to find in map & how to handle invalid key in both maps? – DragonX Nov 27 '15 at 05:27
  • Can you read your code? I can't. That's not a good sign. **Never** write code like that. The question "whether its the right approach" doesn't make sense. It's c++, not assembly. I'll read it now and try to decipher what it does. Next time, consider asking a new question.\ – sehe Nov 27 '15 at 08:54
  • Here's no less than 8 different approaches that could work for you: **[Live On Coliru](http://coliru.stacked-crooked.com/a/c8b8d0dca2677c4e)**. Note that the guiding principle on _all of these_ was: "How do I want the code to look". And "how can I avoid repeating the same information". Only after that "how can I avoid creating/allocating temporaries". (Also note, there was no question "How can I avoid a mess of confusing variables m1,u3,s17..." because... ***you don't do that ever*** :)) – sehe Nov 27 '15 at 11:12
  • yes I know but, I was just testing it. The actual implementation in source is cleaner than this. , hey if you wont mind is there any way I can reach out to you (other than SO & livecoding.tv)? Thanks for helping out :) – DragonX Nov 27 '15 at 11:49
  • I do mind. There is a reason why I'm on SO. Here the idea is that helping one person is helping others too. Also, there is not a personal dependence (if I'm not in the mood, someone else can help). There's no reason for me to go off-site. – sehe Nov 27 '15 at 13:14
  • (PS. If "The actual implementation in source is cleaner than this" - why did you ask the comment?) – sehe Nov 27 '15 at 13:48
  • wanted to know the logic – DragonX Nov 28 '15 at 03:38
  • Hi, i have a doubt in `Shared::String sskey(mgr);`, if sskey is declared & initialized using `mgr` in a function `foo()`, then when `foo()` goes out of scope/unwinds, will the sskey destructed properly on the shared memory during each successive calls to `foo()`? So sskey resides in shared memory or in `foo()`'s stack? – DragonX Dec 04 '15 at 07:25
  • 1
    @DragonX Both, which is exactly how `std::string` normally works. The string object lives where /you/ put it (in this case, a stack local). The allocator is used to allocate the contiguous memory are for storing the string's value (none of this is special for shared memory, you just might not have thought about this before). The destructor does the right thing. – sehe Dec 04 '15 at 08:27