Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » _sehr_ langen vektor durchsuchen

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: [ 1 ] [ 2 ] [ 3 ] > 4 < [ 5 ]
030
21.01.2009, 11:15 Uhr
~raps
Gast



Zitat von 0xdeadbeef:
Du gibst weder eine Größe für die Hashtable an, noch eine auf die erwarteten Werte zugeschnittene Hashfunktion.
bitte informier dich ueber hash_map bzw hash_set.


Zitat:
Davon abgesehen musst du natürlich die Implementierungen in zwei Prozesse auftrennen, sonst leidet der Lauf der Hashtable ggf. unter den selben Problemen, die die Bittable mit sich bringt. Swapping ist, wenn du gigabyteweise Speicher anforderst, durchaus eine ernstzunehmende Möglichkeit.

bitte, wenn ich mir 30min nehme das zu schreiben, nimm dir die 3min um den code anzuschauen, dann siehst du, dass es explizit nur als zwei prozesse zu testen ist.


Zitat:

Bitte, tu uns allen den Gefallen und informier dich darüber, wie und unter welchen Bedingungen Hashtables funktionieren, und was man tun muss, damit sie das gut tun. Dieser Haufen, es tut mir leid, Bockmist zeigt lediglich, dass du nicht verstehst, worum es hier überhaupt geht.

ist es moeglich deine grenzenlose arroganz ein wenig zu maessigen?


Zitat:

Nachtrag: Oh, und natürlich ist der Speicherverbrauch deiner Bittable keinesfalls konstant, er skaliert mit der Registerbreite der CPU (wie bereits angemerkt). Schreib mir bitte mal eine Bittable-Implementierung für x86-64 - ich habe Gerüchte gehört, diese Architektur sei auf dem Vormarsch.

die ganze zeit versuchst du mit beleidigungen und ausfaelligen bemerkungen deine ueberlegenheit zu demonstrieren und hier spielst du den dummen.
du weisst sicher selbst, dass man vom konstanten speicherverbrauch bzw laufzeitverhalten im zusammenhang mit den eingangsvariablen spricht. die bitbreite ist hingegen keine variabel die du zur laufzeit veraendern kannst.
ansonsten kannst du beim quicksort bei steigender zahl der elemente auch linear die laenge deren steigern und hast den beweis erbracht dass quicksort langsammer als O(n*log(n)) ist.


Zitat:

*grummel*Bitarrays für alle möglichen Werte, so ein Unfug...*grummel*

das ist das einzige problem bei der sache, es koennte eine andere loesung geben die besser ist, die ist natuerlich invantil, dumm, trotz beweises natuerlich auch nur eine stuempferhafte implementierung von jemandem der dir noch einen gefallen tun sollte und schauen muss wie man c++ benutzt.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
031
21.01.2009, 11:25 Uhr
~raps
Gast



Zitat:

Zitat von ~raps:

erste wert vom vector: 0xc37bc7f7
dazu mal im vergleich 400 000: 0x0061A80


Höchster möglicher Wert im Vektor bei 32bit: 0xffffffff
In deine Tabelle passt nicht einmal 0xdeadbeef...
*plonk*

der erste eintrag im vector verdeutlich lediglich dass deine erste annahme grundlos und inkorrekt war, es stellt keinesfalls das limit der eintraege da, ich weiss nicht wie du auf diese annahme kommst.

aber danke dass du wenigstens nicht ausfallend wirst.

Dieser Post wurde am 21.01.2009 um 19:28 Uhr von FloSoft editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
032
21.01.2009, 11:36 Uhr
~raps
Gast



Zitat von 0xdeadbeef:
Er kriegt acht Werte in ein Byte, scheinbar reicht der Speicherbereich des Vektors hier von 0xa37bc7f7 bis 0xc37bc7f7. Die Platzierung verwundert mich ein bisschen; scheinbar legt sein Betriebssystem ein sehr großes Heapsegment an, was wohl der Grund sein dürfte, warum das ganze überhaupt funktioniert.


