Tema
/
Algoritmai ir duomenų struktūros GO
Pamoka 12 / 15 Subalansuoti medžiai (AVL) ~55 min
Teorija

Kaina, kurios skaitiklis nemato

Garantija nėra nemokama. Kad pamatytum kainą, matuok ten, kur BST jau veikė puikiai — maišytoje įvestyje.

$ algo rotate -in s10k.jsonl
n = 10000, inserted in the order given

                 build cmps   build time    rotations     height
  BST (11)           157025       3.33ms            -         30
  AVL (12)           120660      4.551ms         6922         16

  rotations per insert: 0.692
  worst |balance| anywhere in the AVL tree: 1

  lookups: BST 16.703 cmps, AVL 12.540 cmps

Skaitiklis sako, kad AVL pigesnis

BST  157 025 palyginimų
AVL  120 660 palyginimų

23 % mažiau. Ir tai tiesa: medis žemesnis, tad kiekvienas įterpimas nueina trumpesnį kelią, o kelio ilgis ir yra palyginimų skaičius.

Pagal vienintelį prietaisą, kurį naudojame nuo pirmos pamokos, balansavimas kainuoja mažiau nei nieko.

Laikrodis sako kitaip

Stulpelis build time toje išvestyje yra vienas matavimas ir jis triukšme — prie 10 000 įrašų abu variantai svyruoja 2–3 ms ribose ir keičiasi vietomis tarp paleidimų. Šeštos pamokos taisyklė galioja: kai skirtumas mažas, vienas matavimas nieko nereiškia.

Todėl matuojam kaip pridera:

$ go test -run XXX -bench 'Build|Lookup' -benchtime=20x -count=5

BenchmarkBuildBST-32       20    1859315 ns/op
BenchmarkBuildBST-32       20    1870065 ns/op
BenchmarkBuildBST-32       20    1800945 ns/op
BenchmarkBuildBST-32       20    1922465 ns/op
BenchmarkBuildBST-32       20    1940315 ns/op
BenchmarkBuildAVL-32       20    3202495 ns/op
BenchmarkBuildAVL-32       20    3352500 ns/op
BenchmarkBuildAVL-32       20    2894720 ns/op
BenchmarkBuildAVL-32       20    3288470 ns/op
BenchmarkBuildAVL-32       20    2817325 ns/op

Penki paleidimai kiekvienam, ir rėžiai nepersidengia: BST 1.80–1.94 ms, AVL 2.82–3.35 ms.

AVL pastatymas apie 1.65 karto lėtesnis — atlikdamas 23 % mažiau palyginimų.

Kur dingo skirtumas

Į stulpelį, kurio skaitiklis nemato:

rotations   6922

Rotacija yra keturios rodyklių priskyrimo operacijos ir du aukščių perskaičiavimai. Ji nėra raktų palyginimas, tad c.Hit() per ją neiškviečiamas — ir teisingai neiškviečiamas: skaitiklis matuoja palyginimus, ne visą darbą.

Ketvirta, šešta ir vienuolikta pamokos jau parodė, kur skaitikliui baigiasi matymas. Šis kartas ketvirtas ir aštriausias, nes du rodmenys rodo į PRIEŠINGAS puses:

Prietaisas parodė, kad AVL pigesnis. Laikrodis parodė, kad brangesnis. Abu teisūs — jie matuoja skirtingus dalykus.

Skaitiklis ir toliau yra teisingas įrankis algoritmo sudėtingumui — jis neprikausto tavęs prie mašinos ir nesikeičia tarp paleidimų. Bet konstanta jam nematoma, o balansavimo kaina visa yra konstantoje.

Antra pusė: paieška

BenchmarkLookupBST-32      20    1514950 ns/op   (1.45–1.56 ms)
BenchmarkLookupAVL-32      20    1246885 ns/op   (1.25–1.33 ms)

Paieška AVL medyje apie 1.16 karto greitesnė, nes medis žemesnis (16 prieš 30).

Sandoris, tikslus

BST (11) AVL (12)
įterpimas, maišyta įvestis 1.80–1.94 ms 2.82–3.35 ms (1.65× lėčiau)
paieška, maišyta įvestis 1.45–1.56 ms 1.25–1.33 ms (1.16× greičiau)
aukštis, maišyta įvestis 30 16
aukštis, -sorted 10 000 14
atmintis mazgui 88 B 96 B (h laukas)

Moki už kiekvieną įterpimą, kad išvengtum atvejo, kuris gali niekada neateiti.

Ir tai apsimoka, jei bent viena iš trijų tiesa:

  • negali garantuoti įterpimo tvarkos — o dažniausiai negali, nes ji ateina iš duomenų bazės, failo ar vartotojo;
  • skaitai dažniau nei rašai — tada 1.16 karto greitesnė paieška vienose svarstyklėse su 1.65 karto lėtesniu įterpimu, ir svarstyklės krypsta pagal santykį;
  • blogiausias atvejis yra nepriimtinas, o ne tik nemalonus.

Jei nė viena netiesa — jei duomenis pats sugeneruoji maišytus ir vieną kartą, o paskui tik skaitai — vienuoliktos pamokos medžio užtenka. Arba, kaip parodė ta pati pamoka, užtenka surikiuoto masyvo.