8000 Feature/future compat by axel22 · Pull Request #454 · scala/scala · GitHub
[go: up one dir, main page]

Skip to content

Feature/future compat #454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 1, 2012
Merged

Feature/future compat #454

merged 8 commits into from
May 1, 2012

Conversation

axel22
Copy link
Contributor
@axel22 axel22 commented May 1, 2012

Mostly tests for the scala.concurrent package, and a couple of bugfixes.

Aleksandar Prokopec and others added 8 commits April 27, 2012 19:00
Fixed a bug in Future.zip.
Ported most of the future tests.
…anBuildFrom.

Removed the implicit modifier on the OnceCanBuildFrom, as we don't
support implicit classes with zero arguments.

Added an implicit OnceCanBuildFrom method.

The idea behind OnceCanBuildFrom is for it to be used by methods which
construct collections, but are defined outside of collection classes.
OnceCanBuildFrom so far worked only for objects of type TraversableOnce:

    shuffle(List(1, 2, 3).iterator: TraversableOnce[Int])

but this used to result in an implicit resolution error:

    shuffle(List(1, 2, 3).iterator)

because after the type parameter M for `shuffle` was inferred to Iterator, no implicit
of type CanBuildFrom[Iterator[_], A, Iterator[A]] could be found.

Introduced another CanBuildFrom to the Iterator companion object.

Modified Future tests appropriately.
The builder is now instantiated as an iterator builder only
if a generic builder cannot be found on the collection that
requested the builder.

Reason - we want this behaviour:

scala> scala.util.Random.shuffle(List(1, 2, 3): collection.TraversableOnce[Int])
res0: scala.collection.TraversableOnce[Int] = List(3, 1, 2)

instead of this one:

scala> scala.util.Random.shuffle(List(1, 2, 3): collection.TraversableOnce[Int])
res0: scala.collection.TraversableOnce[Int] = non-empty iterator

which may lead to nasty surprises.

Alternately, to avoid pattern-matching in OnceCanBuildFrom.apply, we
could mix in GenericTraversableTemplate-related functionaly into
TraversableOnce, but this may become too complicated.

def defaultReporter: Throwable => Unit = {
// `Error`s are currently wrapped by `resolver`.
// Also, re-throwing `Error`s here causes an exception handling test to fail.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I didn't write that comment, just copied it.

Aleksandar Prokopec
LAMP, IC, EPFL

Sent from my Android phone with K-9 Mail. Please excuse my brevity.

viktorklang reply@reply.github.com wrote:

/** Creates an ExecutionContext from the given Executor.
*/

  • def fromExecutor(e: Executor): ExecutionContext with Executor = new impl.ExecutionContextImpl(e)
  • def fromExecutor(e: Executor, reporter: Throwable => Unit = defaultReporter): ExecutionContext with Executor = new impl.ExecutionContextImpl(e, reporter)
  • def defaultReporter: Throwable => Unit = {
  • // Errors are currently wrapped by resolver.
  • // Also, re-throwing Errors here causes an exception handling test to fail.

Which test?


Reply to this email directly or view it on GitHub:
https://github.com/scala/scala/pull/454/files#r758713

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the offender explain him/herself? ;-)

@paulp paulp merged commit b3ced61 into scala:master May 1, 2012
@adriaanm
Copy link
Contributor
adriaanm commented Mar 6, 2014

@axel22, @viktorklang I'm not sure this is the only issue that keeps us from running on google app engine, but the change from AtomicReferenceFieldUpdater to compareAndSwapObject broke GAE (https://issues.scala-lang.org/browse/SI-8362). Could you explain the motivation (I mean, I assume it's performance, but the meta-point is that it would be good to have that be explicit in the code/docs/commit message, so I can answer bug reports on your behalf ;-))?

@viktorklang
Copy link
Contributor

We use Unsafe for performance and compatibility reasons (performance in the sense that the FieldUpdaters are considerably slower due to their design, this has been frequently discussed on concurrency-interest) and compatibility in the sense that our embedded version of ForkJoinPool is written using Unsafe and switching to FieldUpdaters would not only make updates to the FJP code riskier (the FJP code is extremely hard to maintain, have a look) as well as make it slower, which is detrimental for an execution engine).

If someone has a potential solution with a back-off strategy that tries to use Unsafe but falls back to FieldUpdaters if there is any issues, that doesn't carry any runtime performance penalty, I'm all ears!

Also, since Akka also heavily uses Unsafe it wouldn't work on GAE either.

In any case, have we stated GAE compliance? (In which case we should run automated tests on GAE)
According to GAE, Scala works: https://developers.google.com/appengine/kb/general#language

@adriaanm
Copy link
Contributor
adriaanm commented Mar 6, 2014

