Esittelyssä ALS HashMap: yleiskäyttöinen, nopea ja muistitasoinen HashTable-algoritmi.

Esittelyssä ALS HashMap: yleiskäyttöinen, nopea ja muistitasoinen HashTable-algoritmi.

Se on ollut ikäisiä viimeisimmästä blogikirjastani tällä sivustolla ja vieläkin viimeisimmästä osallistumisestani tähän hakkerointiyhteisöön tai ohjelmointimaailmaan yleensä.
Tästä syystä ja näkemällä suurta työtä, jota monet kehittäjät esittävät, olen päättänyt luoda tämän blogikirjoituksen suurimmasta tutkintatehtävänsä toistaiseksi toivomalla, että se on hyödyllinen, kun muut voivat rakentaa työkalunsa.

esittely

Tämä artikkeli keskittyy yleisempiin aiheisiin, jotka eivät suoraan koske konsolin hakkerointia tai homebrew-kehitystä, vaan pikemminkin tietojenkäsittelytieteen yleisesti, tarkemmin sanottuna tietorakenteita ja algoritmista monimutkaisuutta.

Muutaman vuoden ajan olen kehittänyt C-kielen kirjastoa, jonka tavoitteena on tarjota korkeatasoisia ominaisuuksia ja laajentaa standardeja. Yksi näistä ominaisuuksista oli kyky koodata dynaamisesti kirjoitettuja esineitä, ja tämä edellytti HashTable-ominaisuutta, jotta jatkuvat toiminnot saataisiin sisältämään määritteitä ja menetelmiä.

Haamapan sisäinen työ koostuu taulukosta (taulukko tai puskurimuisti), jossa tallennamme elementtejä indeksistä riippuen samoin kuin vektoreita, mutta toisin kuin jälkimmäisessä, indeksi haamupisteessä ei vastaa ryhmässä olevaa sijaintia, mutta pikemminkin vastaa ainutkertaista avainta, jota käytämme tallennetun elementin tunnistamiseen.
Tätä avainten ja arvojen yhdistelmää kutsutaan avainarvopariksi.
Tämä on sama kuin muuttujat toimivat; muuttujan nimi on yksilöllinen tunniste, jolla on annettu arvo.
Mitä haamapää ei ole, avain avautuu Hash-funktiolla, joka kartoittaa avaimen taulukkoon, jossa voimme tallentaa arvon.

Ongelmia on kuitenkin ongelma: mahdollisten avainten lukumäärä on teoreettisesti ääretön, mutta taulukossa olevien merkintöjen määrä on äärimmäisen pieni. Tämä tarkoittaa, että täysin erilaiset avaimet päätyvät kartoitukseen samaan paikkaan, mikä aiheuttaa sen, mitä kutsutaan hash-törmäykseksi.
Miten hallitset tätä törmäystä tallentaaksesi molemmat elementit, on yksi tärkeimmistä tutkimusalueista hajauttamisen alueella.

ketjutus

Yksinkertaisin tekniikka, jolla käsitellään hash-törmäystä, kutsutaan erilliseksi ketjutukseksi.
Ajatuksena on tallentaa linkitetty luettelo tai jokin muu tietorakenne (myös itsenäisiä binääripuita käytetään usein) jokaisen taulukon paikkaan. Aina kun avain kartoitetaan aukkoon, kun se siirtää toiminnon ylimääräiseen rakenteeseen, jolloin useampi kuin yksi avain voidaan jakaa samaan paikkaan.
Tämä HashMap-muoto on erittäin helppo ohjelmoida ja tuottaa suhteellisen hyviä tuloksia useimmissa yleisissä tapauksissa.
Javain HashMap-toteutus käyttää tätä törmäystarkkuuden muotoa punaisten mustien puiden kanssa logaritmiseen hakuun tietyssä ämpärissä.

