vector<bool> serialisieren
-
Bei solchen Mengen würde ich über die Benutzung der STXXL (Standard Template Library for Extra Large Data Sets) nachdenken.
-
Ist es dabei nicht eher problematisch dafür einen so so langen Speicherblock zu bekommen?
-
Es gibt darauf keine Antwort, denn wie std::vector<bool> seine Daten ablegt ist implementationsspezifisch. Wenn du es fuer eine spezielle Implementation machen willst, ist es das einfachste den vector zu analysieren und dann mit fixen offsets und casts zu arbeiten. Oder eine eigene Implementation der Datenstruktur.
-
Jo, selber implementieren und das underlying array als
char-Array in eine Datei schieben. Am Besten mit den C-Funktionen.
-
Erhard Henkes schrieb:
Nein, unbedingt die bittable aus Effizienzgründen im Zugriff.
Wenn du Wert auf Effizenz legst, und mit derart vielen simpel strukturierten Daten hantieren musst, könnte sich eventuell lohnen, das Bitset als Memory Mapped File vorzuhalten. Boost bietet da was Portables (habe damit selbst allerdings noch keine Erfahrungen gesammelt):
http://www.boost.org/doc/libs/1_58_0/libs/iostreams/doc/classes/mapped_file.html
So eine Variante hätte ein paar Vorteile:
- Der User braucht nicht unbedingt 32GiB RAM, lediglich der virtuelle Addressraum sollte gross genug sein (also 64-Bit-Anwendung).
- Laden/Speichern/Caching macht das Betriebssystem.
- "Zero Copy" beim lesen/schreiben.
- Datei ist wie zusammenhängeder Arbeitsspeicher addressierbar (viachar*).Man könnte sogar so verwegen sein, und einen
std::vector<bool>via placementnew(und eigenem Allocator) in der Datei konstruieren. Ich würde allerdings wie Arcoth eher dazu raten, das selbst auf einemchar-Array zu implementieren, damit man eine portable Datenstruktur hat. Einstd::vector<bool>würde dann nämlich auch das "Dateiformat" Implementations-Abhängig machen. D.h. man kann die Datei wahrscheinlich nur "laden", wenn man exakt die selbe Standardbibliothek verwendet (und wenn man Pech hat müssen auch Compiler-Version/Flags, Debug/Release-Versionen der Standard-Lib, etc. übereinstimmen).Finnegan
-
Das geht m.E. nicht, jedes Bit repräsentiert Primzahl ja/nein im Zahlenraum bis z.B. 2*10^11
Versteh ich das richtig ?
Du willst für die info ob ne Zahl von 0 - 2 * 10^11 ne Primzahl ist, in nem vector<bool> oder ähnliches speichern ?ehrlich, wenn du den Speicher dafür nicht am Stück bekommst, bzw zwischenspeichern(platte) musst, geht dir jeglicher vorteil des Arrays verloren.
200 * 10^9 < 2^38
für die bitmask brauchtest also 2^34 byte ^^
16 GB nur für die Bitmask ?Machts dann ned Sinn den lookup gleich auf nen Baum zu legen ?
also in nen std::set<int64_t> und darauf bauen das der B-Baum lookup schneller ist also wie jegliches gerödel was brauchst um diese überdimensinale Bitmask zu verwalten ? Und dabei noch ne menge Speicher sparst (zumindest wenn die Primzahlen speicherst und nicht die "nicht Primzahlen" ^^)Ciao ...
-
Ich habe mit einem std::unordered_set<uint64_t> begonnen (unordered_set hat etwas schnelleren find-Zugriff als set) und via File auf Platte ausgetauscht. Das braucht aber zuviel Speicher im RAM. Man kommt damit bei 32 GB RAM auf meinem PC bis in die Gegend von 5 Milliarden. Mit vector<bool> schafft man immerhin etwas mehr als 200 Mrd. (braucht 25 Mrd. byte).
Hat mich auch verwundert.Vgl.: http://www.michael-holzapfel.de/themen/primzahlen/pz-anzahl.htm
1.000.000.000 50.847.534
10.000.000.000 455.052.511
Mit 8 byte pro Primzahl kommt man da schnell hoch im RAM, da unordered_set einen gigantischen overhead besitzt). Vector<bool> dagegen ist heute wirklich "lean". Dafür fehlt eine eingebaute Serialisierung (man kann die Bits nur als echte ASCII Zahlen in den Stream schieben).
-
Bau dir doch nen Wrapper um nen vector<char>.
-
DocShoe schrieb:
Bau dir doch nen Wrapper um nen vector<char>.
Du meintest
vector<uint8_t>
-
RHBaum schrieb:
Machts dann ned Sinn den lookup gleich auf nen Baum zu legen ?
also in nen std::set<int64_t> und darauf bauen das der B-Baum lookup schneller ist also wie jegliches gerödel was brauchst um diese überdimensinale Bitmask zu verwalten ? Und dabei noch ne menge Speicher sparst (zumindest wenn die Primzahlen speicherst und nicht die "nicht Primzahlen" ^^)In dem Zahlenbereich kann man etwa zwischen 7 und 8 Milliarden Primzahlen erwarten. Selbst wenn man diese maximal speichereffizient in einer Dictionary-Datenstruktur untergebracht bekommt (also 8 Byte je Primzahl), dann benötigt man dafür schon etwa doppelt so viel Speicher wie für das Bitset. Wenn noch zusätzlicher Speicherbedarf für z.B. eine Baumstruktur hinzukommen, sieht es da wohl noch deutlich schlechter aus. Ich denke so ein Bitset ist schon eine ganz gute wahl für das spezifische Problem. Es hat ausserdem den Vorteil, dass man es für Einzelabfragen direkt adressieren kann. Das ist mindestens so gut wie eine Hash-Map, und muss auch nicht "rödeln" wenn das Bitset in einer Datei liegt. Für eine Einzelabfrage kann man direkt in O(1) an die richtige Dateiposition springen - im Gegensatz zum B-Baum, der dafür O(log(n)) Zugriffe braucht. Bei Bereichs-Abfragen (z.B. "gib mir mal alle Primzahlen in [a, b]") könnte allerdings der Baum wieder glänzen.
Für Punkt-Abfragen würde ich aus dem Bauch heraus tippen, dass eine "Memory Mapped" Bitset nur schwer zu schlagen sein wird

Finnegan
-
Ich denke so ein Bitset ist schon eine ganz gute wahl für das spezifische Problem.
Ich weiß es, weil ich es vergleichend ausprobiert habe.
"Memory Mapped" Bitset
Kannst Du mir da einen Link auf ein Beispiel geben? Habe ich bisher noch nicht gebraucht. Klingt interessant für den Austausch mit dem File. Ansonsten baue ich mir wirklich einen wrapper um vector<uint8_t> oder vector<uint64_t> (weiß nicht, was schneller läuft).
unordered_set<uint64_t> primzahlen; // so fing ich an fstream myPrimesDatabase; uint64_t j; myPrimesDatabase.open("myPrimesDatabase.dat", fstream::in | fstream::binary); //cout << "Primzahlen werden aus der Datei geladen ..." << endl; while (!myPrimesDatabase.eof()) { myPrimesDatabase.read((char*)&j, sizeof(j)); //cout << j << " "; primzahlen.insert(j); } myPrimesDatabase.close();Bei 8 Byte muss man weniger read aufrufen als bei einem Byte Größe. Vielleicht sollte man das sogar für das read noch größer basteln mit einer wrapper-class. Kenne mich da nicht gut aus mit Optimieren.
-
Normalerweise macht man dann eine reserve auf die Groesse und dann ein einziges read aus der Datei.
-
TGGC schrieb:
Normalerweise macht man dann eine reserve auf die Groesse und dann ein einziges read aus der Datei.
Du meinst wohl resize. Außerdem hat das Beispiel gerade irgendwie gar nichts mehr mit Bitmasken zu tun. Wenn man dann einen vector<uint8_t> oder vector<uint64_t> hat kann man alles in einer read Anweisung laden, wie TGGC schon sagte. Noch ein Vorschlag um Platz zu sparen wenn du das nicht eh schon gemacht hast: Alle geraden Zahlen würde ich gar nicht in die Bitmaske aufnehmen. Dann braucht man nur die hälfte Speicherplatz und bei ~25GB die im RAM liegen/aus einer Datei geladen sollen wäre das schon sinnvoll.
-
Erhard Henkes schrieb:
"Memory Mapped" Bitset
Kannst Du mir da einen Link auf ein Beispiel geben? Habe ich bisher noch nicht gebraucht. Klingt interessant für den Austausch mit dem File. Ansonsten baue ich mir wirklich einen wrapper um vector<uint8_t> oder vector<uint64_t> (weiß nicht, was schneller läuft).
Ich bezog mich damit auf meinen vorangenagenen Beitrag. Konkreten Beispiel-Code habe ich dafür leider nicht, nur den den Doku-Link aus der iostreams-Bibliothek von Boost, in der das "Memory Mapping" für Dateien portabel implementiert ist:
http://www.boost.org/doc/libs/1_58_0/libs/iostreams/doc/classes/mapped_file.html
Das Ganze setzt also voraus, dass du ein wenig experimentierfreudig bist und die Muße hast dich durch die Doku zu arbeiten - der Dateizugriff läuft dabei etwas anders als das klassische open/read/write ab, und es ist nicht Teil der Standardbibliothek - du brauchst also Boost dafür

Nur für den Fall, dass dir nicht ganz klar sein sollte, was es mit dem "Memory Mapping" auf sich hat:
Es handelt sich dabei um eine die Betriebssystem-Funktion, die auch bei Auslagerungsdateien/Swapspeicher zum Einsatz kommt. Dabei wird eine Datei in den Virtuellen Addressraum (also den "RAM") "abgebildet". Der Zugriff auf den Dateiinhalt erfolgt dabei über einen simplenchar*(sieheboost::iostreams::mapped_file::data()), der auf einen Speicherbereich von der Größe der gemappten Datei zeigt. Das Lesen/Schreiben der Daten wird dabei transparent vom Betriebssystem übernommen (inklusive effizentem Caching der Daten). Das einzige was man dabei selbst macht, ist in den Speicherbereich auf den derchar*zeigt zu schreiben bzw. davon zu lesen (bzw. darin deine Bits zu "schubsen"). Speicherseiten, die nicht im physischen RAM vorliegen, werden dabei vom OS aus der Datei geladen, und gleichzeitig Seiten aus dem physischen Speicher in die Datei "ausgelagert", bzw. zurückgeschrieben.Ich habe wie schonmal erwähnt selbst noch nicht mit
mapped_filegearbeitet, deswegen die Warnung von wegen "experimentierfreudig". Ich denke aber, dass das für deine Datenmassen ein schöner Anwendungsfall für ein solches Memory Mapping sind:
Deine Datenstruktur ist simpel genug, um sie mal eben "roh" in einenchar*"reinschreiben" zu können, und es gibt dir mit überschaubarem Aufwand die Flexibilität, die Daten dank OS eben nicht alle im physischen Arbeitsspeicher vorhalten zu müssen.
Dennoch kann man mit den Daten arbeiten, als lägen sie dort.Finnegan
-
Alle geraden Zahlen würde ich gar nicht in die Bitmaske aufnehmen.
Super Idee!
-
Erhard Henkes schrieb:
Alle geraden Zahlen würde ich gar nicht in die Bitmaske aufnehmen.
Super Idee!
Genausowenig die Zahlen, die durch 3 teilbar sind.
Der Index für n ist dann n-n/2-n/3+n/6.
Und dann natürlich auch für 5, 7, 11 und 13, musst von Hand schauen, wann aufhören.
-
Hi nochmal!
Da ich mich schon länger mal mit Memory Mapped IO beschäftigen wollte, habe ich mir zum Feierabend mal die Zeit genommen und einen Prototypen für das von mir vorgeschlagene Bitset implementiert. Wer möchte, kann den Code gerne verwenden/erweitern/reviewen/sich inspirieren lassen oder was auch immer. War ne nette Übung um mal was mit Memory Mapping abseits von GPU-Hardware herumzuspielen

