0xDEADBEEF

RSS odkazy english edition
««« »»»

Rychlosti a latence SSD disků

16. 2. 2021

Když jsem psal jeden zatím nezveřejněný článek, došlo mi, že se zakládá na něčem, co jsem nevěděl jistě, ale jen to předpokládal: cenu IO operací, konkrétně jejich granularitu na SSD. Je nejmenší jednotka čtení z disku jedna 4 kB stránka? Jaký je rozdíl mezi čtením dvou 4 kB stránek a jednoho 8 kB regionu? Optimalizuje IO systém pro lokalitu dat?

Určité informace o zařízení a souborovém systému poskytne příkaz blockdev (nebo soubory v /sys/block/sd*/queue/). Na odrbaném SATA SSD v laptopu (Kingston 120GB SV300) s kernelem 5.9 a btrfs to dává tyto údaje:

--getbsz      blocksize                     4096 B
--getfra      filesystem readahead          256 x 512 B = 128 kB
--getra       readahead                     256 x 512 B = 128 kB
--getiomin    minimum I/O size              512 B?
--getioopt    optimal I/O size              0
--getmaxsect  max sectors per request       2560 = 1280 kB
--getpbsz     physical block (sector) size  512 B
--getss       logical sector size           512 B

Velikost bloku 4 kB, velikost sektoru 512 B. Takže jak?

Napsal jsem proto malý testovací program, který se snaží odhalit některé parametry a vrhnout aspoň trochu světla na vnitřní chování SSD. Provádí jednak sekvenční čtení a pak čtení z náhodných a nepředvídatelných míst velkého komprimovaného souboru.


Při sekvenčním čtení program (po vyčištění page cache, aby se skutečně testovalo SSD) čte 4 kB bloky dat zarovnané na 4 kB a některé stránky jsou výrazně pomalejší než jiné. Rychlá stránka z page cache dorazí za 0.001 - 0.005 ms, pomalá musí na disk a to trvá něco mezi 0.2 - 0.6 ms.

Vysvětlení je jednoduché: Kernel implementuje techniku anticipatory paging, někdy označovanou jako page cache readahead a ta řeší situace, kdy program požaduje data sekvenčně. Pokud kernel detekuje čtení z po sobě jdoucích stránek (nezáleží jestli jde o jednu velkou I/O operaci nebo mnoho malých), začne načítat data dopředu. Dělá to nejen na rotujících discích, kde to vede k největším přínosům, ale i na SSD.

Když se začíná číst na stránce x pomalé jsou stránky x+0, x+1 , x+5, x+21, x+85. Skoky +1, +4, +16, +64 odpovídají tomu, co jsem vyčetl z linuxových zdrojáků: Kernel opakovaně zvětšuje readahead okno na čtyřnásobek dokud nedosáhne horní hranice. Ta je dle výpisu blockdev nastavena na 128 kB. Tento proces běží paralelně s programem a někdy data nestihnou dorazit do page cache včas a tak ukážou určité menší zpoždění.

Stejné chování nastane, pokud čtu méně než 4 kB dat, ale adresy postupují vpřed bez mezer. Když se čte víc jak 4 kB najednou, prefetch stále pracuje. Může to vypadat, že má odlišný průběh, ale jde jen o zdání. Rozsahy pro readahead se počítají ve 4 kB stránkách, ne v jednotkách, které čte program.

Pokud má čtení určitý krok (např. přečtu 4 kB a pak 4 kB přeskočím), všechny stránky jsou pomalé. OS nedetekuje sekvenční čtení a mechanismus není aktivován.

Readahead má efekt nejdřív při čtení 3 stránek - první stráka nic neznamená, druhá spustí readahead a třetí se přečte samovolně na pozadí.


Zatím šlo o funkcionalitu jádra, ne SSD samotného. OS pracuje se 4 kB stránkami, ale mě zajímá, jaká je granularita čtení na straně SSD. Druhá část testu proto zkouší, jak dlouho trvá čtení 4k stránky nebo souvislého bloku stránek z náhodné pozice přímo z disku.

Na ose x jsou vyneseny jednotlivé přístupy seřazené podle doby trvání, na logaritmické ose y časy v mikrosekundách.

Čtení menší než 4 kB není výrazně rychlejší, což nejspíš ukazuje na fakt, že OS si vždy vyžádá celou stránku. Naproti tomu větší bloky dat jsou pomalejší a to poskytuje odpověď na otázku, jaká je granularita čtení ze SSD: V tomto případě 4 kB limitované OS. Není žádná větší nedělitelné množství dat, které by mělo jednotkovou cenu.

Zajímavé pak je, když vynesu mediány časů pro jednotlivé velikosti, především oblast vlevo dole.

Vysvětlení této křivky poskytne pohled na vnitřní strukturu SSD, které jsou pod kapotou uspořádané nějak takhle:

     1:N         1:N        1:N          1:N          1:N
SSD -----> chip -----> die -----> plane -----> block -----> page