Tällä tekniikalla on kuitenkin muutamia ongelmia: jokainen toiminta vaatii vain dynaamista muistia, ja voi olla tapauksia, joissa taulukon paikat eivät ole käytössä, jolloin tuhlaa tilaa.
Tämä pätee erityisesti, jos hajautusfunktio ei tuota hyvin jakautuneita hajautuksia.
Ketjuttaminen ei ole myöskään välimuistia dynaamisen muistin luonteen vuoksi, ja meillä voi olla tapauksia, joissa suurin osa merkinnöistä on tyhjiä, ja meillä on hyvin vähän miehitettyjä paikkoja, joissa on valtavia ylimääräisiä tietoja, mikä on kiitollinen haittaa aikataululle ja muistihäiriöille.

Tämän ongelman ratkaisemiseksi kehitettiin täysin erilainen tekniikka, joka vähentää aikaa toimintojen suorittamiseen ja takaa pöydän hyvän käytön: koetteleminen.

luotaa

Ketjuttamisen yhteydessä käytimme erilaisia ​​linkitettyjä luetteloita, jotka on tallennettu kunkin taulukon paikkaan, jossa voimme lisätä minkä tahansa lukumäärän törmäyselementtejä. Tämä kuitenkin osoittautuu tehottomaksi sekä tilassa että ajassa.
Probing kuitenkin perustuu täysin erilaiseen lähestymistapaan törmäysten käsittelyssä; ajatus on löytää tyhjä paikka kortin sisällä, jossa voimme tallentaa törmäävän elementin.
On olemassa erilaisia ​​tapoja saavuttaa tämä haku tyhjälle paikalle, mutta ne luokitellaan useimmiten kolmeksi: lineaarinen mittaus, kvadrattinen mittaus ja kaksinkertainen hajautus.

Lineaarisella koettelemalla voidaan etsiä taulukon sisällä olevaa tyhjää kohtaa lineaarisessa suunnassa alkaen törmäyspaikalta, esimerkiksi jos meillä on törmäys 16-taulukossa olevaan indeksiin 4, tarkistamme indeksin 5, 6, 7, 8, jne., Kunnes löydetään tyhjä paikka.
Kvadratistinen mittaus on oleellisesti sama, mutta indeksin lisäys lasketaan neliöllisellä funktiolla taulukon iteroimisen lineaarisen sijaan, kun taas kaksinkertainen hajautus edellyttää avaimen käyttämistä toisen hajautusfunktion avulla, joka laskee indeksin lisäyksen avaimen perusteella.

Probingin avulla taulukkotilaa voidaan käyttää yksinomaan säiliöinä elementteihimme ja parantaa etsintäaikaa useamman välimuistin osumien vuoksi, kun iteroinnit tehdään peräkkäin tallennettujen muistipaikkojen kautta ja jos käytetään hyväa toista hajautusfunktiota, voimme olla logaritmisia ja hyvin jakautuneita taulukoita. Tämä on tekniikka, jota käyttävät Python ja jotkut C ++ -strategiat.

On kuitenkin epäkohtana tutkia: kun taulukko täyttyy, se vie enemmän toistoja etsimään tyhjä aukko, mikä tarkoittaa lopulta, että taulukko saavuttaa täyden kapasiteettinsa operaatiot alkavat saavuttaa lineaarisen O (n) monimutkaisuuden ja taulukko hidastuu. Tämän estämiseksi meidän on määriteltävä suurin sallittu taulukon käyttö, ja tämä on tavallisesti noin 70% taulukon koosta.
Toisin sanoen 32-kokoiselle taulukolle ei voi tallentaa yli 22 elementtiä (0,7 * 32 = 22).
Ja se pahenee joissakin tilanteissa. Oletetaan, että tallennat 30 elementtiä hash-taulukossa, koska tämä luku ylittää 70%: n kokoisesta taulukosta 32, tarvitset taulukon, jossa on 64 merkintää, joista 34 on tyhjä; se on yli 50% pöytätilan menetyksestä riippumatta siitä, että koettelemalla pitäisi olla parempi taulukon käyttö.

Eri tekniikoita on kehitetty parantamaan koettelemusta, tämän artikkelin tärkeimpiä ovat kurkku ja hopscotch hashing.

