Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Alternatively, it can be solved at compile time with the streams api

        long start = System.currentTimeMillis();
        long count = IntStream.rangeClosed(Float.floatToIntBits(0.0f), Float.floatToIntBits(1.0f)).count();
        System.out.println(count);
        System.out.println((System.currentTimeMillis() - start) + "ms");

  1065353217
  0ms


If you do this,

        int count = 1;
        int limit = Float.floatToIntBits(1.0f);
        for (int intBits = Float.floatToIntBits(0.0f); intBits <= limit; intBits++) {
            count++;
        }

it will start out with the same performance as your previous one, and then after a few tries the JVM will optimize it into a constant.

  1065353218, 0.91
  1065353218, 0.69
  1065353218, 0.00
  1065353218, 0.64
  1065353218, 0.64
  1065353218, 0.63
  1065353218, 0.66
  1065353218, 0.63
  1065353218, 0.63
  1065353218, 0.00
  1065353218, 0.00
  1065353218, 0.00
  ...
  1065353218, 0.00

  Process finished with exit code 0


Does 0 ms = "at compile time"? Seeing that the impl. just computes

    ((long) upTo) - from + last;

I would think it just takes less than 1 ms on first execution.

https://github.com/openjdk/jdk/blob/caa841d9a52352a975394e55...


At compile time!?

Really cool trick!

I can't tell that'd be possible - I see the arguments could be evaluated at compile time, but knowing IntStream.rangeClosed will be evaluated at compile time is a leap, to me.

Did you know before you wrote the code that'd happen? Is it like C? You cross your fingers and hope the compiler unrolls?


The key is that the length of IntStream.rangeClosed(a, b) is just going to be equal to (b - a) + 1.

So there's actually no reason to even invoke the streams API there.


> So there's actually no reason to even invoke the streams API there.

While technically correct, in practice it is not the case.

First of all (and this relates to flerchin's comment), it's not actually done at "compile time" in the traditional sense (i.e. when you invoke `javac`). Rather, it is done at runtime when the method is compiled by the JIT.

As for this comment, the call to `IntStream.rangeClosed` will not simply be reduced to `(b - a) + 1` as you suggest (and one reason for this is that streams are far more complex internally than regular iterators). In reality, it will just be (potentially) inlined and then further optimized alongside the rest of the method's code.

Edit: the mentioned previous comment was from flerchin, not meese

Edit 2: I might have misunderstood what you were getting at there. You are sort of correct in that the `count` operation on this stream is optimized, but it is still technically going through the streams API.


It's not clear to me how the streams api did it, but it's clear to me now that the count of a range of integers is just the end minus the start. This is a fast operation, and I'm not sure if it was performed at compile time, by the JIT, or as a specification of the streams api.


The optimization is done in the JDK implementation (math shortcut) and then further taken advantage of by the JIT (inlining and constant folding).

How is this done?

The implementation of a `Spliterator` that is used internally by `IntStream.rangeClosed` overrides the `estimateSize` method to return `((long) upTo) - from + last`, and the `characteristics` method to return, among others, the `Spliterator.SIZED` characteristic. That characteristic indicates that `estimateSize` can be trusted to be exact, and when present, `estimateSize` will be used as a shortcut in the implementation of `Stream.count`, which can be seen in `ReduceOps.makeRefCounting`.


It's not actually done at compile time. See my reply to messe for details.


It's not actually done at compile time. See my reply to messe for details.

Also, in the case of your test, it's not even letting the JIT kick in, but rather using a shortcut to calculate the length of the range.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: