加速朱莉娅写得不好的R例子

Ari*_*man 72 optimization r julia

朱莉娅将性能与R进行比较的例子似乎特别令人费解. 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)
Run Code Online (Sandbox Code Playgroud)

Ste*_*ski 100

这个问题的关键词是"算法":

你可以从下面的两种算法中获得最快的性能(最好解释你改变了什么以使它更像R)?

就像"你能用多长时间在R中制作这些算法?" 这里讨论的算法是标准的Mandelbrot复杂循环迭代算法和标准的递归快速排序内核.

肯定有更快的方法来计算这些基准测试中提出的问题的答案 - 但不使用相同的算法.你可以避免递归,避免迭代,并避免R不擅长的任何东西.但是你不再比较相同的算法了.

如果你真的想在R中计算Mandelbrot集或排序数,是的,这不是你编写代码的方式.您可以尽可能地将其向量化 - 从而将所有工作推送到预定义的C内核中 - 或者只编写自定义C扩展并在那里进行计算.无论哪种方式,结论是R不够快,不能自己获得真正好的性能 - 你需要C做大部分工作才能获得良好的性能.

这正是这些基准测试的重点:在Julia中,您永远不必依赖C代码来获得良好的性能.你可以在纯粹的Julia中写下你想要做的事情,它会有很好的表现.如果迭代标量循环算法是您做自己想做的最自然的方式,那么就这样做.如果递归是解决问题的最自然方式,那也没关系.在任何时候你都不会被迫依靠C来获得性能 - 无论是通过不自然的矢量化还是编写自定义C扩展.当然,你可以在它自然的时候编写矢量化代码,因为它通常是线性代数; 如果您已经有一些可以满足您需求的库,那么您可以调用C. 但你没必要.

我们确实想要跨语言对相同算法进行最公平的比较:

  1. 如果某人确实在R中使用相同算法的更快版本,请提交补丁!
  2. 我相信julia网站上的R基准测试已经是字节编译的,但是如果我做错了并且比较对R不公平,请告诉我,我会修复它并更新基准测试.

  • 我仍然认为基准代码没有任何问题.如果有一些R实现,其中迭代和递归很快,代码仍然会很差吗?我能得出的唯一结论是,语言的实现是错误的,而不是代码.此外,如果R语言的设计使得迭代和递归变得特别具有挑战性(或者可能不可能?),那么我会说这既不是这个特定代码的错误,也不是当前的R实现,而是在语言设计本身. (38认同)
  • @Stefan好点.但是,反过来说,只要通过以语言中自然的方式编写代码,只需几百到几千倍的加速,示例代码就是简单的R代码.如果有人来到SO并发布了这些示例代码,他们很快就会得到如何编写R的课程,就像写R一样.鉴于所有示例都被选为涉及递归(其中R确实很差),然后示例代码不再使用它来避免向量化(R非常擅长).... (28认同)
  • @VB:这完全取决于你想要测量的东西.就个人而言,我对计算斐波纳契数的速度并不感兴趣.然而,这是我们的基准之一.为什么?因为我对语言支持递归的方式非常感兴趣 - 而双重递归算法恰好是递归的一个很好的测试,正是因为它是计算斐波纳契数的一种可怕的方法.那么通过比较C和Julia中的故意缓慢,过度递归的算法与R中棘手,聪明的矢量化算法可以学到什么?什么都没有. (27认同)
  • @Stefan我同意使用_same算法_是一个公平的比较,但也许值得重新编写这些算法的Julia优化版本相同或尽可能接近相同,任何R优化出现的算法(即高度矢量化)并重新评估Julia中基准代码的原始实现的相对性能和性能?最终,如果我可以使用针对另一种语言优化的不同代码来实现相同的最终结果,那么我可能会使用更快的代码.我非常感兴趣地关注! (21认同)
  • 如果有可能编写一个在R中快速递归或迭代的包,那么现在还没有人做过吗?像这样的基本语言改进不是你可以"坚持"的东西 - 它们需要深刻而艰难的改变.这并不意味着递归和迭代在R中不能更快,但它不会"只是一个包". (11认同)
  • 从一个共同的智慧和逻辑的角度来看,如果你需要达到X,那么如果结果是相同的,你选择一种语言中最好的东西.因此,这种对"同一算法逐行"的纯粹坚持只是反映了谁回应的利益冲突.这不是问题的答案,BTW! (2认同)

Mar*_*gan 42

嗯,在Mandelbrot例子中,矩阵M的尺寸被转置

M = matrix(0.0,nrow=length(im), ncol=length(re))
Run Code Online (Sandbox Code Playgroud)

因为它是通过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))
}
Run Code Online (Sandbox Code Playgroud)

加速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
Run Code Online (Sandbox Code Playgroud)

快速排序示例实际上没有排序

> set.seed(123L); qsort(sample(5))
[1] 2 4 1 3 5
Run Code Online (Sandbox Code Playgroud)

但我的主要加速是围绕枢轴矢量化分区

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))
}
Run Code Online (Sandbox Code Playgroud)

加速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
Run Code Online (Sandbox Code Playgroud)

因为在最初的比较中,Julia比R和Mandel快30倍,而快速排序快500倍,上面的实现仍然没有真正的竞争力.