Why are Scala circuit loops so slow compared to FOR loops?

It was said that Scala To understand is actually quite slow. The reason why I was pointed out was because of the Java restriction for understanding (for example, “reduce” used below) it is necessary to create a temporary object with each iteration in order to call the passed function.

... it is truth? The tests below seem to confirm this, but I don’t quite understand why this is.

This may make sense for "lambdas" or anonymous functions, but not for non-anonymous functions.

In my test, I ran for loops against list.reduce (see code below) and found that they were twice as fast, even when each iteration called the same function that was passed to reduce!

I find this surprisingly counterintuitive (one day I would have thought that the Scala library would have been carefully created to be as optimal as possible).

In the test that I put together, I started the same loop (sum the numbers from 1 to a million, regardless of overflow) in five different ways:

  • for a loop over an array of values
  • for a loop, but a function call instead of the built-in arithmetic
  • for the loop by creating an object containing the add function
  • list.reduce by passing an anonymous function
  • list.reduce, passing in the function of the object-object

The results were as follows: test: min / max / average (milliseconds)

1. 27/157/64.78 2. 27/192/65.77 <--- note the similarity between tests 1,2 and 4,5 3. 139/313/202.58 4. 63/342/150.18 5. 63/341/149.99 

As you can see, the “for understanding” versions have the order “for new for each instance”, implying that the “new” can actually be performed for both anonymous and anonymous versions of functions.

Methodology: The code below (test call deleted) was compiled into a single .jar file to ensure that all versions have the same library code. Each test at each iteration was called in a new JVM (i.e. Scala -cp ... for each test) to remove heap size problems.

 class t(val i: Int) { def summit(j: Int) = j + i } object bar { val biglist:List[Int] = (1 to 1000000).toList def summit(i: Int, j:Int) = i+j // Simple for loop def forloop: Int = { var result: Int = 0 for(i <- biglist) { result += i } result } // For loop with a function instead of inline math def forloop2: Int = { var result: Int = 0 for(i <- biglist) { result = summit(result,i) } result } // for loop with a generated object PER iteration def forloop3: Int = { var result: Int = 0 for(i <- biglist) { val t = new t(result) result = t.summit(i) } result } // list.reduce with an anonymous function passed in def anonymousfunc: Int = { biglist.reduce((i,j) => {i+j}) } // list.reduce with a named function def realfunc: Int = { biglist.reduce(summit) } // test calling code excised for brevity. One example given: args(0) match { case "1" => { val start = System.currentTimeMillis() forloop val end = System.currentTimeMillis() println("for="+(end - start)) } ... } 
+6
source share
1 answer

What you were told was true about “for understanding,” but the problem with your question is that you have mixed up “for understanding” with “anonymous functions”.

Scala's "Understanding" is the syntactic sugar for the .flatMap , .map and .filter series of applications. Since you are testing reduction algorithms, and since it is not possible to implement a reduction algorithm using these three functions, your test cases are incorrect.

Here is an “for understanding” example:

 val listOfLists = List(List(1,2), List(3,4), List(5)) val result = for { itemOfListOfLists <- listOfLists itemOfItemOfListOfLists <- itemOfListOfLists } yield (itemOfItemOfListOfLists + 1) assert( result == List(2,3,4,5,6) ) 

The compiler divides the Understanding part into the following:

 val result = listOfLists.flatMap( itemOfListOfLists => itemOfListOfLists.map( itemOfItemOfListOfLists => itemOfItemOfListOfLists + 1 ) ) 

Then it disables the syntax of the anonymous function:

 val result = listOfLists.flatMap( new Function1[List[Int], List[Int]] { override def apply(itemOfListOfLists: List[Int]): List[Int] = itemOfListOfLists.map( new Function1[Int, Int] { override def apply(itemOfItemOfListOfLists: Int): Int = itemOfItemOfListOfLists + 1 } ) } ) 

From the desugarred code, it’s now obvious that the Function1[Int, Int] class gets an instance every time the apply(itemOfListOfLists: List[Int]): List[Int] method is called. This happens for every listOfLists . Thus, the more complex your understanding, the more instances of Function objects you get.

+12
source

Source: https://habr.com/ru/post/945987/


All Articles