Stavba nového PC pre dátovú analytiku a programovanie (nielen) v R
V tomto blogu sa zameriavam na dôležitú, ale často prehliadanú stránku dátovej analytiky – hardvér. Ukážem, ako môže správne zostavený počítač významne ovplyvniť efektivitu práce s veľkými dátami a náročnými výpočtami. Pozrieme sa na celý proces výberu komponentov a zostavenia nového PC, ktoré som optimalizoval pre dátovú analytiku a programovanie v R.
Úvod
V tomto blogu by som sa rád podelil o svoje skúsenosti so stavbou môjho nového počítača. Po rokoch používania Dell Optiplex 7050 s procesorom i7 7700, 16 GB RAM a 512 GB SSD som sa rozhodol pre upgrade. Starý počítač bol síce spoľahlivý pre kancelársku prácu, ale pri náročnejších úlohách, ako práca s veľkými dátami či trénovanie modelov strojového učenia, už nestíhal a bol navyše hlučný kvôli zlému airflow-u. Mojím cieľom bolo postaviť výkonný a tichý (a dobre vyzerajúci) počítač, ktorý bude future proof.
Najpodstatnejšou otázkou na začiatku bolo, či ísť do laptopu alebo PC. Obe možnosti majú svoje pre a proti. V prípade laptopu je to jednoznačne prenosnosť. Na druhú stranu je však výkon značne limitovaný (napr. mobilné CPU nemajú rovnaký výkon ako ich PC verzie) a pokiaľ by som trval na niektorých parametroch, cena by bola výrazne vyššia. V mojom prípade prenosnosť (zatiaľ) nie je tak podstatný faktor, keďže prakticky inde ako doma nepracujem na vedľajších projektoch.
Výber komponentov a ich vplyv na výkon pri práci s dátami a ML
Pri výbere som sa zameral na komponenty, ktoré sú výkonné a majú prípadne potenciál na budúci upgrade. Tu je detailný prehľad použitých komponentov spolu s ich kategorizáciou a vplyvom na výkon:
Základná doska: ASUS ROG Strix B650-E Gaming WiFi
Táto doska ponúka najnovší AMD socket AM5, podporu pre PCIe 5.0 a DDR5 RAM. Má vynikajúce možnosti pripojenia, vrátane Wi-Fi 6E, Bluetooth 5.2, 2,5GB sieťovkou a množstvo USB portov. Podpora pre najnovšie technológie zabezpečuje vysokú priepustnosť dát a rýchlejšiu komunikáciu medzi komponentmi. Jedna z nevýhod moderných (najmä ASUS) základných dosiek je, že púšťajú zbytočne vysoké napätie do procesora. Toto bol aj môj prípad, preto som musel dodatočne aktualizovať BIOS a spraviť undervolting.
Procesor: AMD Ryzen 9 7900X
12-jadrový procesor s 24 vláknami a vysokou frekvenciou 4,7 GHz, v booste až 5,6 Ghz. Podporuje (len) DDR5 operačné pamäte. V čase keď som vyberal procesor bola dostupná už novšia generácia AMD procesorov rady 9000 so Zen 5 architektúrou. Recenzie boli však skôr “vlažné” a ako lepšia voľba sa stále javí táto staršia generácia procesorov (aj vzhľadom na pomer cena/výkon). V konečnom dôsledku je tento procesor ideálny pre moje potreby. Viac jadier a vlákien umožňuje paralelné spracovanie úloh, čo je pri práci s dátami a trénovaní ML modelov neoceniteľné. Zrýchľuje to výpočty a znižuje čas čakania. Rovnako jednovláknový výkon je skvelý, hoci nie na úrovni procesorov od Intelu.
Tu by som sa ešte pristavil pri otázke, prečo teda nie procesor od Intelu. Mimo toho, že aktuálny socket 1700 je už viac menej výbehový, vyššie rady (i7 a i9) 13. a 14. generácie majú veľké problémy s nestabilitou a oxidáciou.
RAM: 64 GB Kingston Fury DDR5 6000MHz CL30
Jedná sa o vysokorýchlostnú DDR5 pamäť s nízkou latenciou. Malo by sa jednať o ideálne parametre pre vybraný procesor. Default nastavenie je však obmedzené na 4800MHz. Pre využitie plnej rýchlosti je nutné povoliť EXPO I profil v BIOSe.
Veľká kapacita RAM umožňuje pracovať s rozsiahlymi datasetmi priamo v pamäti bez nutnosti swapovania na disk. Rýchlosť a nízka latencia zlepšujú výkon pri spracovaní dát a zvyšujú efektivitu ML algoritmov.
SSD: M.2 WD Black SN770 1 TB
Rýchly NVMe SSD disk s vysokými rýchlosťami čítania a zápisu.
Rýchle úložisko je kľúčové pre načítanie veľkých dátových súborov a rýchly prístup k nim. To znižuje latenciu pri vstupno-výstupných operáciách a zrýchľuje celkový proces spracovania dát.
Grafická karta: Inno3D GeForce RTX 4060 Ti Twin X2 OC
Grafická karta nižšej strednej triedy s podporou pre CUDA jadra a ray tracing. Má 8 GB RAM typu GDDR6 a 4352 CUDA jadier.
GPU môže výrazne zrýchliť trénovanie ML modelov, najmä v oblastiach ako hlboké učenie, ale dá sa použiť aj na akceleráciu tradičných algoritmov ako XGBoost alebo LightGBM. V mojom prípade bude jej primárne určenie práve na učenie sa nových knižníc potrebných pre akceleráciu tréningu a optimalizáciu algoritmov a ich hyperparametrov.
Chladenie: NZXT Kraken 240mm AiO
Výkonné all in one (AiO) vodné chladenie s 240mm radiátorom a displejom zobrazujúcim teplotu CPU.
Udržiava procesor pri nízkych teplotách, čo umožňuje udržať vysoký výkon bez throttlingu. Stabilné teploty sú dôležité pri dlhotrvajúcich výpočtoch a trénovaní modelov. 240 mm rozmer som vyberal s ohľadom na vybranú PC skrinku a možnosť montáže chladenia zvrchu, aby teplý vzduch odvádzal cez radiátor von.
Tento model, podobne ako väčšina AiO chladičov, prichádza s predaplikovanou termálnou pastou. Pri procesoroch AMD rady 7000 aj 9000 je menším problémom ich tvar. Keďže majú na sebe “výrezy”, prebytočná termálna pasta sa do nich môže dostať. Nemala by síce nič poškodiť, ale existuje relatívne lacné riešenie (hoci sa jedná len o kus plastu). Je ním Noctua NA-TPG1.
Alternatívou bolo vzduchové chladenie Noctua NH-D1. Nebol som si však istý či sa vojde do skrinky s relatívne vysokými RAM. Navyše ako človeku, ktorý má rád dáta mi zobrazovanie teplôt na displeji robí radosť.
Zdroj: Seasonic Focus GX-750
Kvalitný 750W plne modulárny zdroj s 80+ Gold certifikáciou.
Stabilné a spoľahlivé napájanie je nevyhnutné pre bezproblémovú prevádzku systému, najmä pri vysokom zaťažení. Umožňuje tiež budúce upgrady komponentov bez nutnosti výmeny zdroja (napr výkonnejší procesor alebo grafickú kartu).
Skrinka: Fractal Design North
Štýlová skrinka s drevenými prvkami, výborným prietokom vzduchu a možnosťami pre “cable management”. Dobrý prietok vzduchu zlepšuje chladenie komponentov, čo prispieva k stabilite a výkonu systému. Skrinka obsahuje dva 140 mm ventilátory. K nim je nutné ešte dokúpiť aspoň jeden 120 mm ventilátor na odvod vzduchu zo zadnej strany skrinky. Ten som vybral Cooler Master MasterFan Mf120 HALO s ARGB (adresovateľné RGB osvetlenie).
Zostavenie
Inštalácia procesora do socketu AM5 bola jednoduchá, rovnako ako osadenie DDR5 pamätí a M2 SSD disku. Procesor sa dá vložiť len jedným spôsobom, rovnako aj RAM. Len si treba zapamätať, že ak máme dve RAMky, tak osádzame druhý a štvrtý slot. V prípade tejto dosky je táto informácia naznačená aj vľavo dole pri prvom slote. Je to z dôvodu, aby sme využili dual channel. Osadenie prvého a tretieho slotu by umožnilo tiež použitie dual channel, ale neobsadené sloty (2. a 4.) môžu vytvárať odrazy signálu, čo spôsobuje menšie elektrické interferencie, čo môže zhoršiť stabilitu systému, najmä pri vyšších rýchlostiach RAM. Keď je osadený 2. a 4. slot, stopy signálu sa ukončia priamo na pamäťovom module. M2 SSD som zapojil do prvého slotu, hneď pod CPU. Prvý M.2 slot je často priamo pripojený k PCIe linkám procesora, preto komunikácia medzi SSD a CPU prebieha bez sprostredkovania čipsetom základnej dosky. To znižuje latenciu a môže mierne zlepšiť výkon. Priame pripojenie k CPU zaručuje aj to, že SSD má k dispozícii plnú šírku pásma PCIe 4.0 x4 (prvý slot na tejto konkrétnej doske je PCIe 5.0, avšak samotný M2 disk som vybral so staršou generáciou PCIe 4.0) bez zdieľania s inými zariadeniami. Všetky M2 sloty na doske majú aj hliníkové chladiče a šikovné rýchle uchytenie Q-Latch.
Väčšiu pozornosť som musel venovať správnemu manažmentu káblov a inštalácii AiO chladenia. Rozmer PC skrinky totiž rozhodne nie je štedrý s osadenou základnou doskou. Ako prvé si treba premyslieť, aké káble budú potrebné a zapojiť ich do zdroja. Až následne vložíme zdroj do skrinky. Ja som ho vkladal s ventilátorom smerom dole, aby nasával chladnejší vzduch z vonka. Skrinka vďaka mriežkam vo vnútri umožnuje aj opačnú orientáciu. Keďže však bude PC umiestnené na stole a nie na koberci, navyše v skrinke je aj zo spodu prachový filter, tak mi to prišlo ako efektívnejšie riešenie. Následne je vhodné vyviesť káble cez otvory do priestoru kde bude osadená základná doska a potom jej samotné vloženie.
Nasledovalo vodné chladenie. Na začiatok bolo nutné odmontovať predinštalované uchytenie na chladič, ktoré prišlo s doskou a nahradiť za dodávané k vodnému chladeniu. V prípade AMD je tento krok ľahší ako pri Intel procesoroch, lebo netreba inštalovať aj backplate. Radiátor a ventilátory som montoval čo najviac dopredu a s orientáciou pre odvod vzduchu zo skrinky. Pred uchytením čerpadla som na procesor dal ešte Noctua NA-TPG1 aby sa teplovodivá pasta nedostala do výrezov na ňom.
Posledný krok bolo vloženie grafickej karty. Táto konkrétna si vyžaduje len odmontovanie dvoch slotov na skrinke, vloženie a zapojenie jedného 8 pinového kábla zo zdroja. Na tejto základnej doske je aj Q-Release tlačidlo pre uvoľnenie grafickej karty, čo je veľmi príjemná vec, najmä ak vyberáte von väčší model GPU.
Celá príprava a skladanie mi zabrali asi 2 hodiny. Následná inštalácia Windows 11, nastavenie BIOSu, inštalácia programov a ovládačov výrazne viac. Pár tipov pre Vás:
- Windows pri inštalácii vyžaduje pripojenie na internet a tento krok sa na prvý pohľad nedá preskočiť. Mne počítač nedetekoval pripojenie na sieť a preto som si dohľadal toto riešenie ako obísť tento krok v inštalácii.
- V BIOSe si skontrolujte, na akej frekvencii vám bežia operačné pamäte. Je veľmi pravdepodobné, že budú na nižšej frekvencii ako majú v špecifikácii. V prípade AMD procesorov je nutné vybrať EXPO I profil, a tak pretaktovať RAM.
- Spravte stress test počítača a skontrolujte aké napätie sa dostáva do procesora napr. pomocou HWiNFO. Výrobcovia moderných základných dosiek nie vždy dodržiavajú odporúčania výrobcov procesorov. To môže v (lepšom prípade) skrátiť životnosť CPU. Ak je to nutné, nastavte v BIOSe offset.
Výsledkom je výkonný, stabilný a podľa môjho názoru aj dobre vyzerajúci počítač.
Benchmarking výkonu
Aby som otestoval a porovnal výkon novej zostavy, vykonal som niekoľko benchmarkov v jazyku R na novej zostave aj na starom Dell Optiplex 7050.
Metódy
Pre porovnanie výkonu starého PC a novej zostavy som sa zameral na dva konkrétne aspekty:
Rýchlosť načítania dát: Testoval som načítanie 4,2 GB CSV súboru so 113,6 miliónmi riadkov a 8 stĺpcami pomocou základnej funkcie
read.csv()
,read_csv()
funkcie z knižnicereadr
afread()
funkcie zdata.table
. Primárnymi faktormi ovplyvňujúcimi výkon sú rýchlosť SSD (rýchlosť čítania a zápisu) a veľkosť RAM kvôli swapovaniu.Výkon CPU: Pomocou algoritmu na generovanie prvočísel (Sieve of Eratosthenes) som porovnal jednojadrový výkon a viacjadrové paralelné spracovanie. Tento test prebehol v základnej for slučke a tiež v paralelných procesoch s rôznymi počtami vlákien (7 vlákien na oboch zostavách a 23 vlákien na novej zostave). Tento benchmark bol inšpirovaný týmto článkom na medium a touto diskusiou na stackoverflow. Testoval som tri rôzne prístupy:
- For loop – zameraný na jednojadrový výkon.
- parLapply – zameraný na viacjadrový výkon.
- foreach – zameraný na viacjadrový výkon.
Všetky testy boli zbiehané v piatich iteráciách pomocou funkcie mark
z knižnice bench:
Benchmark kód
# Required Libraries
library(bench) # Benchmarking tools
library(readr) # Fast CSV reader
library(data.table) # Fast data manipulation
library(dplyr) # Data manipulation tools
library(doParallel) # Parallel computing tools
# Part 1: Loading a large CSV file
# This tests the speed of different file loading methods (base R, readr, and data.table),
# with SSD speed and RAM performance being the key factors influencing the results.
# Base R CSV loading
<- mark(
results_csv_load_base read.csv("custom_1988_2020.csv"), # Load CSV with base R's read.csv
iterations = 5, # Number of benchmark iterations
time_unit = 's' # Measure time in seconds
|>
) select(`min time` = min, # Extract relevant metrics
`median time` = median,
`total time` = total_time,
`memory allocated` = mem_alloc)
gc() # Clear memory
# Readr CSV loading
<- mark(
results_csv_load_readr read_csv("custom_1988_2020.csv"), # Load CSV with readr's read_csv
iterations = 5,
time_unit = 's'
|>
) select(`min time` = min,
`median time` = median,
`total time` = total_time,
`memory allocated` = mem_alloc)
gc()
# Data.table CSV loading
<- mark(
results_csv_load_datatable fread("custom_1988_2020.csv"), # Load CSV with data.table's fread
iterations = 5,
time_unit = 's'
|>
) select(`min time` = min,
`median time` = median,
`total time` = total_time,
`memory allocated` = mem_alloc)
gc()
# Adding a column to distinguish the method used
$lib <- "base"
results_csv_load_base$lib <- "readr"
results_csv_load_readr$lib <- "fread"
results_csv_load_datatable
# Combine the results from different loading methods into one data frame
<- bind_rows(
results_data_load
results_csv_load_base,
results_csv_load_readr,
results_csv_load_datatable
)
# Save the results for future comparison
write_rds(results_data_load, "results_data_load.RDS")
# Display the results as a table
::kable(
knitr|>
results_data_load mutate(across(c(1, 2, 4), ~ round(.x, 2)))
)
# Part 2: Testing CPU Performance with a Prime Sieve Algorithm
# The sieve function is a CPU-bound operation that generates a list of prime numbers up to n.
# This tests single-thread performance in the for-loop version and multi-thread performance in the parallel versions.
# Prime sieve algorithm (generates primes up to n)
<- function(n) {
sieve <- as.integer(n)
n <- rep(TRUE, n)
primes 1] <- FALSE
primes[<- 2L
last.prime <- floor(sqrt(n))
fsqr while (last.prime <= fsqr) {
seq.int(2L * last.prime, n, last.prime)] <- FALSE
primes[<- which(primes[(last.prime + 1):(fsqr + 1)])
sel if (any(sel)) {
<- last.prime + min(sel)
last.prime else last.prime <- fsqr + 1
}
}which(primes)
}
# Single-threaded for loop
<- mark(
result_for_loop for (i in 10:5e4) {
<- sieve(i) # Run sieve for each value in the range 10 to 50000
result[[i]]
},iterations = 5,
time_unit = 's'
|>
) select(`min time` = min,
`median time` = median,
`total time` = total_time,
`memory allocated` = mem_alloc)
$method <- "for loop" # Label for results
result_for_loop
gc()
# Multi-threading: parLapply with 7 threads (parallel computing using 7 cores)
registerDoParallel(cores = 7) # Register 7 cores for parallel processing
<- makeCluster(7)
cl
<- mark(
result_par7 parLapply(cl, 10:5e4, sieve), # Run the sieve function in parallel on 7 cores
iterations = 5,
time_unit = 's'
|>
) select(`min time` = min,
`median time` = median,
`total time` = total_time,
`memory allocated` = mem_alloc)
stopCluster(cl) # Stop the parallel cluster
registerDoSEQ()
gc()
$method <- "parLapply 7" # Label for results
result_par7
# Multi-threading: parLapply with 23 threads (parallel computing using 23 cores)
<- detectCores() - 1 # Detect available cores minus one
no_cores registerDoParallel(cores = no_cores)
<- makeCluster(no_cores)
cl
<- mark(
result_par23 parLapply(cl, 10:5e4, sieve), # Run the sieve function in parallel on 23 cores
iterations = 5,
time_unit = 's'
|>
) select(`min time` = min,
`median time` = median,
`total time` = total_time,
`memory allocated` = mem_alloc)
stopCluster(cl)
registerDoSEQ()
gc()
$method <- "parLapply 23" # Label for results
result_par23
# Multi-threading: foreach with 7 threads
<- makeCluster(7)
cl registerDoParallel(7)
<- mark(
result_foreach_7 foreach(i = 10:5e4) %dopar% sieve(i), # Run sieve in parallel using foreach on 7 cores
iterations = 5,
time_unit = 's'
|>
) select(`min time` = min,
`median time` = median,
`total time` = total_time,
`memory allocated` = mem_alloc)
stopCluster(cl)
registerDoSEQ()
gc()
$method <- "foreach 7" # Label for results
result_foreach_7
# Multi-threading: foreach with 23 threads
<- makeCluster(no_cores)
cl registerDoParallel(cl)
<- mark(
result_foreach_23 foreach(i = 10:5e4) %dopar% sieve(i), # Run sieve in parallel using foreach on 23 cores
iterations = 5,
time_unit = 's'
|>
) select(`min time` = min,
`median time` = median,
`total time` = total_time,
`memory allocated` = mem_alloc)
stopCluster(cl)
registerDoSEQ()
gc()
$method <- "foreach 23" # Label for results
result_foreach_23
# Combine CPU benchmark results
<- bind_rows(
result_sieve
result_for_loop,
result_par7,
result_par23,
result_foreach_7,
result_foreach_23
)
gc()
write_rds(result_sieve, "result_sieve.RDS")
::kable(
knitr|>
result_sieve mutate(across(c(1:4), ~ round(.x, 2)))
)
Výsledky
Načítanie CSV súboru: Na starom Dell Optiplexe 7050 trvalo načítanie súboru nasledovne:
min čas (s) | mediánový čas (s) | total čas (s) | alokovaná pamäť (GB) | knižnica |
---|---|---|---|---|
384.19 | 390.82 | 1950.43 | 26.7 | base |
93.09 | 93.09 | 93.09 | 6.8 | readr |
10.23 | 11.83 | 72.78 | 7.6 | fread |
Na novom PC s Ryzen 7900x boli výsledky výrazne lepšie:
min čas (s) | mediánový čas (s) | total čas (s) | alokovaná pamäť (GB) | knižnica |
---|---|---|---|---|
148.02 | 151.14 | 752.08 | 26.7 | base |
15.49 | 15.49 | 15.49 | 6.8 | readr |
2.28 | 2.83 | 13.61 | 7.6 | fread |
Dramatické zlepšenie nastalo pri všetkých metódach. Najviac, o 83%, pri readr a funkcii read_csv()
. Na novej zostave je teraz táto metóda približne na úrovni fread()
na starej zostave. Víťazom v načítaní však jednoznačne ostáva práve fread()
. Pri tejto metóde je zlepšenie o 76% a spomínaný súbor načíta za neuveriteľné necelé tri sekundy.
CPU test: Výsledky na Dell Optiplex 7050:
min čas (s) | mediánový čas (s) | total čas (s) | alokovaná pamäť (GB) | metóda |
---|---|---|---|---|
48.76 | 54.92 | 286.62 | 20.9 | for loop |
8.25 | 8.40 | 16.80 | 0.5 | parLapply 7 |
18.79 | 19.10 | 96.14 | 2.5 | foreach 7 |
Výsledky novej zostavy:
min čas (s) | mediánový čas (s) | total čas (s) | alokovaná pamäť (GB) | metóda |
---|---|---|---|---|
24.48 | 26.69 | 135.55 | 20.9 | for loop |
6.64 | 6.82 | 13.65 | 0.5 | parLapply 7 |
5.96 | 5.96 | 5.96 | 0.5 | parLapply 23 |
7.22 | 7.54 | 38.00 | 2.5 | foreach 7 |
7.74 | 7.88 | 39.65 | 2.5 | foreach 23 |
Jednojadrový výkon stúpol o vyše 50%. To je veľmi dobrá správa, kedže R skripty bežia primárne na jednom jadre a paralelizácia sa používa len v prípadoch, keď to dáva zmysel. Paralelné operácie majú totiž určitý “overhead” pri komunikácii medzi vláknami a spracovávaní dát. Ak je tento overhead príliš veľký vzhľadom na zlepšenie z paralelného vykonávania, nemusí byť výkon vždy proporcionálny počtu vlákien, a to vidíme aj na výsledkoch.
Paralelný výkon vzrástol v prípade parLapply o 19% a pri foreach o 61%. Ak si porovnám najvyšší potenciálny výkon na počte dostupných vlákien (7 vs 23), tak sa bavíme o zlepšení 29% pri parLapply a 59% pri foreach. Napriek výrazne väčším zlepšeniam pri foreach je stále rýchlejší parLapply (hoci sa bavíme o desatinách až jednotkách sekúnd), navyše potrebuje výrazne menej pamäte.
Zrejme ste si všimli, že pri foreach došlo na 23 vláknach k poklesu o 2% oproti 7 vláknam. Toto je ukážka toho, že overhead z príliš veľkého počtu vlákien, zdieľané zdroje (operačná pamäť a cache), konkurencia jadier a málo alebo žiadne zdroje pre operačný systém a ostatné procesy môže mitigovať benefit viacerých jadier. V prípade operačnej pamäte sa môžeme stretnúť s odporúčaniami na 2 až 4 GB RAM na vlákno, v prípade tejto zostavy je pomer 2,7. Budúce rozšírenie na 128 GB by v niektorých prípadoch mohlo teda pomôcť.
Záver
Stavba vlastného počítača bola pre mňa novou a obohacujúcou skúsenosťou. Výber komponentov s dôrazom na výkon pri práci s dátami a ML sa ukázal ako správny krok. Navyše pri benchmarkovaní výkonu som mohol vidieť ako rôzne metódy majú výrazne odlišné nároky na systém, čo je často opomínaný faktor pri škálovaní ML riešení. Pri deploymente do cloudu môže mať zanedbanie týchto faktorov aj značný finančný dopad.
Po spustení a otestovaní zostavy som veľmi spokojný z dosiahnutého výkonu. Systém je výrazne rýchlejší a zároveň tichší. Teploty na procesore sú pri bežnej práci s dátami na úrovni okolo 60°C, pri náročnejších úlohách ako napr. trénovanie ML modelov dosahovala aj 95°C, ale zatiaľ som nenarazil na throttling (preventívne zníženie taktu procesora, aby sa predišlo prehriatiu alebo poškodeniu).
Taktiež oceňujem estetickú stránku – biele RGB osvetlenie a displej na AiO chladiči pridávajú zostave moderný vzhľad. Odporúčam každému, kto má podobné potreby, aby zvážil stavbu vlastného PC – investícia do výkonu a budúcnosti sa určite oplatí.
Leave a Reply