(Acest articol a fost publicat pentru prima dată pe Postări pe blogul R-Hubși a contribuit cu drag la R-Bloggers). (Puteți raporta problema despre conținutul de pe această pagină aici)
Doriți să vă împărtășiți conținutul pe R-Bloggers? Faceți clic aici dacă aveți un blog sau aici dacă nu.
În lumea programării, lenea poate fi adesea un lucru bun: este atât o calitate umană care poate motiva eforturile de automatizare, cât și un concept de programare care evită să irosească resurse precum memoria. Acum, când citiți cod sau documentație, a vedea cuvântul „leneș” poate fi confuz, din cauza polisemiei sale: poartă mai multe semnificații. În această postare, vom enumera diferitele definiții posibile ale „leneș” în codul R.
Leneș ca în evaluarea leneșă
S -ar putea să știți că R oferă Evaluare leneșă: Argumentele unei funcții sunt evaluate numai dacă sunt accesate. Pe scurt, puteți trece orice ca valoare argumentului unei funcții fără nicio problemă, atâta timp cât funcția nu folosește această valoare.
De exemplu, codul de mai jos funcționează în ciuda evaluation
nu există deoarece definiția do_something()
Funcția include elipsis și pentru că lazy
Argumentul nu este de fapt utilizat.
do_something <- function(x, na.rm = TRUE, ...) {
mean(x, na.rm = na.rm)
}
do_something(1:10, lazy = evaluation)
#> (1) 5.5
Contrariul evaluării leneșe este Evaluare dornică.
Cartea avansată R de Hadley Wickham prezintă o introducere foarte clară în evaluarea leneșă.
Rețineți că calul de muncă al evaluării leneșe în baza r este un lucru numit promisiune care conține un expresie (rețeta pentru obținerea unei valori), un mediu (ingredientele care sunt în jur) și un valoare. Acesta din urmă este calculat doar atunci când este accesat și este în cache calculată odată.
Ce zici de promisiunile lui {Future}?
Poate că ați auzit cuvântul „promisiuni” în R în contextul viitorului pachet de Henrik Bengtsson. Oferă o implementare în r de futuresun concept de programare. Statul său de decizie de pornire „În programare, un viitor este o abstractizare pentru o valoare care poate fi disponibilă la un moment dat în viitor. Starea unui viitor poate fi nerezolvată sau rezolvată. ”
Când utilizați pachetul {viitor}, creați un viitor, care este asociat cu un promisiunecare este un Placeholder pentru o valoare și atunci valoarea în sine (deci nu aceeași definiție a „promisiunii” ca „promisiunile” utilizate de baza r în contextul evaluării leneșe). Valoarea poate fi calculată asincron, ceea ce înseamnă în paralel. Prin urmare, pachetul Futures permite programatorilor R să profite din plin de resursele lor locale de calcul: nuclee, clustere etc.
Pentru a reveni la lene, în mod implicit, un viitor este nu leneșeste dornic. Aceasta înseamnă că este calculat imediat.
În mod implicit, crearea unui viitor de mai jos (eager_future
) durează la fel de mult ca să nu înfășoară codul într -un viitor, deoarece calculul este imediat. Setare lazy
la TRUE
face ca creația viitoare să fie mult mai rapidă (lazy_future
)
library("future")
bench::mark(
no_future = is.numeric(runif(n = 10000000)),
eager_future = future(is.numeric(runif(n = 10000000))),
lazy_future = future(is.numeric(runif(n = 10000000)), lazy = TRUE),
check = FALSE
)
#> Warning: Some expressions had a GC in every iteration; so filtering is disabled.
#> # A tibble: 3 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#>
#> 1 no_future 235ms 236ms 4.19 76.3MB 2.80
#> 2 eager_future 241ms 242ms 4.06 83.1MB 4.06
#> 3 lazy_future 745µs 977µs 842. 13.8KB 10.0
Dacă recuperăm valoarea, în general se petrece același timp între crearea viitorului și obținerea valorii noastre:
bench::mark(
no_future = {is.numeric(runif(n = 10000000))},
eager_future = {x <- future(is.numeric(runif(n = 10000000))); value(x)},
lazy_future = {x <- future(is.numeric(runif(n = 10000000)), lazy = TRUE); value(x)},
check = FALSE
)
#> # A tibble: 3 × 6
#> expression min median `itr/sec` mem_alloc `gc/sec`
#>
#> 1 no_future 236ms 239ms 4.20 76.3MB 4.20
#> 2 eager_future 241ms 241ms 4.11 76.6MB 4.11
#> 3 lazy_future 253ms 254ms 3.94 76.4MB 3.94
Prin urmare, utilizarea futuresului și utilizarea evaluării leneșe sunt concepte ortogonale: puteți utiliza viitor cu sau fără evaluare leneșă. Pachetul viitor este despre Cum Valoarea este calculată (în paralel sau secvențial, de exemplu), evaluarea leneșă este aproximativ când Valoarea este calculată (corect așa cum este definită sau numai atunci când este nevoie).
Leneș ca în operațiunile de baze de date leneșe
În lumea bazei de date, interogările pot fi leneșe: interogarea este ca o listă TODO care este executată doar (calculată, evaluată) atunci când doriți să accesați tabelul sau rezultatul rezultat. Efectuarea tangibilă a ieșirii se numește materializare.
Acesta este vocabularul pe care îl putem întâlni atunci când folosim:
- Pachetul DBPlyr menținut de Hadley Wickham, care este back-end-ul DPlyR pentru bazele de date. „Toate apelurile DPlyR sunt evaluate leneș, generând SQL care este trimis în baza de date numai atunci când solicitați datele.”
Ușor modificat de la readme dblyr,
# load packages
library(dplyr, warn.conflicts = FALSE)
library("dbplyr")
#>
#> Attaching package: 'dbplyr'
#> The following objects are masked from 'package:dplyr':
#>
#> ident, sql
# create the connection and refer to the table
con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
copy_to(con, mtcars)
mtcars2 <- tbl(con, "mtcars")
# create the query
summary <- mtcars2 %>%
group_by(cyl) %>%
summarise(mpg = mean(mpg, na.rm = TRUE)) %>%
arrange(desc(mpg))
# the object is lazy, the value is not computed yet
# here is what summary looks like at this stage
summary
#> # Source: SQL (?? x 2)
#> # Database: sqlite 3.47.1 (:memory:)
#> # Ordered by: desc(mpg)
#> cyl mpg
#>
#> 1 4 26.7
#> 2 6 19.7
#> 3 8 15.1
nrow(summary)
#> (1) NA
# we explicitly request the data, so now it's there
answer <- collect(summary)
nrow(answer)
#> (1) 3
- Pachetul DTPlyR menținut și de Hadley Wickham, care este un back-end de date pentru DPlyr. Datele „leneșe”. „Captează intenția verbelor DPlyr, efectuând efectiv calculul doar atunci când este solicitat” (cu
collect()
de exemplu). Manualul explică, de asemenea, că acest lucru permite DTPlyR să facă codul mai performant prin simplificarea apelurilor de date.
Ușor modificat de la DtPlyr Readme,
# load packages
library(data.table)
#>
#> Attaching package: 'data.table'
#> The following objects are masked from 'package:dplyr':
#>
#> between, first, last
library(dtplyr)
library(dplyr, warn.conflicts = FALSE)
# create a “lazy” data table that tracks the operations performed on it.
mtcars2 <- lazy_dt(mtcars)
# create the query
summary <- mtcars2 %>%
filter(wt < 5) %>%
mutate(l100k = 235.21 / mpg) %>% # liters / 100 km
group_by(cyl) %>%
summarise(l100k = mean(l100k))
# the object is lazy, the value is not computed yet
summary
#> Source: local data table (3 x 2)
#> Call: `_DT1`(wt < 5)(, `:=`(l100k = 235.21/mpg))(, .(l100k = mean(l100k)),
#> keyby = .(cyl))
#>
#> cyl l100k
#>
#> 1 4 9.05
#> 2 6 12.0
#> 3 8 14.9
#>
#> # Use as.data.table()/as.data.frame()/as_tibble() to access results
nrow(summary)
#> (1) NA
# we explictly request the data, so now it's there
answer <- as_tibble(summary)
nrow(answer)
#> (1) 3
DuckPlyr, evaluare leneșă și prudență
Pachetul DuckPlyr este un pachet care folosește Duckdb sub capotă, dar acesta este, de asemenea, un înlocuitor de abandon pentru DPlyR. Aceste două fapte creează o tensiune:
-
Când folosim DPlyR, nu suntem obișnuiți să colectăm în mod explicit rezultatele: datele.Frames sunt dornice în mod implicit. Adăugarea a
collect()
Pasul în mod implicit ar confunda utilizatorii și ar face ca „înlocuirea drop-in” o exagerare. Prin urmare, DuckPlyr are nevoie de dorință! -
Întregul avantaj al utilizării DuckDB sub capotă este să lăsați DuckDB să optimizeze calculele, așa cum face DTPlyR cu Data.Table. Prin urmare, DuckPlyr are nevoie de lene!
În consecință, DuckPlyr este leneș la interior pentru toate operațiunile Duckdb, dar dornic la exterior, datorită Altrep, o caracteristică R puternică pe care, printre altele, o susține Evaluare amânată.
„Altrep permite obiectelor R să aibă reprezentări diferite în memorie și să fie executate codul personalizat ori de câte ori sunt accesate aceste obiecte.” Hannes Mühleisen.
Dacă lucrul care accesează datele DuckPlyr.Frame este …
- Nu DuckPlyr, apoi se execută un apel de apel special, permițând materializarea cadrului de date.
- DuckPlyr, atunci operațiunile continuă să fie leneșe (până la un apel la
collect.duckplyr_df()
de exemplu).
Prin urmare, DuckPlyr poate fi atât leneș (în sine), cât și nu leneș (pentru lumea exterioară). 🤪
Acum, materializarea implicită poate fi problematică dacă se ocupă de date mari: Ce se întâmplă dacă materializarea mănâncă toată memoria? Prin urmare, pachetul DuckPlyr are o garanție numită prudenţă pentru a controla materializarea automată (de la DuckPlyr 1.0.0). Are trei setări posibile:
- Materializare luxoasă, automată.
mtcars |>
duckplyr::as_duckdb_tibble() |>
dplyr::mutate(mpg2 = mpg + 2) |>
nrow()
#> (1) 32
- Îngrozitor, fără materializare automată.
mtcars |>
duckplyr::as_duckdb_tibble(prudence = "stingy") |>
dplyr::mutate(mpg2 = mpg + 2) |>
nrow()
#> Error: Materialization would result in 1 rows, which exceeds the limit of 0. Use collect() or as_tibble() to materialize.
- Materializare automată, până la 1 milion de celule, atât de bine aici
mtcars |>
duckplyr::as_duckdb_tibble(prudence = "thrifty") |>
dplyr::mutate(mpg2 = mpg + 2) |>
nrow()
#> (1) 32
În mod implicit,
Leneș ca în încărcarea leneșă a datelor în pachete (LazyData
)
Dacă pachetul dvs. R exportă date și stabilește LazyData
câmp în DESCRIPTION
la true
atunci seturile de date exportate sunt încărcate leneș: sunt disponibile fără utilizarea data()
dar nu iau memorie până când nu sunt accesate.
Există mai multe detalii despre LazyData
În cartea R de pachete de Hadley Wickham și Jenny Bryan și în scris extensii R.
Rețineți că datele interne sunt întotdeauna încărcate leneș și că datele care sunt prea mari nu poate fi încărcat leneș.
Leneș ca în modificările fișierelor frugale
pkgdown::build_site()
funcție, care creează un site web de documentare pentru un pachet R, caracteristici A lazy
argument. „Dacă TRUE
va reconstrui articole și pagini de referință numai dacă sursa este mai nouă decât destinația. ”
Este un concept mult mai simplu de lene: decideți chiar acum dacă este necesar să reconstruiți fiecare pagină.
Pachetul Potools, care oferă instrumente pentru portabilitatea și internaționalizarea pachetelor R, folosește „leneș” pentru un sens similar.
Leneș ca în testarea pachetului frugal
Pachetul LazyTest de Kirill Müller vă economisește timp doar prin re-rularea testelor care au eșuat în ultima rulare:
- Rulați toate testele o dată cu
lazytest::lazytest_local()
în loc dedevtools::test()
. Înregistrările de pachete LazyTest care testele au eșuat. - Următorul apel la
lazytest::lazytest_local()
Rulează doar testele care au eșuat.
În acest fel, puteți itera la rezolvarea testelor până când veți obține o rulare curată. În ce etapă este probabil înțelept să rulați din nou toate testele pentru a verifica că nu ați rupt nimic altceva între timp. 😉
Leneș ca în cuantificatori leneși în expresii obișnuite
În expresiile obișnuite, puteți utiliza cuantificatori pentru a indica de câte ori trebuie să apară un model: modelul poate fi opțional, să apară de mai multe ori, etc. Puteți, de asemenea, să specificați dacă instrumentul ar trebui să se potrivească cât mai multe repetări sau cel mai mic număr de repetări posibil.
Potrivirea celui mai mic număr de repetări posibile este „leneș” (sau zgârcit). Potrivirea cât mai multor repetări este „dornică” (sau lacomă).
string <- "aaaaaa"
# greedy! eager!
stringr::str_match(string, "a+")
#> (,1)
#> (1,) "aaaaaa"
# stingy! lazy!
stringr::str_match(string, "a+?")
#> (,1)
#> (1,) "a"
Concluzie
În contextul evaluării leneșe și al operațiunilor de baze de date leneșe, ne putem gândi la leneș ca la un fel de amânare parcimonioasă. Pentru operațiunile de baze de date leneșe, lenea este ceea ce susține optimizarea întregii conducte. În cazul modificărilor fișierelor frugale în PKGDown și Potools sau teste frugale cu LazyTest, Lazy înseamnă că se ia o decizie informată la fața locului dacă este nevoie de un calcul. În cazul cuantificatorilor leneși în expresii obișnuite, leneș înseamnă zgârcit.
În general, un utilizator se poate aștepta ca „leneș” să însemne „mai puține deșeuri”, dar este crucial ca documentația software-ului particular la îndemână să clarifice sensul și potențialele compromisuri.