nein, der grund ist viel trivialer.


Zitat:

Natürlich bräuchte er auf einer 64bit-Maschine über 2 Exabyte für diesen Scherz (und eine Menge Zeit, während die Maschine den Speicher nullt), und auf einer 32bit-Plattform wird es mit weiteren 400 MB auf dem Heap, sonstigem Verbrauch des Programms, anderer Programme und des Betriebssystems bei unter 2 Gigabyte auch schon knapp. Und das unter der Annahme, dass keine weiteren Prozesse laufen, die vergleichbar große Mengen RAM verbrauchen - gut möglich, dass der Ansatz überhaupt nur auf einer Maschine praktikabel ist, die PAE nicht nur beherrscht, sondern auch ausnutzt. Dazu kommen natürlich mögliche Probleme mit der Heapfragmentation, wenn das ganze mehrfach durchgeführt werden soll.

Es ist einer der albernsten Ansätze, die ich je gehört habe, ganz ernsthaft. Mit uint16_t könnten wir über sowas reden, aber von der Größenordnung sind wir hier ja weit entfernt.
es ist der schnellste ansatz.
aber da du sowas in keinem buch gelesen hast, ist es wohl auch nicht verstaendlich fuer dich weshalb es ueberhaupt so gut laeuft. so voreingenommen wie du bist, willst du es auch nicht verstehen.

das ist eine kontext bezogene loesung, keine die du mit 128bit usw. in die laecherlichkeit zu ziehen versuchen musst.


the right tool for the right job.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
033
21.01.2009, 12:46 Uhr
Guybrush Threepwood
Gefürchteter Pirat
(Operator)



Zitat von ~raps:

nein, der grund ist viel trivialer.


dann erleuchte uns doch damit

Bevor du dich über die Antworten anderer beschwerst solltest du dir erstmal deine eigenen genau anschauen und dann entscheiden ob du vernünftig über das Problem diskutieren oder nur rumtrollen möchtest. Deine letzten Beiträge lassen nämlich leider auf letzteres schließen.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
034
21.01.2009, 13:41 Uhr
~raps
Gast



Zitat von Guybrush Threepwood:

Zitat:

nein, der grund ist viel trivialer.


dann erleuchte uns doch damit

Bevor du dich über die Antworten anderer beschwerst solltest du dir erstmal deine eigenen genau anschauen und dann entscheiden ob du vernünftig über das Problem diskutieren oder nur rumtrollen möchtest. Deine letzten Beiträge lassen nämlich leider auf letzteres schließen.

bei aller freundlichkeit, aber was erwartest du wenn man am stueck versucht mich auf unfreundlichste art zu denunzieren?

ich nehme mir die zeit beide moeglichkeiten auf gelaeufige weise implementiert, mit der stl implementierung die dem kuenftigen standard konform ist und als antwort kommt dass ich hashmaps nicht verstehe und irgendwelche aus den luft gegriffenen annahmen wie "Du machst einen Denkfehler: Es gibt zwar nur 400k verschiedene Werte" die mit einem blick in den sourcecode geklaert waeren.

auf der anderen seite versteht man aber garnicht wieso _in_diesem_fall_ das cpu/cache freundlicher ist eine BitTable zu benutzen und entsprechend schneller laeuft.

