Graal: [native-image] Generating random numbers

Created on 12 Aug 2019  路  9Comments  路  Source: oracle/graal

I wanted to get a random element from a sequence using (rand-nth seq) but I was always getting the same value. After digging into to the source code I figured out it uses (. Math (random)) under the hood.

I've prepared a sample repo to reproduce the issue. It can be found here: https://github.com/tomekw/random

I had to initialize the main class at build time to compile the project. Maybe that's the cause?

EDIT: I'm on GraalVM 19.1.1 CE and on MacOS

bug native-image

Most helpful comment

https://xkcd.com/221/ ;)

All 9 comments

Also I just created simple Java class:

public class RandomNumber {
    public static void main(String[] args) {
        System.out.println(Math.random());
    }
}

and run

javac RandomNumber.java
native-image RandomNumber

This works:

java RandomNumber

but

./randomnumber

always returns the same value.

This does seem to work:

import java.util.Random;

public class RandomNumber {
    public static void main(String[] args) {
        Random x = new Random();
        System.out.println(x.nextDouble());
    }
}

so it might be a random seed issue. Probably the re-used Random object is seeded using the compilation time, not the run time.

I just encountered exactly the same issue with java.util.concurrent.ThreadLocalRandom, this prorgam:

public class Test {
    public static void main(String... args) {
        System.out.println(java.util.concurrent.ThreadLocalRandom.current().nextInt());
    }
}

Always prints out the same thing (per compilation).

Ok, so this is interesting, I created this program:

public class Test {
    public static void main(String... args) {
        if (args.length > 0) {
            System.out.println(java.util.concurrent.ThreadLocalRandom.current().nextInt());
        }
        new Thread(() -> {
            System.out.println(java.util.concurrent.ThreadLocalRandom.current().nextInt());
            System.exit(0);
        }).start();
    }
}

Whether you supply an argument to the program or not should have no impact on what's output from the thread that was started, since it's using thread local random, and so that random instance should be local to it. But as you can see, that's not the case, they're using a shared Random instance:

$ graal-test 
-347521900
$ graal-test 
-347521900
$ graal-test a
-347521900
1780548204
$ graal-test a
-347521900
1780548204

So it looks like ThreadLocalRandom is not even thread local in GraalVM native image, it's just using one shared Random instance.

I've worked it all out and come up with solutions.

The Math.random() non randomness is due to Math.random() using a static Random instance. Making it random is simple, just delay initialization of class that holds it till runtime, that can be done by adding --initialize-at-run-time=java.lang.Math$RandomNumberGeneratorHolder to your arguments to native-image.

For ThreadLocalRandom, the work around is harder, because GraalVM native image compiler itself uses it, and so its initialization can't be delayed to runtime. Instead, as the first thing my program did in the main method, I used reflection to reset its seed:

private static long mix64(long z) {
    z = (z ^ (z >>> 33)) * 0xff51afd7ed558ccdL;
    z = (z ^ (z >>> 33)) * 0xc4ceb9fe1a85ec53L;
    return z ^ (z >>> 33);
}

private static void initializeThreadLocalRandom() throws Exception {
    long seed = mix64(System.currentTimeMillis()) ^ mix64(System.nanoTime());
    Field field = ThreadLocalRandom.class.getDeclaredField("seeder");
    field.setAccessible(true);
    ((AtomicLong)field.get(null)).set(seed);
}

Of course, you can use any logic to choose the seed, the above is the logic from ThreadLocalRandom itself, which chooses a good even distribution of seeds. (Maybe there's an issue with GPL and copying that to your code, but at the same time, I'm pretty sure you can't claim copyright on the mix64 function since it's just the MurmurHash3 64 bit mixer function, not likely that Oracle can claim they invented that.)

Awesome investigation @jroper! Anyway, maybe such things as random seed generators should be forced to be initialized at runtime, shouldn't they?

https://xkcd.com/221/ ;)

@tomekw @jroper thanks for the report, investigation and proposed solutions. Indeed for Math.random() you indeed need to run the initialization of java.lang.Math$RandomNumberGeneratorHolder at runtime. Similarly, for ThreadLocalRandom you can re-run its initialization at run time as pointed out in https://github.com/oracle/graal/issues/1614. Re-running a class initialization at run time is only available via API and not via an option. I will make both of these a default.

Was this page helpful?
0 / 5 - 0 ratings