Tema
/
Algoritmai ir duomenų struktūros GO
Pamoka 10 / 15 Maišos lentelės ~50 min
Teorija

Apkrovos koeficientas yra atsakymas

10 000 įrašų, indeksas su plėtimusi:

$ algo index -in s10k.jsonl
n = 10000, resize = true

  buckets          16384
  stored           10000
  resizes          10
  load factor      0.610
  empty buckets    8925
  mean chain       1.341
  LONGEST chain    5
  build time       1.5ms

  10000 lookups: 13095 comparisons (1.310 per lookup), 0s
  same by linear scan: 50005000 comparisons (5000.5 per lookup), 85ms

1.310 palyginimo vienam ieškojimui. Tiesinė paieška tiems patiems 10 000 klausimų sunaudojo 5000.5. Skirtumas — 3818 kartų, o laikas 85 ms prieš tiek, kad laikrodis jo neužfiksavo.

Blogiausias atvejis — ilgiausia grandinėlė — yra 5. Ne 5000. Būtent tai reiškia „vidutiniškai O(1)": ne kad kiekvienas ieškojimas kainuoja lygiai vieną žingsnį, o kad grandinėlės lieka trumpos ir nepriklauso nuo n.

Kur dingo O(1), kai neplečiama

$ algo index -in s10k.jsonl -nogrow -buckets 16
n = 10000, resize = false

  buckets          16
  stored           10000
  resizes          0
  load factor      625.000
  empty buckets    0
  mean chain       625.000
  LONGEST chain    680
  build time       20.527ms

  10000 lookups: 3134392 comparisons (313.439 per lookup), 17.117ms

Tie patys duomenys, ta pati maišos funkcija, tas pats kodas — 239 kartus daugiau palyginimų. Nes 10 000 įrašų suverstų į 16 kibirų yra tiesiog 16 tiesinių paieškų, sudėtų greta.

Plėtimasis nėra optimizacija. Be jo maišos lentelė nėra maišos lentelė.

Tavo spėjimas

Pirmame žingsnyje užsirašei skaičių: kiek palyginimų su 10 000 įrašų ir 128 kibirais?

$ for b in 128 512 2048 8192 16384; do algo index -in s10k.jsonl -nogrow -buckets $b; done

   buckets      load      mean   longest    cmps/lookup
       128    78.125    78.125       105         40.161
       512    19.531    19.531        34         10.830
      2048     4.883     4.929        15          3.465
      8192     1.221     1.740         7          1.620
     16384     0.610     1.341         5          1.310

40.161 — maždaug pusė apkrovos koeficiento (78.125 / 2 ≈ 39), plius truputis.

Ir taip visoje lentelėje: 19.531 → 10.8, 4.883 → 3.5, 1.221 → 1.6. Sėkmingas ieškojimas vidutiniškai sustoja grandinėlės viduryje, todėl kaina yra apkrova / 2 + 1. Tai ne empirinė taisyklė — tai tiesiog vidurkis, kai raktas yra kažkurioje grandinėlės vietoje ir visos vietos vienodai tikėtinos.

Todėl riba yra 1.0, o ne 10 ar 100: plėtimasis laiko dalmenį mažesnį už du.

Ir atkreipk dėmesį į stulpelį longest. Prie 128 kibirų ilgiausia grandinėlė — 105, nors vidurkis 78. Vidurkis nėra pažadas kiekvienam paieškos veiksmui; kai kuriems raktams visada bus blogiau.