das ganze vorgehen ist echt traurig. mein beruf besteht daraus zu optimieren, das ist was ich 90% der zeit mache und es ist etwas selbstverstaendliches, dass man fuer ein gegebenes problem die beste loesung gibt, und wenn es bubblesort ist. daraufhin muss niemand anfangen ueber den kontext hinweg diese loesung in die laecherlichkeit zu ziehen, es gibt keine loesung in diesem thread die in jedem fall schneller ist. Und mit generischen loesungen eine kontextbezogene dann fuer fiktive faelle zu vergleichen macht keinen sinn (siehe antwort 13)
ich versuche hier auch nicht auf der spur zu fahren, dass bei 100 millionen eintraegen bei 32 bit vermutlich fast jeder unterschiedlich ist und somit die hashmap sogar mit perfect hashing 400MB und mit sicherheits bereich fuer eine 80%ige fuellung (bei nicht perfektem hashing) 500MB waere, was zu einem cachemiss pro zugriff enden wuerde, zu dem ganzen overhead des hashing selbst. was bitte sollte so ein unsinn bringen? wozu macht man das auf der anderen seite mir gegenueber?
der fall ist doch simpel, 400 000 verschiedene eintraege aus 100Millionen heraus zu filtern, fuer einen int datentyp.

Dieser Post wurde am 21.01.2009 um 19:29 Uhr von FloSoft editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
035
21.01.2009, 14:23 Uhr
Guybrush Threepwood
Gefürchteter Pirat
(Operator)



Zitat von ~raps:

bei aller freundlichkeit, aber was erwartest du wenn man am stueck versucht mich auf unfreundlichste art zu denunzieren?


Das kann ich in dem Thread nicht nachvollziehen bis zu dem Punkt wo du dich wohl persönlich angegriffen gefühlt hast und dann etwas flapsig wurdest.


Zitat von ~raps:
die mit einem blick in den sourcecode geklaert waeren.

Vielleicht ja nicht für jeden?

Zitat von ~raps:

auf der anderen seite versteht man aber garnicht wieso _in_diesem_fall_ das cpu/cache freundlicher ist eine BitTable zu benutzen und entsprechend schneller laeuft.


Wie bereits gesagt, dann erklär doch warum das so ist.



Zitat von ~raps:
mein beruf besteht daraus zu optimieren, das ist was ich 90% der zeit mache und es ist etwas selbstverstaendliches

Das mag ja so sein, aber das heißt ja nicht das das für jeden alles so selbstverständlich ist. Ich kann da zum Beispiel vom eigentlichen Thema nicht mitreden, aber wenn ich mir den Thread durchlese kann ich zum Beispiel sehen das Beefy seine Aussagen begründet, wenn du jetzt denkst das das nicht so ist wie er sagt dann schreib auch warum nicht und dann kann man drüber reden.
Ansonsten bringt das alles hier doch nix
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
036
21.01.2009, 20:55 Uhr
0xdeadbeef
Gott
(Operator)


Deine Beispielimplementierung zeigt in keiner Weise Optimierungen. Die Probleme deiner Art der Hashtableverwendung habe ich schon erklärt, Hashing funktioniert stumpf nicht so, dass man einfach hinschreibt "hash jetzt!" und der Compiler die Hashtable automatisch so dimensioniert, wie man sie braucht und einen sinnvollen Hash auswählt.

Wenn dein Beruf daraus bestünde, Programme zu optimieren, sollte dir dann bewusst sein, dass ein Array deiner Bittable-Größe niemals in den CPU-Cache passt. Ich hätte ein bisschen Kreativität beim Hashing mit mindestens SSE2 erwartet. Ich hätte zumindest eine Erwähnung der Probleme, in die du mit der Speicherverwaltung laufen kannst, erwartet. Ich hätte erwartet, dass du dir Gedanken darüber machst, wo der Speicher herkommen soll, und wie oft der Kernel ihn wohl aus der Auslagerung zurückholen muss.

Das, was du dir da ausgedacht hast, kann höchstens ein Mathematiker, der von einer Art "perfekter Architektur" ausgeht und die Realität nicht kennt, als Optimierung auffassen. Wenn mein Ton hier ungehalten klingt, dann liegt das daran, dass du mir mit deiner offenkundigen Inkompetenz, die du auch noch als eine Art göttliche Gabe darzustellen versuchst, verdammt auf die Eier gehst.
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
037
21.01.2009, 23:23 Uhr
kronos
Quotenfisch
(Operator)



