Passing Scala Map as an argument to a function takes too much time

10,396

Solution 1

It's being passed by reference. Something else is going on--you're measuring the first time you use the map, which requires some class loading also (subsequent calls would be much faster), or you're doing lots more work when you pass a map as opposed to null, or you are very nearly out of memory and you're measuring garbage collection time instead.

It could be copied if there was an implicit conversion in scope, but if the type signature is exactly the same in both places, that wouldn't be an issue since "no conversion" always has priority.

Here is the bytecode for the parse call (with a content method added to Asset so it produces an Option[ContentElement], and a sub method added to Node to fill in for subNode):

def parse(node: Node, assets: Map[String, Asset]): Option[ContentElement] =
      AssetParser.getAsset(node.sub, assets).content


public scala.Option parse(Node, scala.collection.immutable.Map);
  Code:
   0:  getstatic     #19; //Field AssetParser$.MODULE$:LAssetParser$;
   3:  aload_1
   4:  invokevirtual #25; //Method Node.sub:()LNode;
   7:  aload_2
   8:  invokevirtual #29; //Method AssetParser$.getAsset:
                            (LNode;Lscala/collection/immutable/Map;)LAsset;
   11: invokevirtual #35; //Method Asset.content:()Lscala/Some;
   14: areturn

See? No map copying. aload_2 is the map that's passed in. Nothing happens to it except that it's passed on to getAsset via invokevirtual.

Solution 2

So I tried to reproduce this in a standalone project and failed, it works fine. But in a project it was slow so apparently something else was going on.

I ended up refactoring the code and getting rid of object to object calls. Created a usual class which accepts assetsMap as a constructor val and now it works much faster

Share:
10,396
makados
Author by

makados

Updated on June 05, 2022

Comments

  • makados
    makados almost 2 years

    I have a scala companion object with a method which accepts a map as a parameter. Then in passes this map to another function in a different companion object with no changes. And the actual method call takes too much time when method execution is fast (I measured everything). If I don't pass a map (use null instead) it works fast, but with passing it as an argument, actual method call is very slow.

    Am I missing something, and Map is being recreated and not just a reference is passed?

    object ContentElementParser {
        def parse(node: Node, assets: Map[String, Asset]): Option[ContentElement] = {
            //Some logic here
            AssetParser.getAsset(subNode, assets) //this call is too slow because of assets map
        }
    }
    
    object AssetParser {
        def getAsset(node: Node, assetMap: Map[String, Asset]): Asset = {
            //logic
        }
    }
    
  • makados
    makados almost 11 years
    assets map is created in a different place, and all of it is executed inside of a for loop. The very first call is longer - 2.5 sec, when all subsequend are around 1.3 seconds. getAsset method body is executed in a millisecond.
  • Rex Kerr
    Rex Kerr almost 11 years
    @user803762 - I've added th suggestion of it actually being garbage collection. (Try -verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails on the command line, maybe?)
  • som-snytt
    som-snytt almost 11 years
    @user803762 by "type signature is exactly the same", do we mean immutable.Map in both methods? Also suspicious that your huge timings differ by factor of 2.
  • makados
    makados almost 11 years
    type signatures are exactly the same - originally it was collection.Map trait, but looking for ways out of it, I changed it to immutable Map what changed nothing.
  • Rex Kerr
    Rex Kerr almost 11 years
    @user803762 - Post a minimized example and then maybe you can get some help. Right now you've posted a sketch of something that should work fine and are searching for problems within it when none exist. It's somewhere else. Since you don't show the "else", it's pretty hard to help find the issue.
  • Rex Kerr
    Rex Kerr almost 11 years
    @user803762 - That you avoid object to object calls is not why it works much faster, but at least the refactor got rid of something else that was actually the problem.