Shared Memory Allocate > 2GB (need to link to 32 bit DLL used by VB6)

Viewed 140

I'm using shared memory from boost library in C++, i'm trying to allocate an unordered_map to share with other process. The code of server is the below:

MapCreator.h

//#pragma once
#pragma warning( disable :4494 )
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/functional/hash.hpp>
#include <boost/unordered_map.hpp>
#include <iostream>

struct dataClass {
    double Somma;
    int Contatore;
};

namespace bip = boost::interprocess;
namespace bc = boost::container;
#ifdef COLIRU
using Segment = bip::managed_mapped_file;
#else
using Segment = bip::managed_shared_memory;
#endif
using Mgr = Segment::segment_manager;

template <typename T> using Alloc = bip::allocator<T, Mgr>;
using MyString = bc::basic_string<char, std::char_traits<char>, Alloc<char>>;
using KeyType = MyString;
using MappedType = dataClass;
using ValueType = std::pair<KeyType const, MappedType>;
using MySHMMap = boost::unordered_map<KeyType, MappedType, boost::hash<MyString>,
    std::equal_to<MyString>, Alloc<ValueType>>;

class MapCreator {
public:
    static constexpr int sizeDeclared = 1324*1024*1024; //< Here the problem, if i set 2000*1024*1024, the client application throw error
    MapCreator(const char* Nome) // : nameMemory(Nome)
    {
        
        nameMemory = Nome;
        remove();
        segment = Segment{ bip::create_only, nameMemory, sizeDeclared };
        
        mappa = segment.find_or_construct<MySHMMap>("MySHMMapName")(segment.get_segment_manager());
    }

    dataClass getValue(std::string key) const {
        return mappa->at(MyString(key.c_str(), segment.get_segment_manager()));
    }
    void insertValue(std::string key,dataClass value) {
        mappa->emplace(MyString(key.c_str(), segment.get_segment_manager()),
            value);
    }
    double getFreeMemory() {
        return ((double)segment.get_free_memory() / 1024 / 1024 / 1024);
    }
    long getSize() {
        return mappa->size();
    }
    void remove() {
        bip::shared_memory_object::remove(nameMemory);
    }
    double getTotalSize() {
        return (double)sizeDeclared/1024/1024/1024;
    }
    double getTotalMemory() {
        return (double)segment.get_size() / 1024 / 1024 / 1024;
    }
private:
    

    // note: declaration order defines initialization order!
    const char* nameMemory = "SharedMemoryName";

    Segment segment;//{ bip::open_or_create, nameMemory, sizeDeclared };
    MySHMMap* mappa = nullptr;
};

Meanwhile the main code is this:

#include "MapCreator.h"
int main(){
  MapCreator mappaClass("thread1");
  mappaClass.insertValue("a", dataClass{ 3.12,2123 });
}

The code of Reader is the below:

ReaderFromMemory.h

//#pragma once
#pragma warning( disable :4494 )
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/functional/hash.hpp>
#include <boost/unordered_map.hpp>
#include <iostream>

struct dataClass {
    double Somma;
    int Contatore;
};

namespace bip = boost::interprocess;
namespace bc = boost::container;
#ifdef COLIRU
using Segment = bip::managed_mapped_file;
#else
using Segment = bip::managed_shared_memory;
#endif
using Mgr = Segment::segment_manager;

template <typename T> using Alloc = bip::allocator<T, Mgr>;
using MyString = bc::basic_string<char, std::char_traits<char>, Alloc<char>>;
using KeyType = MyString;
using MappedType = dataClass;
using ValueType = std::pair<KeyType const, MappedType>;
using MySHMMap = boost::unordered_map<KeyType, MappedType, boost::hash<MyString>,
    std::equal_to<MyString>, Alloc<ValueType>>;

class Reader {
public:
    Reader() : mappa(segment.find_or_construct<MySHMMap>("MySHMMapName")(
        segment.get_segment_manager()))

    {


    }

    dataClass getValue(const char* key) const {
        return mappa->at(MyString(key, segment.get_segment_manager())); // < Here is the error while reading
    }

private:

    // note: declaration order defines initialization order!
    static constexpr char const* nameMemory = "thread1";

    Segment segment{ bip::open_only, nameMemory };;
    MySHMMap* mappa = nullptr;
};

This is the main:

#include "ReaderFromMemory.h"
int main(){
  Reader reader;
  auto testValue = reader.getValue("a");
}

So the problem is allocate and read more than 2GB.

I already tried to use /LARGEADDRESSAWARE flag but for the Reader doesn't work I'm using:

Visual Studio 2022 for compile in x86 mode because the reader is a DLL that be used by VB6

boost library version 1.78.0

1 Answers

Since this is supposed to work in a 32 bit environment, you could place a layer on top of the direct access to the one map you have now. Here's an outline for that idea:

  1. Divide the one big map into as many smaller needed to get the size needed for each map well below what can be addressed in a 32 bit system. Say < 1 GB as an example.
  2. Create a 64 bit hash function for your key.
  3. Use the top 32 bits of the hash value to select the correct smaller map. If you've split the big map up into 16 smaller maps, you only need a nibble (4 bits) from that 32 bit value to do the selection.
  4. Use the bottom 32 bits of the hash value in the actual map selected.
uint64_t hasher64(const KeyType& kt) {
    //...
}
namespace std {
    struct hash<KeyType> {
        size_t operator()(const KeyType& kt) const {
            return hasher64(kt) & 0xFFFFFFFF;
        }
    };
}

MySHMMap& getmap(const KeyType& kt) {
    static MySHMMap maps[16];
    return maps[(hasher64(kt) >> 32) & 0xF];
}

// ...

getmap(my_key)[my_key] = "foo";

The above hashes the key twice, but would provide a workaround to the limits you are facing.

You could also have a separate, much cheaper, function for selecting the smaller map. In case your Key is a std::string, the selection could be made by looking at the first character in the string.

using KeyType = std::string;

MySHMMap& getmap(const KeyType& kt) {
    static MySHMMap maps[16];
    if(kt.empty()) return maps[0];
    return maps[kt[0] & 0xF];
}

An implementation could start like this (but is in no way complete). I've used standard types and containers to not clutter it with specifics, but the idea should work.

#include <unordered_map>
#include <iostream>

template<class KeyType, class MappedType, size_t MapCo,
         class MapSelector = std::hash<KeyType>>
class MapOverlay {
public:
    using MySHMMap = std::unordered_map<KeyType, MappedType>;
    using value_type = typename MySHMMap::value_type;

    struct iterator { // an iterator to iterate seamlessly over the multiple maps
        iterator(MapOverlay* mo, MySHMMap& m, typename MySHMMap::iterator c) :
            mo(mo), map(&m), it(c) 
        {
            find_non_empty(); // find the first map with values
        }

        value_type& operator*() { return *it; }
        value_type* operator->() { return &*it; }

        iterator& operator++() {
            if(it == map->end()) {
                ++map;
                it = map->begin();
            } else ++it;
            find_non_empty();
            return *this;
        }

        bool operator==(const iterator& rhs) const {
            return map == rhs.map && it == rhs.it; 
        }
        bool operator!=(const iterator& rhs) const { return !(*this == rhs); }

    private:
        void find_non_empty() { // find the next map with values
            while(it == map->end() && map != &mo->maps[MapCo-1]) {
                ++map;
                it = map->begin();
            }
        }

        MapOverlay* mo;
        MySHMMap* map;
        typename MySHMMap::iterator it;
    }; // iterator end

    // some member functions making it feel like a normal unordered_map
    MappedType& operator[](const KeyType& kt) {
        return map_lookup(kt)[kt];
    }

    iterator find(const KeyType& kt) {
        auto& m = map_lookup(kt);
        auto it = m.find(kt);
        if(it == m.end()) return end();
        return {this, m, it};
    }

    iterator begin() { return {this, maps[0], maps[0].begin()}; }
    iterator end() { return {this, maps[MapCo-1], maps[MapCo-1].end()}; }

private:
    MySHMMap& map_lookup(const KeyType& kt) {
        // call the map selector for `kt` and use `% MapCo` to get it in range
        return maps[ms(kt) % MapCo];  // ms is an instance of MapSelector
    }

    MySHMMap maps[MapCo];
    MapSelector ms;
};

The default for the above MapOverlay will be to use the MapSelector std::hash<KeyType> on each key to select the map. That is probably a lot more expensive than what you need so you can provide something much quicker as an alternative. Using std::hash<KeyType> is also a bit risky if you build this with different implementations. The MapSelector algorithm must be the same in all programs sharing the maps.

// a simple class to do the underlying map selection cheaper than
// std::hash<std::string> will:
struct getmap {
    size_t operator()(const std::string& kt) const {
        // a naive, quick and simple map selection:
        if(kt.empty()) return 0;
        return kt[0];
    }
};

int main() {
    MapOverlay<std::string, std::string, 16, getmap> mo; // 16 small maps

    mo["apa"] = "bepa"; // this will be stored in one map
    mo["foo"] = "bar";  // and this in another

    for(auto&[k, v] : mo) std::cout << k << " = " << v << '\n';

    auto it = mo.find("foo");
    if(it != mo.end()) std::cout << it->first << " = " << it->second << '\n';
}

Demo

Related