8000 Substantial slowdown in groupBy (all collections) · Issue #10049 · scala/bug · GitHub
[go: up one dir, main page]

Skip to content
Substantial slowdown in groupBy (all collections) #10049
@scabug

Description

@scabug

groupBy is substantially slower in 2.12, with throughput down to as little as 60% of 2.11 on my machine. This seems to affect all collections (Vector, List, and Array tested).

Manually inlining the groupBy code does not seem to help. Indeed, benchmarking of HashMap.getOrElseUpdate shows a considerable decrease in throughput, possibly enough to account for the entire effect. AnyRefMap does not appear to be affected.

Manually inlining the get, match, and update calls from getOrElseUpdate does appear to solve the issue (at least mostly).

The old bytecode in scala.collection.mutable.MapLike$class was:

  public static java.lang.Object getOrElseUpdate(scala.collection.mutable.MapLike, java.lang.Object, scala.Function0);
    Code:
       0: aload_0
       1: aload_1
       2: invokeinterface #35,  2           // InterfaceMethod scala/collection/mutable/MapLike.get:(Ljava/lang/Object;)Lscala/Option;
       7: astore_3
       8: aload_3
       9: instanceof    #119                // class scala/Some
      12: ifeq          31
      15: aload_3
      16: checkcast     #119                // class scala/Some
      19: astore        4
      21: aload         4
      23: invokevirtual #123                // Method scala/Some.x:()Ljava/lang/Object;
      26: astore        5
      28: goto          62
      31: getstatic     #128                // Field scala/None$.MODULE$:Lscala/None$;
      34: aload_3
      35: invokevirtual #132                // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
      38: ifeq          65
      41: aload_2
      42: invokeinterface #137,  1          // InterfaceMethod scala/Function0.apply:()Ljava/lang/Object;
      47: astore        6
      49: aload_0
      50: aload_1
      51: aload         6
      53: invokeinterface #39,  3           // InterfaceMethod scala/collection/mutable/MapLike.update:(Ljava/lang/Object;Ljava/lang/Object;)V
      58: aload         6
      60: astore        5
      62: aload         5
      64: areturn
      65: new           #139                // class scala/MatchError
      68: dup
      69: aload_3
      70: invokespecial #142                // Method scala/MatchError."<init>":(Ljava/lang/Object;)V
      73: athrow

Now, however, the bytecode is as follows, with extra instructions highlighted with >>>>>:

  public V getOrElseUpdate(K, scala.Function0<V>);
    Code:
       0: aload_0
       1: aload_1
       2: invokeinterface #109,  2          // InterfaceMethod get:(Ljava/lang/Object;)Lscala/Option;
       7: astore        4
       9: aload         4
      11: instanceof    #221                // class scala/Some
      14: ifeq          37
      17: aload         4
      19: checkcast     #221                // class scala/Some
      22: astore        5
      24: aload         5
      26: invokevirtual #224                // Method scala/Some.value:()Ljava/lang/Object;
      29: astore        6
>>>>> 31: aload         6
>>>>> 33: astore_3
      34: goto          87
>>>>> 37: goto          40
      40: getstatic     #229                // Field scala/None$.MODULE$:Lscala/None$;
      43: aload         4
      45: invokevirtual #233                // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
      48: ifeq          74
      51: aload_2
      52: invokeinterface #237,  1          // InterfaceMethod scala/Function0.apply:()Ljava/lang/Object;
      57: astore        7
      59: aload_0
      60: aload_1
      61: aload         7
      63: invokeinterface #113,  3          // InterfaceMethod update:(Ljava/lang/Object;Ljava/lang/Object;)V
      68: aload         7
      70: astore_3
>
64AC
>>>> 71: goto          87
>>>>> 74: goto          77
      77: new           #239                // class scala/MatchError
      80: dup
      81: aload         4
      83: invokespecial #242                // Method scala/MatchError."<init>":(Ljava/lang/Object;)V
      86: athrow
      87: aload_3
      88: areturn

Whether these extra jumps themselves are the extra cost, or whether they prevent some JIT optimization, I have not yet investigated. However, the bytecode appears less efficiently organized (note the swap of the exception vs return which requires one of the extra jumps in the success case).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0