Kovin haudutus on suhteellisen yksinkertainen tekniikka; kun uusi kohde lisätään ja se törmää, tavallisesti yrität etsiä uuden kohteen tyhjälle alueelle, mutta kurkkujen hajautuksella, jos aiemmin tallennetusta kohteesta sattuu olemaan uudelleenjakautunut, kun uusi kohde todella kuuluu siihen, panostamme uudelleen jo sisältämässäsi esineessä pitämällä uutta kohdetta sen hajautetussa korttipaikassa. Toisin sanoen, kohteet, jotka kuuluvat tiettyyn paikkaan, jonka hajautusfunktio määrää, ovat etusijalla kohteista, jotka on osoitettu koettelemalla törmäyksen aikana, jolloin lisäämme tavallisten O (1) monimutkaisten kohteiden määrää.

Hopscotch-hajautus toimii ajatuksena rajoittaa lisätoimien toimimiseen tarvittavien iterointien määrää. Ajatus on melko yksinkertainen; käytä hyppäämistä etsimään tyhjän paikan törmäävälle elementille, mutta jos tämä uusi paikka on liian kaukana alkuperäisestä (kuten hopscotch-alueen valtuuttama), meidän on järjestettävä joitain elementtejä varmistaaksemme, että löydämme tarkemman paikan, tavallisesti etsimällä kolmannen miehitetyn korttipaikan lähempänä hajautettua indeksiä, joka voidaan siirtää tyhjään paikkaan katkaisematta hopscotch-alueen rajoitus.

Sekä kakku ja hopscotch järjestävät tallennettujen elementtien sijainnin parantaakseen pöydän suorituskykyä, mutta kaikki tekniikat eivät vieläkään takaa logaritmisia pahimpia tapauksia: saatat silti joutua tekemään O (n) iteraatioita etsimään tyhjä aukko, joten suurin sallittu taulukon käyttörajoitukset ovat edelleen voimassa, joten jätämme paljon pöytätilaa.

ALS HashMap: Yksi tekniikka hallita niitä kaikkia.

Huolimatta kaikista erilaisista tekniikoista ja tutkimuksista, joita on tehty hash-taulukoiden parantamiseksi, meillä on edelleen huonommassa muistin käyttö ja mahdollinen taulukon suorituskyvyn heikkeneminen O (n): lle pahimmassa tapauksessa.
Lopulta sanoin itselleni "Tämä on sietämätöntä! HashMapsin pitäisi taata aina logaritmisen pahimman tapauksen ja korkean taulukon käyttö (mahdollisimman vähän tyhjää tilaa) ", joten aloin luoda algoritmi, joka voisi saavuttaa tämän: korkean taulukon käytön (suuret kuormitekijät) aina varmalla logaritmisella rytmillä kaikki toiminnot. Näin ALS HashMap syntyi.

ALS HashMap on algoritmi, joka yhdistää kaikki edellä mainitut tekniikat yhdeksi, käyttämällä kunkin maailman parhaita mahdollisuuksia mahdollisimman paljon taulukon käyttöä ilman, että joudutaan tekemään operaatiota log (n) -asteikon yläpuolella.
Algoritmi on siis hybridi, se käyttää sekä koettelemusta että ketjuttamista: koettelemalla voimme jakaa elementtejä taulukkoon, jolloin saadaan vähemmän dynaamisia muistia ja vähemmän hukkaan pöytätilaa, mutta ketjuttamalla voimme tallentaa enemmän elementtejä kuin taulukko sallii ja siten välttää kalliit O (n) taulukon uudelleenkäsittely. Kurkkua ja hopscotch-tekniikoita käytetään myös keinona järjestellä elementtejä uudelleen ja estää koettelemukset tekemästä enemmän kuin sallittuja iterointeja.

Tämä saavutetaan pääosin käyttämällä sekä koettelemuksia että ketjuttamista törmäystarkkuuteen. Testaus on rajoitettu vain tiettyihin paikkoihin lähellä hajautettua indeksiä (hopscotch-alue), kun taas ketjuttaminen on rajoitettu vain, kun koettelemus epäonnistuu ja vain tietty määrä elementtejä voidaan tallentaa, kunnes taulukon kokoa tarvitaan.

