Największym problemem i źródłem nieefektywności jest indeksowanie danych. Ramka, mam na myśli wszystkie te linie, w których używasz temp[,]
.
Staraj się unikać tego tak bardzo, jak to możliwe. Wziąłem twoją funkcję, zmień indeksowanie i tutaj wersja_A
dayloop2_A <- function(temp){
res <- numeric(nrow(temp))
for (i in 1:nrow(temp)){
res[i] <- i
if (i > 1) {
if ((temp[i,6] == temp[i-1,6]) & (temp[i,3] == temp[i-1,3])) {
res[i] <- temp[i,9] + res[i-1]
} else {
res[i] <- temp[i,9]
}
} else {
res[i] <- temp[i,9]
}
}
temp$`Kumm.` <- res
return(temp)
}
Jak widać, tworzę wektor, res
który zbiera wyniki. Na koniec dodaję go data.frame
i nie muszę się bawić z imionami. Jak to jest lepsze?
Uruchomić każdy funkcję data.frame
z nrow
od 1,000 do 10,000 i 1000 przez pomiar czasu zsystem.time
X <- as.data.frame(matrix(sample(1:10, n*9, TRUE), n, 9))
system.time(dayloop2(X))
Wynik jest
Możesz zobaczyć, że twoja wersja zależy wykładniczo nrow(X)
. Zmodyfikowana wersja ma zależność liniową, a prosty lm
model przewiduje, że dla 850 000 wierszy obliczenie zajmuje 6 minut i 10 sekund.
Moc wektoryzacji
Jak podają Shane i Calimo w odpowiedzi, wektoryzacja jest kluczem do lepszej wydajności. Z kodu możesz wyjść poza pętlę:
- kondycjonowanie
- inicjalizacja wyników (które są
temp[i,9]
)
To prowadzi do tego kodu
dayloop2_B <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in 1:nrow(temp)) {
if (cond[i]) res[i] <- temp[i,9] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Porównaj wyniki dla tych funkcji, tym razem nrow
od 10 000 do 100 000 na 10 000.
Tuning dostrojony
Kolejną poprawką jest zmiana indeksowania pętli temp[i,9]
na res[i]
(które są dokładnie takie same w iteracji i-tej pętli). To znowu różnica między indeksowaniem wektora a indeksowaniem a data.frame
.
Po drugie: kiedy spojrzysz na pętlę, zobaczysz, że nie ma potrzeby zapętlania wszystkich i
, ale tylko tych, które pasują do warunków.
Więc zaczynamy
dayloop2_D <- function(temp){
cond <- c(FALSE, (temp[-nrow(temp),6] == temp[-1,6]) & (temp[-nrow(temp),3] == temp[-1,3]))
res <- temp[,9]
for (i in (1:nrow(temp))[cond]) {
res[i] <- res[i] + res[i-1]
}
temp$`Kumm.` <- res
return(temp)
}
Wydajność, którą zyskujesz, zależy od struktury danych. Dokładnie - na procent TRUE
wartości w stanie. W przypadku moich danych symulowanych zajmuje to czas obliczeń dla 850 000 wierszy poniżej jednej sekundy.
Chcę, żebyś mógł pójść dalej, widzę co najmniej dwie rzeczy, które można zrobić:
- napisz
C
kod do zrobienia warunkowego sumowania
jeśli wiesz, że w twojej sekwencji maks. sekwencja nie jest duża, możesz zmienić pętlę na wektoryzowaną podczas, coś w tym rodzaju
while (any(cond)) {
indx <- c(FALSE, cond[-1] & !cond[-n])
res[indx] <- res[indx] + res[which(indx)-1]
cond[indx] <- FALSE
}
Kod używany do symulacji i liczb jest dostępny na GitHub .
res = c(1,2,3,4)
acond
wszystkoTRUE
, a wynik końcowy powinien być następujący:1
,3
(bo1+2
)6
(przyczyna jest drugi3
i trzeci są3
również)10
(6+4
). Robi proste sumowanie co masz1
,3
,5
,7
.Ogólne strategie przyspieszenia kodu R.
Najpierw dowiedz się, gdzie naprawdę jest wolna część. Nie ma potrzeby optymalizacji kodu, który nie działa wolno. W przypadku niewielkiej ilości kodu po prostu przemyślenie go może działać. Jeśli to się nie powiedzie, pomocne mogą być RProf i podobne narzędzia do profilowania.
Po wykryciu wąskiego gardła zastanów się nad bardziej wydajnymi algorytmami do robienia tego, co chcesz. Obliczenia należy uruchamiać tylko raz, jeśli to możliwe, więc:
Korzystanie z bardziej wydajnych funkcji może powodować umiarkowane lub duże przyrosty prędkości. Na przykład
paste0
daje niewielki wzrost wydajności, ale.colSums()
jego krewni uzyskują nieco wyraźniejszy wzrost.mean
jest szczególnie wolny .Następnie możesz uniknąć szczególnie częstych problemów :
cbind
spowolni cię bardzo szybko.Spróbuj lepiej wektoryzować , co często, ale nie zawsze, może pomóc. Pod tym względem z natury wektoryzowane polecenia, takie jak
ifelse
,diff
i tym podobne zapewnią większą poprawę niżapply
rodzina poleceń (które zapewniają niewielki lub żaden wzrost prędkości w dobrze napisanej pętli).Można także spróbować podać więcej informacji do funkcji R . Na przykład użyj
vapply
raczej niżsapply
i określcolClasses
podczas czytania danych tekstowych . Przyrosty prędkości będą zmienne w zależności od tego, ile zgadujesz.Następnie rozważ zoptymalizowane pakiety :
data.table
Pakiet może generować ogromne przyrosty prędkości tam, gdzie jest możliwe jego użycie, w manipulacji danymi i odczytywaniu dużych ilości danych (fread
).Następnie spróbuj uzyskać wzrost prędkości dzięki bardziej wydajnym sposobom wywoływania R :
Ra
ijit
razem do kompilacji just-in-time (Dirk ma przykład w tej prezentacji ).I na koniec, jeśli wszystkie powyższe nadal nie przyspieszają tak szybko, jak potrzebujesz, być może będziesz musiał przejść do szybszego języka, aby uzyskać wolny fragment kodu . Połączenie
Rcpp
iinline
tutaj sprawia, że zamiana tylko najwolniejszej części algorytmu na kod C ++ jest szczególnie łatwa. Oto, na przykład, moja pierwsza próba i zdmuchuje nawet wysoce zoptymalizowane rozwiązania R.Jeśli mimo wszystko nadal masz problemy, potrzebujesz tylko większej mocy obliczeniowej. Zajrzyj do paralelizacji ( http://cran.r-project.org/web/views/HighPerformanceComputing.html ), a nawet rozwiązań opartych na GPU (
gpu-tools
).Linki do innych wskazówek
źródło
Jeśli używasz
for
pętli, najprawdopodobniej kodujesz R tak, jakby to było C, Java lub coś innego. Prawidłowo wektoryzowany kod R jest niezwykle szybki.Weźmy na przykład te dwa proste fragmenty kodu, aby wygenerować listę 10 000 liczb całkowitych w sekwencji:
Pierwszym przykładem kodu jest sposób kodowania pętli za pomocą tradycyjnego paradygmatu kodowania. Wykonanie zajmuje 28 sekund
Można uzyskać prawie 100-krotną poprawę dzięki prostej czynności wstępnego przydzielania pamięci:
Ale przy użyciu podstawowej operacji wektorowej R za pomocą operatora dwukropka
:
operacja ta jest praktycznie natychmiastowa:źródło
a[i]
się nie zmienia. Alesystem.time({a <- NULL; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- 1:1e5; for(i in 1:1e5){a[i] <- 2*i} }); system.time({a <- NULL; a <- 2*(1:1e5)})
ma podobny wynik.rep(1, 1e5)
- czasy są identyczne.Można to zrobić znacznie szybciej, pomijając pętle za pomocą indeksów lub
ifelse()
instrukcji zagnieżdżonych .źródło
i
-ta wartość zależy odi-1
-tego, więc nie można ich ustawić w sposób, w jaki to robisz (używającwhich()-1
).Nie przepadam za przepisywaniem kodu ... Oczywiście ifelse i lapply to lepsze opcje, ale czasem trudno jest to dopasować.
Często używam data.frames, tak jak przy użyciu list takich jak
df$var[i]
Oto wymyślony przykład:
wersja data.frame:
wersja listy:
17 razy szybsze użycie listy wektorów niż data.frame.
Wszelkie uwagi na temat tego, dlaczego wewnętrzne data.frame są tak powolne w tym zakresie? Można by pomyśleć, że działają jak listy ...
Aby uzyskać jeszcze szybszy kod, zrób to
class(d)='list'
zamiastd=as.list(d)
iclass(d)='data.frame'
źródło
[<-.data.frame
, który jest jakoś nazywany, kiedy to robisz,d$foo[i] = mark
i może skończyć się tworzeniem nowej kopii wektora prawdopodobnie całej data.frame przy każdej<-
modyfikacji. Byłoby to interesujące pytanie dotyczące SO.df$var[i]
notacja[<-.data.frame
spełnia tę samą funkcję? Zauważyłem, że to naprawdę dość długo. Jeśli nie, z jakiej funkcji korzysta?d$foo[i]=mark
grubsza się tłumaczyd <- `$<-`(d, 'foo', `[<-`(d$foo, i, mark))
, ale z pewnym użyciem zmiennych tymczasowych.Jak Ari wspomniał na końcu swojej odpowiedzi, pakiety
Rcpp
iinline
sprawiają, że niezwykle szybko można zrobić wszystko szybko. Jako przykład wypróbuj teninline
kod (ostrzeżenie: nie testowano):Istnieje podobna procedura do
#include
generowania rzeczy, w której wystarczy przekazać parametrdo cxxfunction, as
include=inc
. Naprawdę fajne jest to, że wykonuje za Ciebie wszystkie połączenia i kompilacje, więc prototypowanie jest naprawdę szybkie.Oświadczenie: Nie jestem całkowicie pewien, że klasa tmp powinna być macierzą numeryczną, a nie macierzą numeryczną lub czymś innym. Ale jestem prawie pewien.
Edycja: jeśli nadal potrzebujesz większej prędkości, OpenMP jest dobrym narzędziem do równoległości
C++
. Nie próbowałem tego używaćinline
, ale powinno działać. Pomysł byłoby, w przypadkun
rdzeni, iteracja pętli mająk
być przeprowadzone przezk % n
. Odpowiedni wprowadzenie znajduje się w Matloff znajduje The Art of Programming R , dostępny tutaj , w rozdziale 16, Odwołanie się do katalogu C .źródło
Odpowiedzi tutaj są świetne. Jednym drobnym aspektem, który nie został uwzględniony, jest to, że pytanie brzmi: „ Mój komputer wciąż działa (teraz około 10 godzin) i nie mam pojęcia o środowisku uruchomieniowym ”. Zawsze opracowuję następujący kod w pętlach podczas opracowywania, aby wyczuć, jak zmiany wydają się wpływać na szybkość, a także monitorować, ile czasu zajmie ukończenie.
Działa również z lapply.
Jeśli funkcja w pętli jest dość szybka, ale liczba pętli jest duża, rozważ drukowanie tylko tak często, jak drukowanie na samej konsoli ma narzut. na przykład
źródło
cat(sprintf("\nNow running... %40s, %s/%s \n", nm[i], i, n))
ponieważ zwykle zapętlam nazwane rzeczy (z imionami wnm
).W R często można przyspieszyć przetwarzanie pętli za pomocą
apply
funkcji rodziny (w twoim przypadku prawdopodobnie tak byłobyreplicate
). Spójrz naplyr
pakiet, który zawiera paski postępu.Inną opcją jest całkowite unikanie pętli i zastępowanie ich wektoryzowaną arytmetyką. Nie jestem pewien, co dokładnie robisz, ale prawdopodobnie możesz zastosować swoją funkcję do wszystkich wierszy jednocześnie:
Będzie to znacznie szybsze, a następnie możesz filtrować wiersze według swojego warunku:
Wektoryzacja arytmetyki wymaga więcej czasu i zastanowienia się nad problemem, ale czasem można zaoszczędzić kilka rzędów wielkości w czasie wykonywania.
źródło
Przetwarzanie za pomocą
data.table
jest realną opcją:Jeśli zignorujesz możliwe korzyści z filtrowania warunków, jest to bardzo szybkie. Oczywiście, jeśli możesz wykonać obliczenia na podzbiorze danych, to pomaga.
źródło