Das Interface orientiert sich größtenteils an
std::bitset, und die Klasse sollte bis auf Details nahezu als Drop-In Replacement einsetzbar sein, wenn man mal ganz furchtbar viele Flags benötigen sollte :p.
Natürlich stecken wahrscheinlich noch eine Reihe Fehler drin - extrem grob falsch wird es allerdings hoffentlich nicht sein
Auch wenn ich nur einen simplen Zeittest gemacht habe, scheint die Klasse im linearen Schreiben/Lesen durchaus mit einem
std::vector<bool>im Arbeitsspeicher mithalten zu können (siehe Testcode), obwohl die Datenstruktur in eine Datei ausgelagert ist. Der Testcode erstellt die Datenstruktur mit jeweils 10 Milliarden Bits, schreibt alle davon linear und liest sie anschliessend ebenfalls linear. Auf meiner Mühle - Windows 8.1 - kompiliert mit Visual C++ 2013 mit Optimierungen im Release-Modus sieht das ganze etwa so aus:MemoryMappedBitset: 92.6532 Sekunden (Code siehe unten). std::vector<bool>: 98.7792 Sekunden (1). std::bitset (im Heap): 115.5720 Sekunden (2).1: http://pastebin.com/R3sDkhb6
2: http://pastebin.com/egdsRTtMOb sich so eine dateigestützte Datenstruktur wirklich lohnt, sollte man allerdings individuell mit realistischen Zugriffsmustern austesten. Gut möglich, dass das Teil bei mehr "Random Access" noch deutlich absackt. Einen Vorteil hat sie allerdings:
Man muss sich keinen Kopf um den physichen RAM und das nachladen/wegschreiben der Daten machen - Das Testprogramm lümmelt hier bei mir mit 300KiB im Arbeitsspeicher rum - genutzt wird de facto nur der freie Systemspeicher, den das OS grad entbehren kann.Gruss,
FinneganP.S.: Ja, Kommentare sind rar, ich hoffe der Code ist simpel genug dass sich die Funktionsweise aus dem Code erschließt (siehe auch Boost-Doku zu
mapped_file). Mit demcreate-Parameter des Konstruktors wird angegeben, ob eine neue Datei erstellt (überschreibt existierende) oder eine vorhandene Datei verwendet werden soll.#include <cstdint> #include <iostream> #include <algorithm> #include <chrono> #include <boost/filesystem.hpp> #include <boost/iostreams/device/mapped_file.hpp> class MemoryMappedBitset { using mapped_file = boost::iostreams::mapped_file; using mapped_file_params = boost::iostreams::mapped_file_params; public: class reference { friend class MemoryMappedBitset; public: void flip(); reference& operator=(bool value); reference& operator=(const reference& other); operator bool() const; bool operator~() const; private: MemoryMappedBitset& bitset; std::size_t pos; reference(MemoryMappedBitset& bitset, std::size_t pos); }; MemoryMappedBitset(const MemoryMappedBitset&) = delete; MemoryMappedBitset& operator=(const MemoryMappedBitset&) = delete; MemoryMappedBitset(std::string file_name); MemoryMappedBitset(std::string file_name, std::size_t number_of_bits, bool create = false); ~MemoryMappedBitset(); const std::string& getFileName() const; std::size_t size() const; bool test(std::size_t pos); void set(std::size_t pos, bool value = true); void reset(std::size_t pos); void flip(std::size_t pos); bool operator[](std::size_t pos) const; reference operator[](std::size_t pos); private: mapped_file_params params; mapped_file file; std::size_t number_of_bits; unsigned char* data; bool testUnchecked(std::size_t pos) const; void setUnchecked(std::size_t pos, bool value); void flipUnchecked(std::size_t pos); }; MemoryMappedBitset::reference::reference(MemoryMappedBitset& bitset, std::size_t pos) : bitset(bitset), pos(pos) { } void MemoryMappedBitset::reference::flip() { bitset.flipUnchecked(pos); } MemoryMappedBitset::reference& MemoryMappedBitset::reference::operator=(bool value) { bitset.setUnchecked(pos, value); return *this; } MemoryMappedBitset::reference& MemoryMappedBitset::reference::operator=(const reference& other) { bitset.setUnchecked(pos, other); return *this; } MemoryMappedBitset::reference::operator bool() const { return bitset.testUnchecked(pos); } bool MemoryMappedBitset::reference::operator~() const { return !bitset.testUnchecked(pos); } MemoryMappedBitset::MemoryMappedBitset(std::string file_name) : MemoryMappedBitset(std::move(file_name), 0, false) { } MemoryMappedBitset::MemoryMappedBitset(std::string file_name, std::size_t number_of_bits, bool create) : data(nullptr) { params.path = std::move(file_name); params.mode = std::ios_base::binary | std::ios_base::in | std::ios_base::out; // Size of the memory map in bytes. std::size_t map_size = number_of_bits / 8; // One extra byte for additional bits. if (number_of_bits % 8 > 0) map_size += 1; // Map size must be multiple of (OS) page alignment. std::size_t non_aligned = map_size % mapped_file::alignment(); if (non_aligned > 0) map_size += mapped_file::alignment() - non_aligned; // Create new (overwrite) or re-use file. if (create) { params.new_file_size = map_size; params.length = map_size; } else { // Map requested number of bits unless file is not large enough. map_size = std::min(static_cast<std::uintmax_t>(map_size), boost::filesystem::file_size(params.path)); // Ensure that map size is still multiple of (OS) page alignment // (if file size was not) and not larger than file size. non_aligned = map_size % mapped_file::alignment(); if (non_aligned > 0) map_size -= non_aligned; params.new_file_size = 0; params.length = map_size; } // Set actual number of bits. This is at least the requested number of bits // unless we re-used an existing file which was not large enough. MemoryMappedBitset::number_of_bits = map_size * 8; file.open(params); data = reinterpret_cast<unsigned char*>(file.data()); assert(data != nullptr); } MemoryMappedBitset::~MemoryMappedBitset() { if (file.is_open()) file.close(); } const std::string& MemoryMappedBitset::getFileName() const { return params.path; } std::size_t MemoryMappedBitset::size() const { return number_of_bits; } bool MemoryMappedBitset::test(std::size_t pos) { if (pos >= size()) throw std::out_of_range("Bit position out of range."); return testUnchecked(pos); } void MemoryMappedBitset::set(std::size_t pos, bool value) { if (pos >= size()) throw std::out_of_range("Bit position out of range."); setUnchecked(pos, value); } void MemoryMappedBitset::reset(std::size_t pos) { set(pos, false); } void MemoryMappedBitset::flip(std::size_t pos) { if (pos >= size()) throw std::out_of_range("Bit position out of range."); flipUnchecked(pos); } bool MemoryMappedBitset::operator[](std::size_t pos) const { return testUnchecked(pos); } MemoryMappedBitset::reference MemoryMappedBitset::operator[](std::size_t pos) { return reference(*this, pos); } bool MemoryMappedBitset::testUnchecked(std::size_t pos) const { return (data[pos / 8] & (1 << (pos % 8))) != 0; } void MemoryMappedBitset::setUnchecked(std::size_t pos, bool value) { std::size_t index = pos / 8; std::size_t bit = pos % 8; if (value) data[index] |= 1 << bit; else data[index] &= ~(1 << bit); } void MemoryMappedBitset::flipUnchecked(std::size_t pos) { data[pos / 8] ^= 1 << (pos % 8); } int main() { const std::size_t number_of_bits = 10000000000; const std::uint32_t test_pattern = 0xaaaaaaaa; const std::uint32_t use_pattern_bits = 7; auto start_time = std::chrono::high_resolution_clock::now(); try { std::cout << "Erstelle Bitset mit " << number_of_bits << " Bits ..." << std::endl; MemoryMappedBitset mbs("bitset.dat", number_of_bits, true); std::cout << "Schreibe Bits ..."; for (std::size_t i = 0; i < number_of_bits; ++i) { mbs[i] = (test_pattern & (1 << (i % use_pattern_bits))) != 0; if (i % (number_of_bits / 100) == 0) std::cout << "."; } std::cout << "\nLese Bits ..."; std::uint32_t value = 0; for (std::size_t i = 0; i < number_of_bits; ++i) { std::uint32_t shift = (i % use_pattern_bits); value |= mbs[i] << shift; if (shift == use_pattern_bits - 1) { assert(value == (test_pattern & (1 << use_pattern_bits) - 1)); value = 0; } if (i % (number_of_bits / 100) == 0) std::cout << "."; } std::cout << "\nfertig!" << std::endl; } catch (const std::exception& e) { std::cout << "ERROR: " << e.what() << std::endl; } std::cout << std::chrono::duration<double>( std::chrono::high_resolution_clock::now() - start_time).count() << " Sekunden." << std::endl; }Code benötigt Boost und muss gegen
boost_filesystem,boost_systemundboost_iostreamsgelinkt werden.
-
Finnegan schrieb:
char* data;Ich würde das auf unsigned char ändern, auch wenn du dann einmal casten musst.
-
ubbot schrieb:
würde das auf unsigned char ändern, auch wenn du dann einmal casten musst.
Gern. Die mag ich auch lieber wenn ich mit Bits hantiere. Ist halt das was man von Boost bekommt...
-
andrepeat schrieb:
Erhard Henkes schrieb:
Alle geraden Zahlen würde ich gar nicht in die Bitmaske aufnehmen.
Super Idee!
Genausowenig die Zahlen, die durch 3 teilbar sind.
Der Index für n ist dann n-n/2-n/3+n/6.
Und dann natürlich auch für 5, 7, 11 und 13, musst von Hand schauen, wann aufhören.
Da gibt es, finde ich, ein natürliches Optimum: 2,3und5 nehmen.
Rechnet man eine Zahl modulo 30, gibt es 8 mögliche Divisionsreste, die Primzahlen erlauben. Und 8 Bit hat ein Byte, wodurch der Zugriff ganz geschmeidig bleibt.