ALS HashMapissa jokainen taulukossa oleva ämpäri sisältää kaksi bittiä informaatiota, joka kertoo algoritmille, mitä kyseisessä kauhassa tallennetaan ja miten sekä avainarvopari tai lisätietojen keruu (yleinen osoitin tai C-liityntyyppi voidaan mallinnuttaa). On olemassa neljä eri tilaa, joista jokainen ämpäri voi olla, ja algoritmi saa sen nimen.

– E: merkki, joka merkitsee, että kauha on tyhjä.

– A: merkki, jossa ämpäri sisältää lisätietojen keräämisen. A-tyyppiseen kauhaan hajautetut kohteet sijoitetaan suljettuun keräykseen, kuten normaalisti tehdään ketjuttamalla.

– L: tämä merkki tarkoittaa, että ämpäri sisältää yhden avainarvoparin, joka on allokoitu tässä hajautusfunktiolla, toisin sanoen tallennettu avain kuuluu tähän ja kaikki tämän kohteen toiminnot ovat O (1).

– S: Tätä merkkiä käytetään ämpäreissä, joissa on yksi avain-arvo-pari, joka on täällä koonnut. Toisin sanoen tässä tallennetun parin iskeytyy toiseen paikkaan, joka on jo käytössä, joten algoritmi on käyttänyt kokeilua etsimään läheisen tyhjän ämpärin tämän kohteen tallentamiseksi.

Kun uutta kohdetta asetetaan, ajetaan avain hash-toiminnon läpi tavalliseen tapaan ja toimimme kauhan tilan mukaan avaimen kohdalla.

– E: ämpäri on tyhjä, joten säilytetään vain avainarvopari ja merkitään se tyypiksi L.

– A: ämpäri sisältää lisätietojen keräämisen, kun asetamme avain-arvo -parin siihen.

– L: ämpäri sisältää kohteen, joka kuuluu tähän, joten meidän on ratkaistava törmäys. Tässä on, missä taika tapahtuu. ALS-algoritmi pyrkii ensin etsimään lähellä olevan tyhjän ämpärin hopscotch-alueella havainnollistamalla lineaarisesti molempiin suuntiin lähtemättömästä aukosta lähtien. Laskennallisten koettimien lukumäärää kutsutaan koettimen alueeksi ja se on arvo, joka ei saa olla enemmän kuin taulukon koon 2 logaritmi. Jos koettelemus onnistuu etsimään tyhjän aukon hopscotch-alueella, sijoitamme törmäyselementin siihen ja merkitsemme korttipaikan tyypiksi S. Kun koetin epäonnistuu, koska se ei löydä tyhjää kauhoa rajallisen alueen sisällä, sitten turvaudumme ketjuttamiseen: luomme lisädatarakenne hajautetun korttipaikan sisällä ja merkitse se tyypiksi A, sitten lisäämme siihen kaikki tähän paikkaan kuuluvat kohteet (jotka kaikki löytyvät hopscotch-alueella).

– S: tämä on silloin, kun hamppu hajautuu.Tähän aikaväliin sisältyvä avain-arvo-pari on allokoitu täällä koettelemalla, mikä tarkoittaa, että tämän kohteen toiminnot ovat logaritmisessa mittakaavassa, mutta uusi lisättävä kohde on hajautettu tähän paikkaan, joten sen monimutkaisuus on vakio, jos lisätään tähän. Siksi poistetaan ja asetetaan uusi elementti paikalleen asettamalla uusi elementti tähän paikkaan ja muuttamalla sen merkki S: stä L: hen.

