Использование boost::iostreams::mapped_file_source с std::multimap
У меня достаточно большой объем данных для анализа - каждый файл составляет около 5 гигабайт. Каждый файл имеет следующий формат:
xxxxx yyyyy
И ключ, и значение могут повторяться, но ключи сортируются в порядке возрастания. Я пытаюсь использовать файл с отображенной памятью для этой цели, а затем найти необходимые ключи и работать с ними. Вот что я написал:
if (data_file != "")
{
clock_start = clock();
data_file_mapped.open(data_file);
data_multimap = (std::multimap<double, unsigned int> *)data_file_mapped.data();
if (data_multimap != NULL)
{
std::multimap<double, unsigned int>::iterator it = data_multimap->find(keys_to_find[4]);
if (it != data_multimap->end())
{
std::cout << "Element found.";
for (std::multimap<double, unsigned int>::iterator it = data_multimap->lower_bound(keys_to_find[4]); it != data_multimap->upper_bound(keys_to_find[5]); ++it)
{
std::cout << it->second;
}
std::cout << "\n";
clock_end = clock();
std::cout << "Time taken to read in the file: " << (clock_end - clock_start)/CLOCKS_PER_SEC << "\n";
}
else
std::cerr << "Element not found at all" << "\n";
}
else
std::cerr << "Nope - no data received."<< "\n";
}
По сути, мне нужно найти диапазоны ключей и вытащить эти куски для работы. Я получаю segfault в первый раз, когда я пытаюсь использовать метод на мультикарте. Например, когда find
метод называется. Я попробовал upper_bound
, lower_bound
и другие методы тоже, и все еще получают segfault.
Это то, что gdb
дает мне:
Program received signal SIGSEGV, Segmentation fault.
_M_lower_bound (this=<optimized out>, __k=<optimized out>, __y=<optimized out>, __x=0xa31202030303833) at /usr/include/c++/4.9.2/bits/stl_tree.h:1261
1261 if (!_M_impl._M_key_compare(_S_key(__x), __k))
Может кто-нибудь указать, что я делаю не так? Мне удалось найти только упрощенные примеры для файлов с отображенной памятью - пока ничего подобного. Благодарю.
РЕДАКТИРОВАТЬ: Больше информации:
Файл, который я описал выше, в основном представляет собой текстовый файл с двумя столбцами, который нейронный симулятор дает мне в качестве результата моих симуляций. Это просто так:
$ du -hsc 201501271755.e.ras
4.9G 201501271755.e.ras
4.9G total
$ head 201501271755.e.ras
0.013800 0
0.013800 1
0.013800 10
0.013800 11
0.013800 12
0.013800 13
0.013800 14
0.013800 15
0.013800 16
0.013800 17
Первый столбец - время, второй столбец - нейроны, которые сработали в это время - (это растровый файл с всплеском времени). Фактически, выходные данные - это файл, подобный этому, для каждого ранга MPI, который используется для запуска симуляции. Различные файлы были объединены в этот мастер-файл с помощью sort -g -m
, Более подробная информация о формате файла находится здесь: http://www.fzenke.net/auryn/doku.php?id=manual:ras
Чтобы вычислить частоту срабатывания и другие метрики нейрона, установленного в определенные моменты моделирования, мне нужно - найти время в файле, извлечь фрагмент между [время -1, время] и запустить некоторые метрики и т. Д. этот кусок. Этот файл довольно мал, и я ожидаю, что его размер увеличится немного, так как мои симуляции становятся все более сложными и работают в течение более длительных периодов времени. Вот почему я начал изучать файлы, отображенные в памяти. Я надеюсь, что это несколько разъясняет постановку проблемы. Мне нужно только прочитать выходной файл, чтобы обработать информацию, которую он содержит. Мне не нужно изменять этот файл вообще.
Для обработки данных я снова буду использовать несколько потоков, но, поскольку все мои операции на карте предназначены только для чтения, я не ожидаю, что там возникнут проблемы.
2 ответа
Вы пытаетесь делать вещи, которые вы на самом деле не понимаете:) Нет проблем.
Мультикарты не располагаются последовательно в памяти. (Это контейнеры на основе узлов, но я отвлекся). Фактически, даже если бы они были, шансы были бы невелики, что компоновка соответствовала бы расположению ввода текста.
Есть два основных способа сделать эту работу:
Продолжайте использовать
multimap
но используйте пользовательский распределитель (чтобы все выделения выполнялись в области отображаемой памяти). Это "самый хороший" с точки зрения C++ высокого уровня, но / вам нужно будет перейти на двоичный формат вашего файла.Если вы можете, это то, что я бы предложил. Boost Container + Boost Interprocess имеют все необходимое, чтобы сделать это относительно безболезненно.
Вы пишете пользовательский контейнер "абстракция", который работает непосредственно с отображаемыми данными. Вы могли бы либо
- распознать пару "xxxx yyyy" откуда угодно (конец строки?) или
- построить индекс всей строки начинается в файле.
Используя их, вы можете разработать интегратор (Boost Iterator
iterator_facade
) которые вы можете использовать для реализации операций более высокого уровня (lower_bound
,upper_bound
а такжеequal_range
).После того, как они у вас есть, у вас есть все возможности для запроса этой карты памяти как базы данных значений ключей только для чтения.
К сожалению, этот вид представления памяти был бы крайне плох для производительности, если вы также хотите поддерживать операции с мутированием (
insert
,remove
).
Если у вас есть пример файла, я мог бы продемонстрировать любой из описанных подходов.
Обновить
Быстрые образцы:
С boost::interprocess вы можете (очень) просто определить желаемую мультикарту:
namespace shared { namespace bc = boost::container; template <typename T> using allocator = bip::allocator<T, bip::managed_mapped_file::segment_manager>; template <typename K, typename V> using multi_map = bc::flat_multimap< K, V, std::less<K>, allocator<typename bc::flat_multimap<K, V>::value_type> >; }
Заметки:
Я выбрал
flatmap
(flat_multimap
на самом деле) потому что он, вероятно, более эффективен при хранении и намного более сопоставим со вторым подходом (приведенным ниже);Обратите внимание, что этот выбор влияет на стабильность итераторов / ссылок и довольно сильно благоприятствует операциям только для чтения. Если вам нужна стабильность итератора и / или множество операций мутации, используйте обычные
map
(или для очень больших объемовhash_map
) вместо плоских вариаций.Я выбрал
managed_mapped_file
сегмент для этой демонстрации (так что вы получите постоянство). Демонстрация показывает, как 10G редко выделяется заранее, но на диске используется только фактически выделенное пространство. Вы могли бы также использоватьmanaged_shared_memory
,Если у вас есть двоичное постоянство, вы можете вообще отказаться от текстового файла данных.
Я анализирую текстовые данные в
shared::multi_map<double, unsigned>
изmapped_file_source
используя Boost Spirit. Реализация полностью универсальная.Там нет необходимости писать
iterator
классы,start_of_line()
,end_of_line()
,lower_bound()
,upper_bound()
,equal_range()
или любой из них, так как они уже являются стандартными вmulti_map
интерфейс, так что все, что нам нужно, это написатьmain
:
#define NDEBUG #undef DEBUG #include <boost/iostreams/device/mapped_file.hpp> #include <boost/fusion/adapted/std_pair.hpp> #include <boost/container/flat_map.hpp> #include <boost/interprocess/managed_mapped_file.hpp> #include <boost/spirit/include/qi.hpp> #include <iomanip> namespace bip = boost::interprocess; namespace qi = boost::spirit::qi; namespace shared { namespace bc = boost::container; template <typename T> using allocator = bip::allocator<T, bip::managed_mapped_file::segment_manager>; template <typename K, typename V> using multi_map = bc::flat_multimap< K, V, std::less<K>, allocator<typename bc::flat_multimap<K, V>::value_type> >; } #include <iostream> bip::managed_mapped_file msm(bip::open_or_create, "lookup.bin", 10ul<<30); template <typename K, typename V> shared::multi_map<K,V>& get_or_load(const char* fname) { using Map = shared::multi_map<K, V>; Map* lookup = msm.find_or_construct<Map>("lookup")(msm.get_segment_manager()); if (lookup->empty()) { // only read input file if not already loaded boost::iostreams::mapped_file_source input(fname); auto f(input.data()), l(f + input.size()); bool ok = qi::phrase_parse(f, l, (qi::auto_ >> qi::auto_) % qi::eol >> *qi::eol, qi::blank, *lookup); if (!ok || (f!=l)) throw std::runtime_error("Error during parsing at position #" + std::to_string(f - input.data())); } return *lookup; } int main() { // parse text file into shared memory binary representation auto const& lookup = get_or_load<double, unsigned int>("input.txt"); auto const e = lookup.end(); for(auto&& line : lookup) { std::cout << line.first << "\t" << line.second << "\n"; auto er = lookup.equal_range(line.first); if (er.first != e) std::cout << " lower: " << er.first->first << "\t" << er.first->second << "\n"; if (er.second != e) std::cout << " upper: " << er.second->first << "\t" << er.second->second << "\n"; } }
Я реализовал это точно так, как я описал:
- простой контейнер над сырым
const char*
регион нанесен на карту; - с помощью
boost::iterator_facade
сделать итератор, который разбирает текст по разыменованию; - для печати строк ввода я использую
boost::string_ref
- что позволяет избежать динамического выделения для копирования строк. парсинг выполняется с помощью Spirit Qi:
if (!qi::phrase_parse( b, _data.end, qi::auto_ >> qi::auto_ >> qi::eoi, qi::space, _data.key, _data.value))
Ци был выбран для скорости и универсальности: вы можете выбрать
Key
а такжеValue
типы во время создания экземпляра:text_multi_lookup<double, unsigned int> tml(map.data(), map.data() + map.size());
Я реализовал
lower_bound
,upper_bound
а такжеequal_range
функции-члены, использующие преимущества основного смежного хранилища. Хотя "линия"iterator
не произвольный доступ, а двунаправленный, мы все еще можем перейти кmid_point
такого диапазона итератора, потому что мы можем получитьstart_of_line
от любогоconst char*
в основной картированный регион. Это делает бинарный поиск эффективным.
Обратите внимание, что это решение анализирует строки при разыменовании
iterator
, Это может быть неэффективно, если одни и те же строки разыменовываются много раз.Но для нечастых поисков или поисков, которые не типичны для одной и той же области входных данных, это примерно настолько эффективно, насколько это возможно (выполнение только минимально необходимого анализа и
O(log n)
бинарный поиск), все время полностью обходя начальное время загрузки, сопоставляя файл (нет доступа означает, что ничего не нужно загружать).Live On Coliru (включая данные испытаний)
#define NDEBUG #undef DEBUG #include <boost/iostreams/device/mapped_file.hpp> #include <boost/utility/string_ref.hpp> #include <boost/optional.hpp> #include <boost/spirit/include/qi.hpp> #include <thread> #include <iomanip> namespace io = boost::iostreams; namespace qi = boost::spirit::qi; template <typename Key, typename Value> struct text_multi_lookup { text_multi_lookup(char const* begin, char const* end) : _map_begin(begin), _map_end(end) { } private: friend struct iterator; enum : char { nl = '\n' }; using rawit = char const*; rawit _map_begin, _map_end; rawit start_of_line(rawit it) const { while (it > _map_begin) if (*--it == nl) return it+1; assert(it == _map_begin); return it; } rawit end_of_line(rawit it) const { while (it < _map_end) if (*it++ == nl) return it; assert(it == _map_end); return it; } public: struct value_type final { rawit beg, end; Key key; Value value; boost::string_ref str() const { return { beg, size_t(end-beg) }; } }; struct iterator : boost::iterator_facade<iterator, boost::string_ref, boost::bidirectional_traversal_tag, value_type> { iterator(text_multi_lookup const& d, rawit it) : _region(&d), _data { it, nullptr, Key{}, Value{} } { assert(_data.beg == _region->start_of_line(_data.beg)); } private: friend text_multi_lookup; text_multi_lookup const* _region; value_type mutable _data; void ensure_parsed() const { if (!_data.end) { assert(_data.beg == _region->start_of_line(_data.beg)); auto b = _data.beg; _data.end = _region->end_of_line(_data.beg); if (!qi::phrase_parse( b, _data.end, qi::auto_ >> qi::auto_ >> qi::eoi, qi::space, _data.key, _data.value)) { std::cerr << "Problem in: " << std::string(_data.beg, _data.end) << "at: " << std::setw(_data.end-_data.beg) << std::right << std::string(_data.beg,_data.end); assert(false); } } } static iterator mid_point(iterator const& a, iterator const& b) { assert(a._region == b._region); return { *a._region, a._region->start_of_line(a._data.beg + (b._data.beg -a._data.beg)/2) }; } public: value_type const& dereference() const { ensure_parsed(); return _data; } bool equal(iterator const& o) const { return (_region == o._region) && (_data.beg == o._data.beg); } void increment() { _data = { _region->end_of_line(_data.beg), nullptr, Key{}, Value{} }; assert(_data.beg == _region->start_of_line(_data.beg)); } }; using const_iterator = iterator; const_iterator begin() const { return { *this, _map_begin }; } const_iterator end() const { return { *this, _map_end }; } const_iterator cbegin() const { return { *this, _map_begin }; } const_iterator cend() const { return { *this, _map_end }; } template <typename CompatibleKey> const_iterator lower_bound(CompatibleKey const& key) const { auto f(begin()), l(end()); while (f!=l) { auto m = iterator::mid_point(f,l); if (m->key < key) { f = m; ++f; } else { l = m; } } return f; } template <typename CompatibleKey> const_iterator upper_bound(CompatibleKey const& key) const { return upper_bound(key, begin()); } private: template <typename CompatibleKey> const_iterator upper_bound(CompatibleKey const& key, const_iterator f) const { auto l(end()); while (f!=l) { auto m = iterator::mid_point(f,l); if (key < m->key) { l = m; } else { f = m; ++f; } } return f; } public: template <typename CompatibleKey> std::pair<const_iterator, const_iterator> equal_range(CompatibleKey const& key) const { auto lb = lower_bound(key); return { lb, upper_bound(key, lb) }; } }; #include <iostream> int main() { io::mapped_file_source map("input.txt"); text_multi_lookup<double, unsigned int> tml(map.data(), map.data() + map.size()); auto const e = tml.end(); for(auto&& line : tml) { std::cout << line.str(); auto er = tml.equal_range(line.key); if (er.first != e) std::cout << " lower: " << er.first->str(); if (er.second != e) std::cout << " upper: " << er.second->str(); } }
Для любопытных: вот разборка. Обратите внимание, как все алгоритмические вещи встроены прямо в
main
: http://paste.ubuntu.com/9946135/- простой контейнер над сырым
data_multimap = (std::multimap<double, unsigned int> *)data_file_mapped.data();
Насколько я могу судить по документации Boost, вы неправильно поняли эту функцию, что приведение не будет работать, вам нужно заполнить мультикарту символом *, предоставленным data()
Я редактирую, чтобы добавить немного более подробный контент, например, после сопоставления, вы можете сделать
std::getline(data_file_mapped, oneString);
И после этого доставьте содержимое в строку (вы можете использовать поток строк для этой задачи) и заполните свою мультикарту.
Повторите процесс до конца файла.