2012-04-11 23 views
31

Niedawno odkryłem, że można używać kompilacji JIT (just in time) z R przy użyciu pakietu kompilatora (podsumowuję moje ustalenia na ten temat w a recent blog post).Ewentualne braki w korzystaniu z JIT z R?

Jedno z pytań zadawanych jest ja:

Czy jest jakaś pułapka? brzmi zbyt dobrze, aby mogło być prawdziwe, wystarczy umieścić jedną linię kodu kodu i to wszystko.

Po rozejrzeniu się mogłem znaleźć jeden możliwy problem związany z "początkiem" dla JIT. Ale czy jest inny problem, który należy zachować podczas używania JIT?

Zgaduję, że będzie pewne ograniczenie związane z architekturą środowiska R, ale nie mogę wymyślić prostego przykładu problemu z góry mojej głowy, sugestie lub czerwone flagi będą bardzo pomocne?

+0

Nie jestem pewien trafień wydajności (inne niż początkowe kompilacjach (a może zwiększone zużycie pamięci)), ale „Uwaga: brak widocznych wiążące” wiadomości często może być przytłaczająca do początkujących (np w przypadku korzystania ggplot2) i może wyrzuć tab-complete (przynajmniej są dla mnie) – mweylandt

+0

Witaj mweylandt. Czy zdajesz sobie sprawę, co oznacza ten masaż błędu? –

+4

Umieszczam 'ByteCompile: true' w pliku DESCRIPTION moich pakietów, ponieważ tworzę nowe wersje i wygląda na to, że działa poprawnie. Zrobiłem jeden mały test 'http: // www.johnmyleswhite.com/notebook/2012/03/31/julia-i-love-you/comment-page-1/# comment-19522' i skompilowaną wersję bajtową,' fib2c' działał 4x szybciej niż zwykły, 'fib2a'. W niektórych przypadkach R jest już szybki, nawet bez kompilacji bajtów (na przykład wysoce wektoryzowany kod wykorzystujący C pod spodem), aw tych przypadkach jest oczywiście mała szansa na przyspieszenie - jest to głównie przydatne dla powolnego kodu R. –

Odpowiedz

4

Przykład rpart podane powyżej, nie wydaje się być problemem:

library("rpart") 
fo = function() { 
    for(i in 1:500){ 
    rpart(Kyphosis ~ Age + Number + Start, data=kyphosis) 
    } 
} system.time(fo()) 
# user system elapsed 
# 1.212 0.000 1.206 
compiler::enableJIT(3) 
# [1] 3 
system.time(fo()) 
# user system elapsed 
# 1.212 0.000 1.210 

Próbowałem również szereg innych przykładów, takich jak

  • rosnące wektor;
  • Funkcja, która jest po prostu otoki wokół mean

Chociaż nie zawsze uzyskać przyspieszyć, nigdy nie wystąpi znaczące spowolnienie.


R> sessionInfo() 
R version 3.3.0 (2016-05-03) 
Platform: x86_64-pc-linux-gnu (64-bit) 
Running under: Ubuntu 16.04 LTS 
11

wyjście prostego testu z rpart może być poradę, aby nie używać enableJIT we wszystkich przypadkach:

library(rpart) 
fo <- function() for(i in 1:500){rpart(Kyphosis ~ Age + Number + Start, data=kyphosis)} 
system.time(fo()) 
#User  System verstrichen 
#2.11  0.00  2.11 

require(compiler) 
enableJIT(3) 
system.time(fo()) 
#User  System verstrichen 
#35.46  0.00  35.60 

Wszelkie explanantion?

+1

To jest dziwne, więc coś na temat kompilacji pętli jest przyczyną problemu. Jeśli skompilujesz to normalnie, to się nie stanie. http://ideone.com/Nu8IZ, uwaga rpart jest już skompilowanym bajtem. – Hansi

+7

Kompilacja zajmuje dobre pół minuty: widzę to samo (2,8 s - 42,6 s), ale potem wykonanie system.time (fo()) ponownie zajmuje tylko 2.6 s. – cbeleites

+1

Podejrzewam, że jest to nieoczekiwane zachowanie ... –

-2

Po poprzedniej odpowiedzi, eksperymentowanie pokazuje, że problem polega na nie z kompilacją pętli, to z kompilacją zamknięć. [enableJIT (0) lub enableJIT (1) szybko zostawia kod, enableJIT (2) znacznie spowalnia, a enableJIT (3) jest nieco szybszy niż poprzednia opcja (ale wciąż bardzo wolno)]. Również w przeciwieństwie do komentarza Hansi, cmpfun spowalnia wykonywanie w podobnym stopniu.

+0

Wydaje się oczywiste, że przyczyną różnych wyników były różne wersje R. Używanie R 3.3.2 dzisiaj, enableJIT (i) daje dokładnie taki sam czas (1,21 sekundy) niezależnie od tego, czy ja jest 0, 1, 2, czy 3. Zostawię interpretację innym, ale wiadomość dla mnie jest zoptymalizowana bez żadnego teraz pomoc od użytkownika. – Elroch

0

W zasadzie, gdy bajt kod jest skompilowany i załadowany, to zawsze powinien być interpretowany co najmniej tak szybko jak oryginalny AST tłumacza. Jakiś kod będzie korzystał z dużych przyspieszeń, zwykle jest to kod z wieloma skalarnymi operacjami i pętlami, w których większość czasu spędza się na interpretacji R (widziałem przykłady z 10-krotnym przyspieszeniem, ale arbitralne mikro-testy mogły w istocie nadmuchać to w razie potrzeby). Niektóre kody będą działały z tą samą prędkością, zazwyczaj jest to kod wektoryzowany, przez co prawie nie ma czasu na interpretację. Teraz sama kompilacja może być wolna. W związku z tym kompilator just in time nie kompiluje teraz funkcji, gdy zgaduje, że nie będzie się opłacał (a zmiany heurystyczne zmieniają się w czasie, jest to już w 3.4.x). Heurystyki nie zawsze są trafne, więc mogą zdarzyć się sytuacje, w których kompilacja się nie opłaca. Typowymi problematycznymi wzorcami są generowanie kodu, modyfikacja kodu i manipulowanie powiązaniami środowisk przechwytywanych w zamknięciach.

Pakiety można skompilować w czasie instalacji, aby koszt kompilacji nie był płacony (wielokrotnie) w czasie wykonywania, przynajmniej dla kodu, który jest znany z wyprzedzeniem. Jest to teraz domyślna wersja rozwojowa R. Chociaż ładowanie skompilowanego kodu jest znacznie szybsze niż kompilowanie go, w niektórych sytuacjach może być ładowany nawet kod, który nie zostanie wykonany, więc faktycznie może to być narzut, ale ogólnie wstępna kompilacja jest korzystna. Ostatnio niektóre parametry GC zostały dostrojone w celu zmniejszenia kosztów ładowania kodu, który nie zostanie wykonany.

Moja rekomendacja dla twórców pakietów będzie polegać na używaniu domyślnych ustawień (kompilacja "just-in-time" jest teraz domyślnie włączona w wydanych wersjach, kompilacja bajtów w czasie instalacji pakietu jest już w wersji rozwojowej). Jeśli znajdziesz przykład, w którym kompilator kodu bajtowego nie działa dobrze, prześlij raport o błędzie (widziałem też przypadek z udziałem rpart we wcześniejszych wersjach). Poleciłbym przeciw generowaniu kodu i manipulowaniu kodami, szczególnie w gorących pętlach. Obejmuje to definiowanie zamknięć, usuwanie i wstawianie powiązań w środowiskach przechwytywanych przez zamknięcia. Zdecydowanie nie powinno się robić eval(parse(text= w gorących pętlach (a to już było złe bez kompilacji bajtów). Zawsze lepiej jest używać gałęzi niż dynamicznie generować nowe zamknięcia (bez gałęzi). Lepiej jest pisać kod z pętlami niż dynamicznie generować kod z dużymi wyrażeniami (bez pętli). Teraz z kompilatorem kodu bajtowego, obecnie dobrze jest napisać pętle działające na skalarach w R (wydajność nie będzie tak zła jak wcześniej, więc można częściej uciec bez przełączania na C dla części krytycznych pod względem wydajności) .

Powiązane problemy