We don't promise GAE support, but it would be nice if we could if the cost is low.
My Java is a bit rusty, but how about:

    private final static long _refoffset;
    private final static boolean _useUnsafe;

    static {
      boolean ok = true;
      long ofs = 0;
      try {
        ofs = Unsafe.instance.objectFieldOffset(AbstractPromise.class.getDeclaredField("_ref"));
      } catch (Throwable t) {
        // fallback to AtomicReferenceFieldUpdater if Unsafe is not available, e.g. on GAE (SI)
        ok = false;
      }
      _useUnsafe = ok; // TODO: check _refoffset != sun.misc.Unsafe.INVALID_FIELD_OFFSET
      _refoffset = ofs;
    }

    protected final boolean updateState(Object oldState, Object newState) {
      if (_useUnsafe) return Unsafe.instance.compareAndSwapObject(this, _refoffset, oldState, newState);
      else            return updater.compareAndSet(this, oldState, newState);
    }

@adriaanm
Copy link
Contributor
adriaanm commented Mar 6, 2014

I assume the JIT will quickly get rid of the if on a final static boolean(?).

@viktorklang
Copy link
Contributor

Could work, but it's a lot of ceremony and is error prone (X fields and Y different uses (CAS, getAndSet etc)). Can we work out a solution where we can get to:

private final static IntUpdater _ref = Updater.create(AbstractPromise.class, "_ref");

And make sure that things will get inlined and intrinsified properly, then we're all good.
We'll need to run tests on GAE tho.

@viktorklang
Copy link
Contributor

Would make a ton of sense to create something like this, private to the Scala StdLib.
That would open up the possibility of another idea, to provide language-level atomics in Scala, eliminating the need for Unsafe/Updaters in user code.

something like:

@atomic var _ref: AnyRef = null
_ref.getAndSet("foo")
_ref.compareAndSet("foo", "bar")

@adriaanm
Copy link
Contributor
adriaanm commented Mar 6, 2014

I agree that would be ideal & really cool. I guess I got a little carried away, let's schedule considering a minimal fix for 2.11.1, as it seems it would be binary compatible anyway.

@adriaanm
Copy link
Contributor
adriaanm commented Mar 6, 2014

I'm kind of intrigued by this, so I read the thread you alluded to and tried to encapsulate the fallback to the updater in another class.

It seems that the whole point, though, is better performance, so we don't want to add any indirection. Thus, it seems unwise to encapsulate the CAS behaviour in a new Updater class. Then we might as well use AtomicReferenceFieldUpdater<U,W>.

By the way, AbstractPromise is a bit sloppy: it still has the dead _updater field, so I'm a little less inclined to favor the status quo.

Alternative updater below because I already wrote all this stuff down anyway and I'd feel less guilty about spending time on this if at least there's a record of some sort :-)

object FastAtomicUpdater {
  def apply[T, V](tclass: Class[T], vclass: Class[V], fieldName: String): AtomicReferenceFieldUpdater[T, V] =
    try new AtomicUpdaterUnsafe(Unsafe.instance.objectFieldOffset(tclass.getDeclaredField(fieldName)))
    catch { case _ : Throwable => // ok ok, should be NonFatal
      // fallback to AtomicReferenceFieldUpdater if Unsafe is not available, e.g. on GAE (SI)
      AtomicReferenceFieldUpdater.newUpdater(tclass, vclass, fieldName)
    }
}


final class AtomicUpdaterUnsafe[T, V](fieldOffset: Long) extends AtomicReferenceFieldUpdater[T, V] {
  def compareAndSet(target: AnyRef, oldState: AnyRef, newState: AnyRef) =
    Unsafe.instance.compareAndSwapObject(target, fieldOffset, oldState, newState)

  // ...
}

@carey
Copy link
carey commented Mar 6, 2014

(I reported the issue on the Scala Jira.)

I have Scala code running perfectly well on Google App Engine, without any threading for now. Even single-threaded we're not exceeding the App Engine request deadlines at this point, and if I did want to use multiple threads I could use the ExecutorService directly.

AtomicReferenceFieldUpdater is not final, so AtomicUpdaterUnsafe could extend it while keeping the method short enough for it to be inlined, and substituting the official implementation otherwise. I did some testing with an implementation like that below, and the lock cmpxchg was inlined into the caller:

@Override
public boolean compareAndSet(T obj, Object expect, Object update) {
    return scala.concurrent.util.Unsafe.instance.compareAndSwapObject(obj, offset, expect, update);
}

I expect this could be written in Scala, but I didn't want to complicate things.

@viktorklang
Copy link
Contributor

Whatever we do, we need to make sure we use Unsafe if it's available; and that the overhead will be that of what it currently is, i.e. it gets inlined and intrinsified properly)
We also need to check if we need to backport any fixes to 2.10.4(?) (or less likely, 2.9.5)

Thanks for being on top of this Adriaan

@adriaanm
Copy link
Contributor
adriaanm commented Mar 7, 2014

@carey, thanks for investigating! Any chance you could write this up as a PR or detail your investigation? How do you verify the cmpxchg gets emitted & inlined? (I'm more of a front-end guy.)

I think it would be nice (literally, "nice", since we don't really officially promise this) not to regress on our GAE support.

@carey
Copy link
carey commented Mar 7, 2014

Hotspot has an option to disassemble and dump the assembly code that it generates; in this case I used -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*TestUpdates.update where the update method is where I hoped to get the atomic operation inlined. Some of the output looks like this:

  0x0245e03a: lock cmpxchg %esi,(%edi)
  0x0245e03e: mov    $0x1,%esi
  0x0245e043: je     0x0245e04e
  0x0245e049: mov    $0x0,%esi

leaving out how the getfield and getstatic byte code was implemented to get hold of the Unsafe instance and the field offset. There's other stuff there that I don't understand, hopefully just checking for null pointers and object types.

I can write up some more details of my testing later.

@som-snytt
Copy link
Contributor

@adriaanm front-end guy -> "front man." Also, "nice" literally:
http://etymonline.com/index.php?term=nice

@adriaanm
Copy link
Contributor
adriaanm commented Mar 7, 2014

Interesting evolution for such a small word. I guess I'd call myself a nice (renaissance) man then. Also, innocent in this particular pr.

@axel22
Copy link
Contributor Author
axel22 commented Mar 7, 2014

Agree with everything said - if we could have our own FieldUpdaters available in the stdlib that fall back to ARFU and make sure they are properly inlined, I see a few other places we could use them, e.g.: https://github.com/scala/scala/blob/v2.10.3/src/library/scala/collection/concurrent/INodeBase.java#L19

@viktorklang
Copy link
Contributor

Sounds great, I'm happy to review any PR that tries to address this.

@carey
Copy link
carey commented Mar 8, 2014

My testing was slowed down by finding that the Java 6 Hotspot compiler is quite a bit worse than Java 7, and can't see past abstract classes or checking against null.

The best option is maybe the simplest; if AbstractPromise._refoffset is set to -1 when Unsafe isn't available, then it seems that Hotspot will compile the only possible branch of if (_refoffset == -1).

@viktorklang
Copy link
Contributor

Manually fiddling with offsets means duplicating this code whenever/wherever (we're meant to be together — Shakira) Unsafe is used. I'm much more in favor of subclassing the Updaters if we can get it to inline and intrinsify properly.

@carey
Copy link
carey commented Mar 9, 2014

The Java 6 Client VM doesn't inline alternate subclasses, as at https://github.com/carey/hotspot-tests/blob/444cd0c64d030cbfa8b78fdc6a5b5f967e99e53e/hotspot-tests/src/nz/geek/carey/unsafe/abstractclasses/BaseClass.java. Only 32-bit Java on Windows uses the client VM, where threading performance of Scala code is probably not a major concern, especially when it only seems to be Java 6.

See https://gist.github.com/carey/9444544 for the compiler output, which I saved this time.

@viktorklang
Copy link
Contributor

I agree with the comment on client vm for Java 6. How does it behave with Tiered Compilation?

@carey
Copy link
carey commented Mar 19, 2014

I haven't been able to see any difference in my tests between the client VM and tiered compilation in Java 6 and Java 7, and Java 8 says:

Java HotSpot(TM) Client VM warning: TieredCompilation is disabled in this release.

@ijuma
Copy link
Contributor
ijuma commented Mar 19, 2014

You have to use -server to use TieredCompilation. There is a tiered policy that determines which compiler gets used when, see http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/5d8f5a6dced7/src/share/vm/runtime/advancedThresholdPolicy.cpp for an example.

@viktorklang
Copy link
Contributor

I am experimenting with this for SIP-23:

https://github.com/viktorklang/scala/pull/6

Sadly, JSR166 relies on things that are not exposed on AtomicXFieldUpdaters (like relaxed writes), which means that there be uglies.

Also, I see a huge risk in modifying JSR166 classes (as well as the investment into testing and verifying UnsafeAtomicXUpdaters).

@viktorklang
Copy link
Contributor

@adriaanm @carey I had a stab at implementing this but sadly I'll have to abort the effort: I just don't see how it will be possible to use AtomicXFieldUpdaters to do offset-based (relaxed!) writes into arrays, as done in the ForkJoinPool.java1. See my attempt here2.

@adriaanm
Copy link
Contributor
adriaanm commented Apr 4, 2014

Thanks for the update, @viktorklang.

szeiger pushed a commit to szeiger/scala that referenced this pull request Mar 20, 2018
* * Add tests of `force` for LazyList and Stream.
* Test string representations of LazyList and Stream after `force` call.
* Add tests of `sameElements` for LazyList and Stream.
* Change `force` result type from `Option[(head, tail)]` to `CC[A]`.
* Force `force` to evaluate whole LazyList and Stream.
* Update methods depended on `force`: `unapply` and `sameElements`.

* Reimplement `sameElements`.
** Remove Tuple allocation.
** Check reference equality on every step.

* * Code style.

* * Remove redundant override of `sameElements` method.

* * Set more strict type of `force` method.

* * Stack safe `force` with cyclic references detection.

* * Update LazyListForceBenchmark

* * Fix method name.
* Test `sameElements` with cyclic collections.
* Reduce allocation in `force` method.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants
0