Прискорення погано написаних прикладів Р.


77

Приклади Джулії для порівняння продуктивності проти Р видаються особливо заплутаними . https://github.com/JuliaLang/julia/blob/master/test/perf/perf.R

Яку найшвидшу продуктивність ви можете отримати з двох наведених нижче алгоритмів (бажано з поясненням того, що ви змінили, щоб зробити його більш схожим на R)?

## mandel

mandel = function(z) {
    c = z
    maxiter = 80
    for (n in 1:maxiter) {
        if (Mod(z) > 2) return(n-1)
        z = z^2+c
    }
    return(maxiter)
}

mandelperf = function() {
    re = seq(-2,0.5,.1)
    im = seq(-1,1,.1)
    M = matrix(0.0,nrow=length(re),ncol=length(im))
    count = 1
    for (r in re) {
        for (i in im) {
            M[count] = mandel(complex(real=r,imag=i))
            count = count + 1
        }
    }
    return(M)
}

assert(sum(mandelperf()) == 14791)

## quicksort ##

qsort_kernel = function(a, lo, hi) {
    i = lo
    j = hi
    while (i < hi) {
        pivot = a[floor((lo+hi)/2)]
        while (i <= j) {
            while (a[i] < pivot) i = i + 1
            while (a[j] > pivot) j = j - 1
            if (i <= j) {
                t = a[i]
                a[i] = a[j]
                a[j] = t
            }
            i = i + 1;
            j = j - 1;
        }
        if (lo < j) qsort_kernel(a, lo, j)
        lo = i
        j = hi
    }
    return(a)
}

qsort = function(a) {
  return(qsort_kernel(a, 1, length(a)))
}

sortperf = function(n) {
    v = runif(n)
    return(qsort(v))
}

sortperf(5000)


10
За бога ... запроси програмістів R програмувати R.
Джон

24
(1) Ось приклад фібоначі в R johnmyleswhite.com/notebook/2012/03/31/julia-i-love-you, і, схоже, вони використовують це, щоб зробити висновок, що Джулія була швидшою, але перевіряючи мої коментарі під постом блогу Я зміг переписати рішення R (все ще лише з чистим R) і змусив його працювати швидше в 2000 разів. (2) Багато можна отримати, щоб запустити 3x-4x швидше в R шляхом компіляції байтів, і для цього навіть не потрібно змінювати код. (3) Багато прикладів складені проти R з самого початку, оскільки вони використовують рекурсію, в якій R поганий. Включення проблем у мікс, які легко векторизуються, було б справедливішим.
Г. Гротендік,

7
@ G.Grothendieck Ви повинні розмістити свій коментар як відповідь Gabor; безліч відповідних пунктів. +1
Гевін Сімпсон,

2
Може бути цікавим побачити, як усі ці порівняльні показники поширюються і на pqR Редфорда Ніла.
Spacedman

Відповіді:


44

Хм, у прикладі Мандельброта матриця M має розміщені розміщення

M = matrix(0.0,nrow=length(im), ncol=length(re))

тому що він заповнюється збільшенням countвнутрішнього циклу (послідовні значення im). Моя реалізація створює вектор комплексних чисел у mandelperf.1всіх елементах та оперує ними, використовуючи індекс та підмножину, щоб відстежувати, які елементи вектора ще не виконали умовуMod(z) <= 2

mandel.1 = function(z, maxiter=80L) {
    c <- z
    result <- integer(length(z))
    i <- seq_along(z)
    n <- 0L
    while (n < maxiter && length(z)) {
        j <- Mod(z) <= 2
        if (!all(j)) {
            result[i[!j]] <- n
            i <- i[j]
            z <- z[j]
            c <- c[j]
        }
        z <- z^2 + c
        n <- n + 1L
    }
    result[i] <- maxiter
    result
}

mandelperf.1 = function() {
    re = seq(-2,0.5,.1)
    im = seq(-1,1,.1)
    mandel.1(complex(real=rep(re, each=length(im)),
                     imaginary=im))
}

для 13-кратного прискорення (результати рівні, але не ідентичні, оскільки оригінал повертає числові, а не цілі значення).

> library(rbenchmark)
> benchmark(mandelperf(), mandelperf.1(),
+           columns=c("test", "elapsed", "relative"),
+           order="relative")
            test elapsed relative
2 mandelperf.1()   0.412  1.00000
1   mandelperf()   5.705 13.84709

> all.equal(sum(mandelperf()), sum(mandelperf.1()))
[1] TRUE

Приклад швидкої сортування насправді не сортує

> set.seed(123L); qsort(sample(5))
[1] 2 4 1 3 5

але головним моїм прискоренням було векторизувати розділ навколо стовпа

qsort_kernel.1 = function(a) {
    if (length(a) < 2L)
        return(a)
    pivot <- a[floor(length(a) / 2)]
    c(qsort_kernel.1(a[a < pivot]), a[a == pivot], qsort_kernel.1(a[a > pivot]))
}

qsort.1 = function(a) {
    qsort_kernel.1(a)
}

sortperf.1 = function(n) {
    v = runif(n)
    return(qsort.1(v))
}

для 7-кратного прискорення (у порівнянні з невідправленим оригіналом)

> benchmark(sortperf(5000), sortperf.1(5000),
+           columns=c("test", "elapsed", "relative"),
+           order="relative")
              test elapsed relative
2 sortperf.1(5000)    6.60 1.000000
1   sortperf(5000)   47.73 7.231818

Оскільки в оригінальному порівнянні Джулія приблизно в 30 разів швидша, ніж R для миндалини, і в 500 разів швидша для швидкого сортування, наведені вище реалізації все ще не є конкурентоспроможними.


компіляція байтів @ gsk3 насправді не змінює час виконання мандели; sortperf.1 стає приблизно в 10 разів замість 7 разів швидшим, ніж sortperf.
Мартін Морган,

Проблема в тому, що qsort_kernel.1все ще робиться рекурсія, і R не добре в цьому. Щоб він швидше працював, перетворіть його в цикли за допомогою стека.
Г. Гротендік,

@ G.Grothendieck рекурсія не така глибока (журнал N), ітераційні рішення, швидше за все, використовують стек (також не підходить для R), різниця швидкості все ще велика, а алгоритм менш інтуїтивний.
Мартін Морган,

1
Використання хорошої обчислювальної математичної бібліотеки на R, як Intel MKL, також допомагає зменшити розрив між R та Julia.
aatrujillob

Сенс порівняння продуктивності полягає не в тому, щоб зрозуміти, наскільки швидко вирішення проблеми може бути отримано різними мовами, а в порівнянні того, як одна і та ж реалізація (тобто алгоритм) працює на різних мовах.
bdeonovic

101

Ключовим словом у цьому питанні є "алгоритм":

Яку найшвидшу продуктивність ви можете отримати з двох наведених нижче алгоритмів (бажано з поясненням того, що ви змінили, щоб зробити його більш схожим на R)?

Як і в "як швидко ви можете зробити ці алгоритми в R?" Розглянутими тут алгоритмами є стандартний алгоритм ітерації складного циклу Мандельброта та стандартне ядро ​​рекурсивного швидкого сортування.

Безумовно, існують більш швидкі способи обчислення відповідей на проблеми, поставлені в цих тестах, але не за допомогою тих самих алгоритмів. Ви можете уникнути рекурсії, уникати ітерацій і уникати того, у чому ще не вдається R. Але тоді ви більше не порівнюєте однакові алгоритми.

Якщо ви дійсно хотіли обчислити набори Мандельброта в R або сортувати числа, так, це не так, як ви писали б код. Ви б або векторизували його якомога більше - тим самим штовхаючи всю роботу в заздалегідь визначені ядра C - або просто написали власне розширення C і зробили там обчислення. У будь-якому випадку, висновок полягає в тому, що R недостатньо швидкий, щоб отримати справді хороші показники самостійно - вам потрібно, щоб C зробив більшу частину роботи, щоб отримати хороші показники.

І саме в цьому полягає сенс цих тестів: у Джулії ніколи не доводиться покладатися на код С, щоб отримати хорошу продуктивність. Ви можете просто написати, що ви хочете зробити, чистою Джулією, і це матиме хороші результати. Якщо ітеративний алгоритм скалярного циклу є найбільш природним способом зробити те, що ви хочете зробити, то просто зробіть це. Якщо рекурсія є найбільш природним способом вирішення проблеми, то це теж нормально. Ви жодного разу не будете змушені покладатися на C для продуктивності - будь то за допомогою неприродної векторизації або написання власних розширень C. Звичайно, ви можете писати векторизований код, коли це природно, як це часто буває в лінійній алгебрі; і ви можете зателефонувати C, якщо у вас вже є бібліотека, яка робить те, що ви хочете. Але не потрібно.

Ми хочемо мати максимально справедливе порівняння одних і тих же алгоритмів на різних мовах:

  1. Якщо хтось має швидші версії на R, які використовують той самий алгоритм , надішліть виправлення!
  2. Я вважаю, що тести R на сайті julia вже складені за байтами, але якщо я роблю це неправильно і порівняння несправедливе щодо R, будь ласка, дайте мені знати, і я виправлю це та оновлюю тести.

29
@Stefan Хороші бали. Однак протилежним моментом є те, що коли ви можете отримати прискорення від декількох сотень до декількох тисяч разів, просто написавши код природним для мови способом, приклад коду - це просто поганий код R. Якби хтось звернувся до SO та опублікував ці приклади кодів, їм дуже швидко дали б уроки, як писати R, як R, як передбачається писати. Враховуючи, що всі приклади, схоже, відібрані з урахуванням рекурсії (на яку R, імовірно, поганий), а потім зразок коду стає непотрібним, щоб уникнути векторизації (що R дуже добре) ....
Арі Б. Friedman

39
Я б все одно стверджував, що в коді тесту немає нічого поганого. Якби була якась реалізація R, в якій ітерація та рекурсія були швидкими, чи був би код все ще поганим? Єдиний висновок, який я можу зробити, це те, що помилкою є реалізація мови, а не код. Якщо, крім того, дизайн мови R робить особливо складним (або, можливо, неможливим?) Зробити швидкість ітерації та рекурсії, то я б сказав, що це не помилка ні в цьому конкретному коді, ні в поточній реалізації R, а скоріше в сам дизайн мови.
StefanKarpinski

22
@Stefan Я згоден, що використання того самого алгоритму - це справедливе порівняння, але, можливо, варто було б переписати оптимізовані Джулією версії цих алгоритмів однакові, або наближені до тих самих, що можуть бути, як і будь-який з оптимізованих R алгоритми, які з’являються (тобто сильно векторизуються) і переоцінюють як відносну продуктивність, так і продуктивність порівняно з оригінальними реалізаціями тестового коду в Джулії? Зрештою, якщо я можу досягти того самого кінцевого результату, використовуючи інший код, оптимізований для іншої мови, який швидший, тоді я, ймовірно, скористаюся швидшим кодом. Слідую з великим інтересом!
Саймон О'Хенлон

12
Якби можна було написати пакет, який швидко робить рекурсію або ітерацію в R, хіба б хтось цього не зробив до цього часу? Подібні фундаментальні вдосконалення мови - це не те, з чим ви можете «боротися» - вони вимагають глибоких, складних змін. Це не означає, що рекурсія та ітерація не можуть бути зроблені швидше в R, але це не буде "просто пакет".
StefanKarpinski

29
@VB: Все залежить від того, що ви намагаєтеся виміряти. Мене особисто зовсім не цікавить, наскільки швидко можна обчислити числа Фібоначчі. Однак це один із наших орієнтирів. Чому? Оскільки мене дуже цікавить, наскільки добре мови підтримують рекурсію - і подвійно рекурсивний алгоритм є чудовим випробуванням рекурсії, саме тому, що це такий жахливий спосіб обчислення чисел Фібоначчі. Отже, чому можна навчитися, порівнюючи навмисно повільний, надмірно рекурсивний алгоритм у C та Julia та хитрий, розумний, векторизований алгоритм у R? Нічого взагалі.
StefanKarpinski
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.