Osien asettaminen on algoritmin hankalampi osa, koska se vaatii kohteiden uudelleenjärjestämistä kauhojen merkkien mukaan, kun esineitä on käsitelty. Kuitenkin tapa, jolla algoritmi toimii, tekee etsinnästä ja poistamisesta huomattavasti yksinkertaisemman ja yleisemmin nopeamman kuin sinun on todella otettava huomioon tyypin L ja A kauhat.
Hakeaksesi ja poistamalla avain, joka on haudutettu tyyppiä E olevaan kauhaan, avainta ei ole, muussa tapauksessa se olisi löytynyt tässä ämpärissä tai läheisessä. Sama koskee S-tyyppisiä kauvoja: etsiessäsi tai poistamalla ja hajautusfunktiota kohdistuu tyypin S kauhaan, päätämme, että tällaista avainta ei ole koskaan asetettu, muutoin ämpäri olisi L-tyypistä johtuen kakkon tekniikasta S-elementti jossain muualla, jotta L-elementti olisi mahdollista jatkuvasti.
Tämä tarkoittaa, että hakuun ja poistoon on vain kaksi kauhan tyyppiä: L ja A.
Jos hajautusfunktio osoittaa A-tyypin ämpäriin, etsimme vain elementin sisältämässä tietorakenteessa. Jos ämpäri on L-tyypistä, tarkistamme, että suljettu avain vastaa etsimääsi avainta, ellei sitten tehdä koettelemusta ja jos se epäonnistuu, avainta ei löydy.

Koska mittaus on rajoitettu hopscotch-alueella, varmistamme, että mitään koetustoimintaa ei koskaan ylitetä log (n) -nopeuden yläpuolella, ja ylimääräiset datarakenteet rajoitetaan myös tiettyyn määrään elementtejä samasta syystä. tapahtuu.

Nyt voit kysyä: Kuinka hyvä tämä algoritmi suorittaa?
Olen suorittanut testejä tällä algoritmilla, joka sisälsi 500 000 elementin lisäämistä siihen ja vertaa sitä std :: unordered_map -ohjelmaan, standardin HashMap-toteutukseen C ++: lle. Tulokset, joissa hämmästyttävä:

– C ++: n HashMap ei onnistunut tallentamaan kyseistä määrää, koska se tarvitsi taulukon 2 ^ 20 (taulukko, jossa oli miljoona merkintää), jotta taulukon käyttö väheni 70% (kuten edellä on selostettu, kokeilu vaatii tämän rajoituksen toiminnan pitämiseksi logaritmisena) ja minun tietokone ei voinut jakaa niin paljon tilaa kerralla. Vaikka se olisi mahdollista, se vaatii vielä yhden miljoonan syötteen taulukon tallentamaan vain puolet esineistä.

– ALS HashMap ei ainoastaan ​​pysty tallentamaan kaikkia elementtejä, vaan käyttää sitä taulukossa, jonka koko on 2 ^ 19, noin 500 000.

– ALS HashMapin taulukon käyttökerroin on yli 90%, toisin sanoen yli 90% taulukkotilasta käytetään, eli taulukkotilaa käytetään erittäin tehokkaasti.

– Kuten selitettiin, kaikki ALS-algoritmin suorittamat toiminnot ovat taatusti logaritmisen asteikon alapuolella. Testauksen tapauksessa koetinalue oli 19 ja suurin löydetty apurakenne sisälsi vain 9 kohdetta. Tämä tarkoittaa sitä, että 500 000 elementtiä sisältävässä hajautuspisteessä pahin mahdollinen tapaus on joko 19: n lineaarisen iteroinnin tekeminen tai iteroituminen 9 elementin yksinkertaisen linkitetyn luettelon kautta.

Toinen todellisten tietojen avulla suoritettu testi (tarkemmin sanoen Pythonin merkkijono- menetelmät luokkaseuran simuloimiseksi) näytti samat tulokset: nopeampi käyttöaika, jossa korkea taulukon käyttö ja vähemmän hukkaan menevää muistia.

Nyt minun on kysyttävä itseltäsi, mitä tämän algoritmin tulevaisuus on ja onko se osoittautunut hyödylliseksi todellisissa sovelluksissa, vain aika kertoo. Tällä hetkellä jätän sinut github-linkkiä varten, josta löydät hankkeen lähdekoodin ja dokumentaation.



GitHub-projektin sivu

Toivottavasti olet nauttinut lukemisesta ja toivottavasti tämä ei ole viimeinen, jonka kuulet minulta.

Like this post? Please share to your friends:

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: