Is Scala functional programming slower than traditional coding?

前端 未结 9 2089
离开以前
离开以前 2020-12-22 22:23

In one of my first attempts to create functional code, I ran into a performance issue.

I started with a common task - multiply the elements of two arrays and sum up

相关标签:
9条回答
  • 2020-12-22 23:07

    To answer the question in the title: Simple functional constructs may be slower than imperative on the JVM.

    But, if we consider only simple constructs, then we might as well throw out all modern languages and stick with C or assembler. If you look a the programming language shootout, C always wins.

    So why choose a modern language? Because it lets you express a cleaner design. Cleaner design leads to performance gains in the overall operation of the application. Even if some low-level methods can be slower. One of my favorite examples is the performance of BuildR vs. Maven. BuildR is written in Ruby, an interpreted, slow, language. Maven is written in Java. A build in BuildR is twice as fast as Maven. This is due mostly to the design of BuildR which is lightweight compared with that of Maven.

    0 讨论(0)
  • 2020-12-22 23:08

    Here is dbyrnes solution with arrays (assuming Arrays are to be used) and just iterating over the index:

    def multiplyAndSum (l1: Array[Int], l2: Array[Int]) : Int = 
    {
        def productSum (idx: Int, sum: Int) : Int = 
            if (idx < l1.length)
                productSum (idx + 1, sum + (l1(idx) * l2(idx))) else 
                    sum
        if (l2.length == l1.length) 
            productSum (0, 0) else 
        error ("lengths don't fit " + l1.length + " != " + l2.length) 
    }
    
    
    val first = (1 to 500).map (_ * 1.1) toArray                                                
    val second = (11 to 510).map (_ * 1.2) toArray     
    def loopi (n: Int) = (1 to n).foreach (dummy => multiplyAndSum (first, second))
    println (timed (loopi (100*1000)))
    

    That needs about 1/40 of the time of the list-approach. I don't have 2.8 installed, so you have to test @tailrec yourself. :)

    0 讨论(0)
  • 2020-12-22 23:14

    I did some variations of this with Scala 2.8. The loop version is as you write but the functional version is slightly different:

    (xs, ys).zipped map (_ * _) reduceLeft(_ + _)
    

    I ran with Double instead of Float, because currently specialization only kicks in for Double. I then tested with arrays and vectors as the carrier type. Furthermore, I tested Boxed variants which work on java.lang.Double's instead of primitive Doubles to measure the effect of primitive type boxing and unboxing. Here is what I got (running Java 1.6_10 server VM, Scala 2.8 RC1, 5 runs per test).

    loopArray               461             437             436             437             435
    reduceArray             6573            6544            6718            6828            6554
    loopVector              5877            5773            5775            5791            5657
    reduceVector            5064            4880            4844            4828            4926
    
    loopArrayBoxed          2627            2551            2569            2537            2546
    reduceArrayBoxed        4809            4434            4496            4434            4365
    loopVectorBoxed         7577            7450            7456            7463            7432
    reduceVectorBoxed       5116            4903            5006            4957            5122
    

    The first thing to notice is that by far the biggest difference is between primitive array loops and primitive array functional reduce. It's about a factor of 15 instead of the 40 you have seen, which reflects improvements in Scala 2.8 over 2.7. Still, primitive array loops are the fastest of all tests whereas primitive array reduces are the slowest. The reason is that primitive Java arrays and generic operations are just not a very good fit. Accessing elements of primitive Java arrays from generic functions requires a lot of boxing/unboxing and sometimes even requires reflection. Future versions of Scala will specialize the Array class and then we should see some improvement. But right now that's what it is.

    If you go from arrays to vectors, you notice several things. First, the reduce version is now faster than the imperative loop! This is because vector reduce can make use of efficient bulk operations. Second, vector reduce is faster than array reduce, which illustrates the inherent overhead that arrays of primitive types pose for generic higher-order functions.

    If you eliminate the overhead of boxing/unboxing by working only with boxed java.lang.Double values, the picture changes. Now reduce over arrays is a bit less than 2 times slower than looping, instead of the 15 times difference before. That more closely approximates the inherent overhead of the three loops with intermediate data structures instead of the fused loop of the imperative version. Looping over vectors is now by far the slowest solution, whereas reducing over vectors is a little bit slower than reducing over arrays.

    So the overall answer is: it depends. If you have tight loops over arrays of primitive values, nothing beats an imperative loop. And there's no problem writing the loops because they are neither longer nor less comprehensible than the functional versions. In all other situations, the FP solution looks competitive.

    0 讨论(0)
提交回复
热议问题