Skip to content
This repository was archived by the owner on Sep 1, 2020. It is now read-only.

Offer optimistic locking for lazy vals #10

Closed
puffnfresh opened this issue Sep 4, 2014 · 13 comments
Closed

Offer optimistic locking for lazy vals #10

puffnfresh opened this issue Sep 4, 2014 · 13 comments
Assignees
Milestone

Comments

@puffnfresh
Copy link

Lazy vals use double-locking to ensure they are evaluated and evaluated only once. This imposes a small cost on every subsequent access, so there is a cumulative performance gain to be had. We should provide the option of avoiding the second lock, at the risk of incurring multiple evaluations.

I think the current behavior (correctness/predictability) should remain the default, and single-locking (performance) should be an opt-in, using an annotation, for example something like:

@optimistic lazy val x = { /* evaluate */ }

Any thoughts?

@jdegoes
Copy link

jdegoes commented Sep 4, 2014

What about a @pure annotation which would be more broadly applicable? Compiler has a lot more optimization options if it knows something is pure -- optimistic locking is just one example.

@puffnfresh
Copy link
Author

@jdegoes that's a sensible idea.

@paulp
Copy link

paulp commented Sep 5, 2014

The real winner with lazy vals would be to rescue the 90+% of lazy vals which are not intended to be lazy from paying synchronization costs on every access until the end of time. It's pretty nuts if you think about it.

Very few of the lazy vals I have seen in the wild (and I have seen a lot of them) are lazy because it's relevant that the evaluation is delayed. They are lazy to avoid NPEs and cycles. The two major brands of problem which you would encounter if there were no lazy vals are inter-file (mixin composition) and intra-file, as in e.g. Definitions.scala, any nontrivial parser combinator, etc etc.

The first one is probably hard, but the second one isn't. The compiler can do the dependency analysis of what needs to come before what and then just order them. All you need is a way for the user to indicate it.

@propensive
Copy link

@paulp Good point, though is the dependency analysis genuinely easy? What if a val calls a virtual method whose implementation isn't known at compile time, and refers to something else in the dependency graph?

@paulp
Copy link

paulp commented Sep 5, 2014

@propensive It's easy because it's impossible for the compiler to do anything with that, so you won't be surprised to find the burden is on you. It's the kind of thing you'd opt into and which would come with warning labels (and it could tell you useful things like the set of unknown method implementations to which you are vulnerable.)

@propensive
Copy link

@paulp Seems reasonable. So we'd replace all the lazy vals with @reorderable vals (or something like that), and the compiler would give us a load of warnings whenever we refer to something it can't analyze? Apart from my unverified suspicion that you just wouldn't be able to do anything useful most of the time with such a constraint, I'm very much in favor of it as a solution.

@propensive
Copy link

@jdegoes @puffnfresh I'd love a @pure annotation too if we could actually make use of it. But purity analysis is nontrivial (though could be framed as a dependent-typing problem, maybe?) and I'm tempted to believe it wouldn't be useful most of the time. Nevertheless, I really think it should be explored.

@jdegoes
Copy link

jdegoes commented Sep 5, 2014

@propensive A @pure annotation wouldn't be checked, that's too hard, it would just be a promise the value is pure which would enable the compiler to perform optimistic locking on lazy vals, memoization on def'd vals, duplicate expression elimination, etc.

@paulp
Copy link

paulp commented Sep 5, 2014

In that case I already implemented it: scala#2416

aloiscochard pushed a commit to aloiscochard/scala that referenced this issue Sep 5, 2014
@propensive
Copy link

@jdegoes I wouldn't want to rule out the possibility of a checked @pure annotation until I'd tried it. Purity guarantees would be so valuable that it could be worth the effort and the risk of failure.

@propensive
Copy link

See @odersky's comments on plans for (@volatile) lazy vals in Scala Don Giovanni on #20

@aryairani
Copy link

+1 (are +1's helpful? I know they're not as helpful as pull requests...)

milessabin referenced this issue in milessabin/scala Apr 15, 2016
Rather than in implementation of the abstract method in the
expanded anonymous class.

This leads to more more efficient use of the constant pool,
code shapes more amenable to SAM inlining, and is compatible
with the old behaviour of `-Xexperimental` in Scala 2.11,
which ScalaJS now relies upon.

Manual test:

```
scala> :paste -raw
// Entering paste mode (ctrl-D to finish)

package p1; trait T { val x = 0; def apply(): Any }; class DelambdafyInline { def t: T = (() => "") }

// Exiting paste mode, now interpreting.

scala> :javap -c p1.DelambdafyInline
Compiled from "<pastie>"
public class p1.DelambdafyInline {
  public p1.T t();
    Code:
       0: new           scala#10                 // class p1/DelambdafyInline$$anonfun$t$1
       3: dup
       4: aload_0
       5: invokespecial scala#16                 // Method p1/DelambdafyInline$$anonfun$t$1."<init>":(Lp1/DelambdafyInline;)V
       8: areturn

  public final java.lang.Object p1$DelambdafyInline$$$anonfun$1();
    Code:
       0: ldc           scala#22                 // String
       2: areturn

  public p1.DelambdafyInline();
    Code:
       0: aload_0
       1: invokespecial scala#25                 // Method java/lang/Object."<init>":()V
       4: return
}

scala> :javap -c p1.DelambdafyInline$$anonfun$t$1
Compiled from "<pastie>"
public final class p1.DelambdafyInline$$anonfun$t$1 implements p1.T,scala.Serializable {
  public static final long serialVersionUID;

  public int x();
    Code:
       0: aload_0
       1: getfield      scala#25                 // Field x:I
       4: ireturn

  public void p1$T$_setter_$x_$eq(int);
    Code:
       0: aload_0
       1: iload_1
       2: putfield      scala#25                 // Field x:I
       5: return

  public final java.lang.Object apply();
    Code:
       0: aload_0
       1: getfield      scala#34                 // Field $outer:Lp1/DelambdafyInline;
       4: invokevirtual scala#37                 // Method p1/DelambdafyInline.p1$DelambdafyInline$$$anonfun$1:()Ljava/lang/Object;
       7: areturn

  public p1.DelambdafyInline$$anonfun$t$1(p1.DelambdafyInline);
    Code:
       0: aload_1
       1: ifnonnull     6
       4: aconst_null
       5: athrow
       6: aload_0
       7: aload_1
       8: putfield      scala#34                 // Field $outer:Lp1/DelambdafyInline;
      11: aload_0
      12: invokespecial scala#42                 // Method java/lang/Object."<init>":()V
      15: aload_0
      16: invokespecial scala#45                 // Method p1/T.$init$:()V
      19: return
}

scala> :quit
```

Adriaan is to `git blame` for `reflection-mem-typecheck.scala`.
@milessabin
Copy link
Member

To resurrect this issue, please rework it as an issue/PR against Lightbend Scala (ie. scala/scala).

@milessabin milessabin added this to the Parked milestone Aug 12, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

7 participants