Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should I avoid unintentionally capturing the local scope in function literals?

I'll ask this with a Scala example, but it may well be that this affects other languages which allow hybrid imperative and functional styles.

Here's a short example (UPDATED, see below):

def method: Iterator[Int] {
    // construct some large intermediate value
    val huge = (1 to 1000000).toList        
    val small = List.fill(5)(scala.util.Random.nextInt)
    // accidentally use huge in a literal
    small.iterator filterNot ( huge contains _ )    
}

Now iterator.filterNot works lazily, which is great! As a result, we'd expect that the returned iterator won't consume much memory (indeed, O(1)). Sadly, however, we've made a terrible mistake: since filterNot is lazy, it keeps a reference to the function literal huge contains _.

Thus while we thought that the method would require a large amount of memory while it was running, and that that memory could be freed up immediately after the termination of the method, in fact that memory is stuck until we forget the returned Iterator.

(I just made such a mistake, which took a long time to track down! You can catch such things looking at heap dumps ...)

What are best practices for avoiding this problem?

It seems that the only solution is to carefully check for function literals which survive the end of the scope, and which captured intermediate variables. This is a bit awkward if you're constructing a non-strict collection and planning on returning it. Can anyone think of some nice tricks, Scala-specific or otherwise, that avoid this problem and let me write nice code?

UPDATE: the example I'd given previously was stupid, as huynhjl's answer below demonstrates. It had been:

def method: Iterator[Int] {
    val huge = (1 to 1000000).toList // construct some large intermediate value
    val n = huge.last                // do some calculation based on it
    (1 to n).iterator map (_ + 1)    // return some small value 
}

In fact, now that I understand a bit better how these things work, I'm not so worried!

like image 200
Scott Morrison Avatar asked Oct 18 '10 04:10

Scott Morrison


People also ask

What happens to variables in a local scope when the function call returns?

When the execution of the function terminates (returns), the local variables are destroyed.

What is the scope of local variables * Within the function in which variables declared in main function in all user defined functions outside the main function?

A scope is a region of the program and broadly speaking there are three places, where variables can be declared: Inside a function or a block which is called local variables, In the definition of function parameters which is called formal parameters. Outside of all functions which is called global variables.

How many local scopes are there in a Python program?

At any given time during execution, you'll have at most four active Python scopes—local, enclosing, global, and built-in—depending on where you are in the code. On the other hand, you'll always have at least two active scopes, which are the global and built-in scopes.

How is scope managed in Python?

A variable created in the main body of the Python code is a global variable and belongs to the global scope. Global variables are available from within any scope, global and local.


1 Answers

Are you sure you're not oversimplifying the test case? Here is what I run:

object Clos {
  def method: Iterator[Int] = {
    val huge = (1 to 2000000).toList
    val n = huge.last
    (1 to n).iterator map (_ + 1)
  }

  def gc() { println("GC!!"); Runtime.getRuntime.gc }

  def main(args:Array[String]) {
    val list = List(method, method, method)
    list.foreach(m => println(m.next))
    gc()
    list.foreach(m => println(m.next))
    list.foreach(m => println(m.next))
  }
}

If I understand you correctly, because main is using the iterators even after the gc() call, the JVM would be holding onto the huge objects.

This is how I run it:

JAVA_OPTS="-verbose:gc" scala -cp classes Clos

This is what it prints towards the end:

[Full GC 57077K->57077K(60916K), 0.3340941 secs]
[Full GC 60852K->60851K(65088K), 0.3653304 secs]
2
2
2
GC!!
[Full GC 62959K->247K(65088K), 0.0610994 secs]
3
3
3
4
4
4

So it looks to me as if the huge objects were reclaimed...

like image 97
huynhjl Avatar answered Oct 11 '22 08:10

huynhjl