Zitat von ~raps:

ich nehme mir die zeit beide moeglichkeiten auf gelaeufige weise implementiert, mit der stl implementierung die dem kuenftigen standard konform ist und als antwort kommt dass ich hashmaps nicht verstehe und irgendwelche aus den luft gegriffenen annahmen wie "Du machst einen Denkfehler: Es gibt zwar nur 400k verschiedene Werte" die mit einem blick in den sourcecode geklaert waeren.

Zur Erklärung meiner aus der Luft gegriffenen Annahme:
Der code ist ja nun leider nicht mehr online, aber ich bin mir ziemlich sicher, dass dein Bitvector die Länge 100.000.000 hatte. Wenn man von unit32 ausgeht, was du ja offenbar tust, kannst du also Werte bist 3.200.000.000 abhaken. Der größte uint32-Wert ist aber wie schon erwähnt 4 294 967 296. Das ist mehr als 3.200.000.000. Die Implementierung funktioniert also nur so lange die Werte im Eingabevektor unter dieser Grenze bleiben und nicht im kompletten int-Bereich.
Das ist jetzt keine Luft und wenn du glaubst, dass ich mich irre, dann sag' bitte an welcher Stelle. Wenn ich mich nicht irre, dann heißt das entweder, dass du willentlich unzulänglich implementiert hast oder - wie ich wohlwollend angenommen habe - einen Denkfehler gemacht hast...
--
main($)??<-$<='?'>>2?main($-!!putchar(
(("$;99M?GD??(??/x0d??/a:???;a"+'?'/4)
??($??)+'?'/3-2-1+$%2)??''?')):'?';??>
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
038
21.01.2009, 23:42 Uhr
0xdeadbeef
Gott
(Operator)


Das Ding war 2<<(32 - 3) Byte lang, genug Platz war also schon. Die 100 Millionen war die Länge des Datenvektors.

Ich hab mich allerdings noch mal ein bisschen über die Hashtable-Implementierung, die er da benutzt hat, schlau gemacht (STLPort). Es handelt sich dabei um eine geschlossene Hashtable, die per default die Identität (!) als Hash-Funktion benutzt. Sie fängt mit 7 (!) Buckets an, und wenn diese voll sind (es kann eine maximale Auslastung angegeben werden, das hat er aber nicht getan), wird mehr Speicher angefordert und neu gehasht. Dabei wird aus einer unvollständigen Liste von Primzahlen (mit Ausnahme der ersten beiden und der letzten ist dabei jede etwa doppelt so groß wie die vorherige) jedes mal die nächstgrößere als neue Größe ausgewählt.

Dass es sich dabei um einen suboptimalen Hash handelt, ist offensichtlich - das ist aber bei einer derart generischen Implementierung nicht weiter verwunderlich. Dass die Hashtable, wenn sie 16 mal (!) volläuft (!) und neu angelegt (!) werden muss, nicht gut skaliert, kann niemanden überraschen, der auch nur die geringste Ahnung davon hat, wie die Dinger funktionieren.

Das nur, um meine Wahl des Begriffs "Bockmist" zu erklären. Rückblickend hätte ich vielleicht ein kräftigeres Wort gebrauchen sollen.
--
Einfachheit ist Voraussetzung für Zuverlässigkeit.
-- Edsger Wybe Dijkstra

Dieser Post wurde am 21.01.2009 um 23:46 Uhr von 0xdeadbeef editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
039
22.01.2009, 10:11 Uhr
~raps
Gast



Zitat von Guybrush Threepwood:

Das mag ja so sein, aber das heißt ja nicht das das für jeden alles so selbstverständlich ist. Ich kann da zum Beispiel vom eigentlichen Thema nicht mitreden,


dann bekommst du gerne von mir die grundausbildung in Hash.

"A hash function is any well-defined procedure or mathematical function which converts a large, possibly variable-sized amount of data into a small datum, usually a single integer that may serve as an index into an array." (quelle wikipedia).

und auf gut deutsch: ein hash algorithmus mapt auf einen n-dimensionalen raum von einem >n dimensionalem raum z.b. einen string von 200byte auf 32bit.
aus diesem grund kann es mehrere werte geben die den selben hash-wert erhalten (nennt man dann Hash collision).
Ein guter hash algorithmus nutz den ganzen raum voll aus, bei 32bit kann also bei einem 32bit input jeder wert wieder rauskommen, das heisst auch dass keine hashcollision enstehen kann.
Was bedeutet ein hash algorithmus, angewendet auf werte der selben und von kleineren dimensionen, nur eine permutation der werte erzeugt und man erhaelt keine Collision, was aber auch bedeutet dass man die dimension nicht verkleinert.
eine schluesseleigenschaft von hashes ist, dass sie irreversibel sind, du kannst von einem hash nicht mehr die ursprungsdaten errechnen, dazu:
"Hash Functions take a block of data as input, and produce a hash or message digest as output. The usual intent is that the hash can act as a signature for the original data, without revealing its contents. Therefore, it's important that the hash function be irreversible" (quelle: www.lincoln.edu/math/rmyrick/ComputerNetworks/InetReference/142.htm)

das heisst auch, ein (guter) hash von 32bit auf eine 32bit zahl bringt nichts, das ist soviel wert wie ein hash auf einen hash, du erhaellt keine dimensionsverkleinerung und kannst deswegen auch ein inverses mapping erstellen, von hash zu ursprungs-wert.
Es waere also nur eine zeitverschwendung. nun ein kleiner blick in die hash implementierung von STL_PORT (die nicht sonderlich anders ist als andere)


C++:
template <class _Key> struct hash { };

inline size_t __stl_hash_string(const char* __s) {
  _STLP_FIX_LITERAL_BUG(__s)
  unsigned long __h = 0;
  for ( ; *__s; ++__s)
    __h = 5*__h + *__s;
  
  return size_t(__h);
}

_STLP_TEMPLATE_NULL struct hash<char*> {
  size_t operator()(const char* __s) const
  { _STLP_FIX_LITERAL_BUG(__s) return __stl_hash_string(__s); }
};

_STLP_TEMPLATE_NULL struct hash<const char*> {
  size_t operator()(const char* __s) const
  { _STLP_FIX_LITERAL_BUG(__s) return __stl_hash_string(__s); }
};

_STLP_TEMPLATE_NULL struct hash<char> {
  size_t operator()(char __x) const { return __x; }
};
_STLP_TEMPLATE_NULL struct hash<unsigned char> {
  size_t operator()(unsigned char __x) const { return __x; }
.
.
.
_STLP_TEMPLATE_NULL struct hash<size_t> {
  size_t operator()(size_t __x) const { return __x; }
};

du siehst, die fachwelt gibt einfach wieder den eingangswert zurueck fuer char,short, unsigned int (bei 32bit systemen size_t genannt).
nun einmal in meinem beispielcode die hier von "beefy" vorgeschlagene " vernünftige Hash-Funktion z.B. Jenkin's one-at-a-time hash" eingebaut, ergebniss: statt >18s hat man nun 25s laufzeit. Die anzahl der collisions in der hashtable veraendert sich nicht, es kommt lediglich die zeit hinzu die du fuers hashing brauchst.


----------------------------------------------------------------
soviel zu hashes, nun zu hashtables: (ich hoffe ich bin verstaendlich ).

wieso dann ueberhaupt eine hashtable? ganz einfach, das eigentliche "hashing" was dann auch die kollisionen verursacht erzeugt man indem man den 32bit index auf die hash-table laenge mapt. also entweder ein modulo (%size ) oder bei power of two groessen eine und verknuepfung (&(size-1)). wenn also die hashtable eine groesse von 400 000 hat und du pruefst ob 50 oder ob 400 050 in der hashtable ist, hast du eine hash collision. das ist auch der ganze grund fuer die langsammkeit, man muss diese collisions aufwendig behandeln.

Die hier grundlangenlos in den raum gestellte aussage "Natürlich ist das so langsam, was erwartest du denn? Wahrscheinlich kopiert das hash_set dauernd seine Daten hin und her und hasht die halbe Zeit neu." kann man leicht wiederlegen indem man die hashtable in einem ersten durchgang fuellt und in einem zweiten dann das eigentliche rausfiltern der daten durchfuehrt (Weil die hashmap voll ist, werden natuerlich _alle_ werte als schon vorhanden erachtet und somit nicht in den vector kopiert, aber fuer die validierung ist das ok). das ergebnis ist: von 18.8ms -> 18.3ms. (ich erspar dir mal die details weshalb das schon so effizient ist, aber falls du es dennoch wissen willst, frage ruhig ).

nun meine behauptung, es liegt an hash collision die man immer aufloesen muss. wie du vielleicht in meinem sourcecode sehen kannst, nutz ich so ziemlich den ganzen 32bit raum aus um die 400 000 werte einzufuegen, diese sind in immer gleichen intervalen (das ist beim thread starter nicht so, das ist ja nur mein worst-case fuer bit-table szenario). entsprechend kann man mit ein paar weiteren operationen einfach durch die intervallaenge teilen und erhaellt einen perfect hash (fuer diesen konstruierten fall). das mal in meine beispiel applikation eingebaut und die zeiten aendern sich folgendermassen: 18ms -> 10ms.
Ist aber immer noch nicht so schnell wie die 8ms der bittable, wieso? weil es noch overhaed gibt. auch wenn es keine collision gibt, stl_port rechnet modulo und da sie nicht weiss dass es ein perfect hash ist, prueft sie natuerlich immer ob eine collision vorliegt.

q.e.d.

----------------------------------------------------------------
nun zur vorab analyse des problems bevor wir eine loesung vorschlagen koennen.
es gibt 400 000 werte die 100Mio mal angefasst werden. kritisch ist also jeder lookup und sollte moeglichst schnell sein, also ohne collisions.
perfect hashing ist in der realen welt leider nicht moeglich, also lass uns die alternative checken. BitTables.

wieviel ist das an daten? 512MB? oder 400 000/8 Byte? wieviel schaufelt man schlussendlich?

antwort: das haengt davon ab auf welcher architektur. einzelne bits kannst du nicht laden, was du laden kannst sind cachelines, bottleneck ist dabei entweder der cache zugriff falls die daten reinpassen, ansonsten ramzugriff.
amd xp: 32byte(cacheline groesse) * 400 000 = ca 12MB
intel core2duo: 64byte -> 24mb
PowerPC (z.b. cell): 128byte -> 48MB

ergo, die limitation ist der main memory zugriff. du kannst _ganz_grob_ mit ca 75cycles bis 150cycles rechnen die ein zugriff kostet. 100Mio -> 15Gigacycles, bei 2.4 Ghz sind das bei 0 cachehits 6.25s. (nebenbei, der modulo alleine bei einer hashtable kostet ca 40cycles)

mit der simplen implementierung von mir kommen wir bei >8.9s raus. es gibt ja auch die moeglichkeit noch prefetching einzubauen um waehrend der aktuellen berechnungen die latenz auszunutzen, sodass man vom cahe noch was hat. mit diesen zwei zeilen


C++:
        _mm_prefetch((const char*)&rVec[a+16],_MM_HINT_NTA);
        _mm_prefetch((const char*)&RBitTable[rVec[a+4]>>5],_MM_HINT_NTA);


erhaellt man schliesslich ca 4.5s an laufzeit -> 108 cycles pro test.

so, ich hoffe ich hab dich alten piraten nicht zu sehr gelangweilt. danke
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: [ 1 ] [ 2 ] [ 3 ] > 4 < [ 5 ]     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: