0Pricing
R Academy · Lekcja

Wektoryzacja na potrzeby wydajności

Zastąp jawne pętle operacjami wektorowymi, aby znacznie przyspieszyć działanie kodu.

Wektoryzacja na potrzeby wydajności to bezpłatna lekcja R Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej R Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs R Academy zawiera 4 lekcji w sumie.

Dlaczego wektoryzacja ma znaczenie

R jest językiem interpretowanym, dlatego pętle for wiążą się z narzutem przy każdej iteracji — obsługą wywołań funkcji, sprawdzaniem zakresów i konwersją typów. Operacje wektoryzowane przenoszą tę pracę do skompilowanego kodu C, który działa wielokrotnie szybciej.

Wektoryzacja jest najbardziej znaczącą optymalizacją dostępną w base R.

Przykład: pętla a cumsum()

Obliczenie sumy narastającej za pomocą pętli for w porównaniu z użyciem wbudowanej funkcji cumsum() wyraźnie pokazuje różnicę. cumsum() wywołuje skompilowany kod na poziomie C i przetwarza cały wektor w jednym przebiegu.

n <- 500000
x <- rnorm(n)

t_loop <- system.time({
  result <- numeric(n)
  result[1] <- x[1]
  for (i in 2:n) result[i] <- result[i-1] + x[i]
})['elapsed']

t_vec <- system.time({
  result2 <- cumsum(x)
})['elapsed']

cat('Loop  :', t_loop, 's
')
cat('cumsum:', t_vec, 's
')

ifelse() a for + if

ifelse(condition, yes, no) to zwektoryzowany warunek, który jednocześnie ocenia warunek dla całego wektora. Zastępuje pętle for + if działające element po elemencie pojedynczym przebiegiem na poziomie C.

n <- 300000
x <- rnorm(n)

t_loop <- system.time({
  result <- numeric(n)
  for (i in seq_len(n)) result[i] <- if (x[i] > 0) x[i] else -x[i]
})['elapsed']

t_vec <- system.time({
  result2 <- ifelse(x > 0, x, -x)
})['elapsed']

cat('for+if :', t_loop, 's
')
cat('ifelse :', t_vec, 's
')

Wstępna alokacja wektorów wynikowych

Gdy pętla jest nieunikniona, należy wstępnie zaalokować wektor wynikowy przed jej uruchomieniem. Powiększanie wektora za pomocą c(result, new_val) wewnątrz pętli kopiuje cały wektor przy każdej iteracji — łącznie daje to O(n^2) operacji pamięciowych.

n <- 20000

t_grow <- system.time({
  result <- c()
  for (i in seq_len(n)) result <- c(result, i^2)
})['elapsed']

t_prealloc <- system.time({
  result2 <- numeric(n)
  for (i in seq_len(n)) result2[i] <- i^2
})['elapsed']

cat('Growing vector:', t_grow, 's
')
cat('Pre-allocated :', t_prealloc, 's
')

Prawidłowe typy przy wstępnej alokacji

Aby uniknąć niejawnej konwersji podczas wstępnej alokacji, należy użyć konstruktora odpowiedniego dla danych:

  • numeric(n) — liczby zmiennoprzecinkowe podwójnej precyzji
  • integer(n) — liczby całkowite
  • character(n) — puste ciągi znaków
  • logical(n) — wartości FALSE
  • vector('list', n) — lista wartości NULL
n <- 5
cat('numeric  :', numeric(n), '
')
cat('integer  :', integer(n), '
')
cat('logical  :', logical(n), '
')
cat('character:', character(n), '
')
cat('list len :', length(vector('list', n)), '
')

colSums() i rowSums() a apply()

W przypadku operacji na macierzach funkcje colSums(m), rowSums(m), colMeans(m) i rowMeans(m) są wysoce zoptymalizowanymi procedurami C. Ich działanie jest stale szybsze niż apply(m, 1, sum), które wywołuje funkcję R sum raz dla każdego wiersza.

m <- matrix(rnorm(1000 * 2000), nrow = 1000)

t_apply <- system.time(apply(m, 2, sum))['elapsed']
t_colsums <- system.time(colSums(m))['elapsed']

cat('apply(m,2,sum):', t_apply, 's
')
cat('colSums(m)    :', t_colsums, 's
')

Arytmetyka wektoryzowana jest zawsze szybka

Podstawowe operacje arytmetyczne na wektorach — +, -, *, /, ^, sqrt(), log(), exp() — są zwektoryzowane. Działają element po elemencie na całym wektorze w ramach pojedynczego wywołania C. Zawsze należy preferować je zamiast pętli.

x <- 1:1000000

t1 <- system.time(y <- x^2 + 2*x + 1)['elapsed']

t2 <- system.time({
  y2 <- numeric(length(x))
  for (i in seq_along(x)) y2[i] <- x[i]^2 + 2*x[i] + 1
})['elapsed']

cat('Vectorized:', t1, 's
')
cat('Loop      :', t2, 's
')

Podzbiór logiczny zamiast pętli

Filtrowanie wektora za pomocą warunku logicznego jest zwektoryzowane. Zamiast wykonywać pętlę i warunkowo dodawać elementy, należy utworzyć indeks logiczny i wykonać podzbiór tylko raz — bazowy kod C przejdzie po danych jednokrotnie.

x <- rnorm(500000)

t_loop <- system.time({
  pos <- c()
  for (v in x) if (v > 0) pos <- c(pos, v)
})['elapsed']

t_vec <- system.time({
  pos2 <- x[x > 0]
})['elapsed']

cat('Loop filter:', t_loop, 's
')
cat('Logical idx:', t_vec, 's
')

which() i tabulate() do pracy z indeksami

Jeśli potrzebują Państwo pozycji wartości TRUE, which(condition) jest rozwiązaniem zwektoryzowanym i szybkim. tabulate(bin_vector) zlicza wystąpienia liczb całkowitych szybciej niż table() dla gęstych zakresów liczb całkowitych.

x <- sample(1:10, 100000, replace = TRUE)

t_table    <- system.time(table(x))['elapsed']
t_tabulate <- system.time(tabulate(x, nbins = 10))['elapsed']

cat('table()   :', t_table, 's
')
cat('tabulate():', t_tabulate, 's
')

idx <- which(x == 5)
cat('Positions of 5: first 5 =', head(idx, 5), '
')

Kiedy pętle są nadal dopuszczalne

Nie każda pętla jest zła. Pętle są dopuszczalne, gdy:

  • Każda iteracja zależy od wyniku poprzedniej (zależność sekwencyjna)
  • Liczba iteracji jest mała (< 1000)
  • Treść pętli wywołuje złożoną funkcję bez zwektoryzowanego odpowiednika

W takich przypadkach należy skupić się na wcześniejszej alokacji pamięci i unikać powiększania struktur wewnątrz pętli.

# Sequential dependency -- loop is correct here
fib <- function(n) {
  result <- integer(n)
  result[1] <- 1L
  if (n >= 2) result[2] <- 1L
  for (i in seq_len(n)[-c(1,2)]) result[i] <- result[i-1] + result[i-2]
  result
}
cat('Fibonacci:', fib(10), '
')

Podsumowanie wektoryzacji

Najważniejsze zasady wektoryzacji dla szybkiego kodu R:

  • Należy używać cumsum/cumprod/diff do sekwencyjnego kumulowania wartości
  • Należy używać ifelse() dla warunków stosowanych element po elemencie
  • Należy wcześniej alokować pamięć za pomocą numeric(n) / vector('list',n)
  • Należy używać colSums/rowSums/colMeans/rowMeans zamiast apply()
  • Podzbiór logiczny jest lepszy niż pętle filtrujące

Szybkie sprawdzenie: wcześniejsza alokacja pamięci

Dlaczego powiększanie wektora za pomocą result <- c(result, new_val) wewnątrz pętli jest tak wolne dla dużego n?

Powtórzenie: wektoryzacja

Wektoryzacja jest najważniejszym sposobem poprawy wydajności w R:

  • Funkcje zwektoryzowane (cumsum, ifelse, operatory arytmetyczne) wywołują skompilowany kod C — są od 10 do 100 razy szybsze niż równoważne pętle R
  • Należy wcześniej alokować kontenery na wyniki przed każdą nieuniknioną pętlą, aby uniknąć kopiowania O(n^2)
  • colSums/rowSums są lepsze niż apply() przy agregowaniu macierzy
  • Podzbiór logiczny zastępuje pętle filtrujące w przejrzysty i szybki sposób

Często zadawane pytania

Czy lekcja „Wektoryzacja na potrzeby wydajności” jest bezpłatna?

Tak — pełny tekst „Wektoryzacja na potrzeby wydajności” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu R Academy, przejdź na CoddyKit PRO. Kurs R Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Wektoryzacja na potrzeby wydajności”?

Zastąp jawne pętle operacjami wektorowymi, aby znacznie przyspieszyć działanie kodu. Ćwiczysz R Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć R Academy?

Nie wymagamy żadnego doświadczenia. R Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Wektoryzacja na potrzeby wydajności”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji R Academy?

Tak. Każda lekcja R Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. system.time() i proc.time()
  2. Profilowanie kodu za pomocą Rprof i profvis
  3. Wektoryzacja na potrzeby wydajności
  4. Benchmarking za pomocą microbenchmark
← Powrót do R Academy