Na PCB leží několik čipů, každý obsahuje několik placek křemíku, každá z nich nese několik nezávislých částí označovaných jako plane, každá z nich je rozdělena na několik bloků a každý blok pokrývá několik SSD stránek. Blok představuje minimální jednotku, kterou je možné smazat a SSD stránka je nejmenší množství dat, které se dá přečíst nebo zapsat.

Uvnitř SSD se nachází mnoho nezávislých částí a ty nabízejí velký potenciál pro paralelismus. Několik nebo všechny plane v die můžou běžet paralelně, kdy 1 plane čte jednu SSD stránku.

Jaká bývá velikost SSD stránky v současnosti? To se nedá nikde snadno vyčíst, výrobci informace tají, jak to jen jde. Nejspíš bude větší než 4 kB. Tady Micron naznačuje, že to bude víc než 8 kB.

K rychlostem se tady píše:

Data reads are at the granularity of flash pages, and a typical read operation takes 25μs to read a page from the media into a 4KB data register, and then subsequently shift it out over the data bus. The serial line transfers data at 25ns per byte, or roughly 100μs per page.

Jde o data z roku 2008, přesto poskytují mentální model, jak se dívat na chování SSD: Přečíst blok dat z flash paměti do interního registru trvá nějakou fixní dobu (tady uvádějí SLC 25 μs, MLC 50 μs, TLC 100 μs) + poslat data dál do systému trvá podle počtu bajtů. Fixní režie se nemůžeme zbavit, jde o limitaci technologie. Na druhou stranu může běžet mnoho čtení paralelně a tím vykoupit latenci velkou propustností. Každá jednotlivá IO operace nese malou nedělitelnou prodlevu, ale ta je amortizovaná paralelismem.

Pokud se čte rozsah dat menší než součet šířky interních stránek (označované jako superpage nebo clustered page), všechny tyto dlouhé operace snímání dat z flash můžou teoreticky běžet paralelně. Při nekonečném paralelismu zbývá konstantní cena za dotaz. Rychlost a paralelismus propojení flash modulů se zbytkem systému pak ovlivňuje lineární faktor ceny závisející na množství přenesených dat.

Ve výsledku se chování dá popsat jako t(n) = tq + tp × n, kde tq je konstantní čas dotazu a tp čas přenesení jednoho bajtu ven.

Moje SSD podle grafu nahoře zhruba odpovídá rovnici t(n) = 0.0081 × n + 279 [μs]

Ta dává maximální rychlost někde v okolí 123 MB/s. Výrobce udává 450 MB/s čtení, ale jen 180 MB/s v případě incompressible data transfer. Protože testuji na souboru komprimovaném přes zstd, odhad bude nejspíš docela blízko skutečnosti. Rozdíl může mít spoustu různých důvodů: Je to starý disk, který neustále dělá read-retry, data nejsou perfektně rozhozená, aby se využil dostupný paralelismus, jde o extra přesné měření a tak dále.

Číst 8k je dražší než číst 4k, nicméně ne dvojnásobně. Na měřeném disku to vypadá, že 32 kB je 2x pomalejší než 4 kB. Na jiných discích to bude jinak, ale konečně mám odpověď na položené otázky.


První verzi testovacího programu jsem napsal v PHP, protože proč ne. Je to jazyk, sice není nejrychlejší, ale rozhodně ne tak pomalý, aby plně zamaskoval cenu pomalých IO operací. Čtení různě velkých dat z náhodných míst po vyčištění page cache vypadalo jako v grafu.

Čas pro 4 kB a 8 kB byl identický. Navíc se najednou načítaly páry stránek jako (0,1), (4,5), (6,7), ale ne (1,2), (5,6) nebo (7,8). Když jsem chtěl jen jednu, ta druhá byla připravena v paměti. Začal jsem z toho vyvozovat, že minimální granularita SSD je 8 kB, ale bylo to celé špatně. Teprve až profilování syscallů přes strace ukázalo, že za to může PHP. Když řeknu fread($f, 1), PHP ve své nekonečné moudrosti najednou načte a bufferuje 8 kB dat. Velikost bufferu se dá změnit1 , ale skoro mě to donutilo tu psát nesmysly.

Jako obvykle platí, že benchmarkování není snadné. V cestě stojí mnoho vrstev: SSD, jádro OS, souborový systém, libc a tady i PHP, a každá z nich může ovlivnit výsledné chování.


Program jsem opravil a prubnul SSD serveru, kde běží i tento web, abych měl čísla z něčeho jiného než starého šrotu.

Data vypadají celkem podobně, jen je disk celkově svižnější. Pro mediánový dotaz zhruba t(n) = 0.0052 × n + 207 [μs]. To znamená čtení narazí na strop kolem 192 MB/s nekomprimovatelných dat a stále tam je těch 200 μs latence, než se začne něco dít.


Něco dalšího:


  1. stream_set_chunk_size nastavuje kolik dat načte jedna IO operace, stream_set_read_buffer nastavuje velikost bufferu.
píše k47 (@kaja47, k47)