javaperformancechroniclechronicle-byteschronicle-values

Chronicle Values: What's the point if it still needs to allocate an instance on heap?


I'm having trouble understanding how to use this library correctly, because it's creating garbage per allocation and that seems to be the intention? Is the library intended to be used with these characteristics? Or am I missing something, and is there another way to allocate?

For example:

package org.jire;

import net.openhft.chronicle.bytes.Byteable;
import net.openhft.chronicle.bytes.NativeBytesStore;
import net.openhft.chronicle.core.values.IntValue;
import net.openhft.chronicle.values.Values;

public class Test {
    public static void main(String[] args) {
        NativeBytesStore<Void> store = NativeBytesStore.nativeStoreWithFixedCapacity(4);
        while (true) {
            IntValue value = Values.newNativeReference(IntValue.class);
            ((Byteable) value).bytesStore(store, 0, 4);
        }
    }
}

After being profiled in YourKit, you can see there are many allocations (garbage) being produced:

YourKit Profiling


Solution

  • You need to create at least one instance on the heap.

    However, once you have done this it can be used as a flyweight which points to any area in native memory.

    In this example, the data is many times the heap size, yet uses no heap.

    import net.openhft.chronicle.bytes.Byteable;
    import net.openhft.chronicle.bytes.NativeBytesStore;
    import net.openhft.chronicle.core.values.IntValue;
    import net.openhft.chronicle.values.Values;
    
    public class Test {
        public static void main(String[] args) {
            System.out.println("Run with -Xmx64m -verbose:gc -ea");
            int size = 250_000_000;
            IntValue value = Values.newNativeReference(IntValue.class);
            Byteable valuePtr = (Byteable) value;
            // NOTE: This is much, much larger than the heap size
            NativeBytesStore<Void> store = NativeBytesStore.nativeStoreWithFixedCapacity(size * Integer.BYTES);
    
            for (long t = 1; ; t++) {
                // set all the values
                for (int i = 0; i < size; i++) {
                    valuePtr.bytesStore(store, i * Integer.BYTES, Integer.BYTES);
                    boolean set = value.compareAndSwapValue(0, i);
                    assert set;
                }
                System.out.print("set " + t * size + ", ");
    
                // check and unset all the values
                for (int i = 0; i < size; i++) {
                    valuePtr.bytesStore(store, i * Integer.BYTES, Integer.BYTES);
                    boolean set = value.compareAndSwapValue(i, 0);
                    assert set;
                }
                System.out.println("unset " + t * size);
            }
        }
    }
    

    run with -Xmx64m -verbose:gc -ea print some GC on startup but when running produces no objects.

    Run with -Xmx64m -verbose:gc -ea
    [main] INFO net.openhft.chronicle.core.Jvm - Chronicle core loaded from file:/C:/cygwin64/home/peter/Build-All/Chronicle-Core/target/classes/
    [GC (Allocation Failure)  16384K->2072K(62976K), 0.0016387 secs]
    [GC (Allocation Failure)  18456K->2376K(62976K), 0.0016103 secs]
    [GC (Allocation Failure)  18760K->5979K(62976K), 0.0039673 secs]
    [GC (Allocation Failure)  22363K->9703K(62976K), 0.0152631 secs]
    set 250000000, unset 250000000
    set 500000000, unset 500000000
    set 750000000, unset 750000000
    set 1000000000, unset 1000000000
    set 1250000000, unset 1250000000
    set 1500000000, unset 1500000000
    set 1750000000, unset 1750000000
    set 2000000000, unset 2000000000
    set 2250000000, unset 2250000000
    set 2500000000, unset 2500000000
    set 2750000000, unset 2750000000
    set 3000000000, unset 3000000000
    set 3250000000, unset 3250000000
    set 3500000000, unset 3500000000
    set 3750000000, unset 3750000000
    set 4000000000, unset 4000000000
    set 4250000000, unset 4250000000
    set 4500000000, unset 4500000000
    set 4750000000, unset 4750000000
    set 5000000000, unset 5000000000
    

    Now, to get really interesting you can now map this to a file, this file can be accessed concurrently by multiple processes.

    import net.openhft.chronicle.bytes.*;
    import net.openhft.chronicle.core.values.IntValue;
    import net.openhft.chronicle.values.Values;
    
    import java.io.File;
    import java.io.FileNotFoundException;
    
    public class Test {
        public static void main(String[] args) throws FileNotFoundException {
            System.out.println("Run with -Xmx64m -verbose:gc -ea");
            int size = 250_000_000;
            IntValue value = Values.newNativeReference(IntValue.class);
            Byteable valuePtr = (Byteable) value;
            // NOTE: This is much, much larger than the heap size
            File deleteme = new File("deleteme");
            deleteme.delete(); // has to delete or there will be some data later.
            BytesStore store = MappedBytes.mappedBytes(deleteme, size * Integer.BYTES);
    
            for (long t = 1; ; t++) {
                long start = System.nanoTime();
                // set all the values
                for (int i = 0; i < size; i++) {
                    valuePtr.bytesStore(store, i * Integer.BYTES, Integer.BYTES);
                    boolean set = value.compareAndSwapValue(0, i);
                    assert set;
                }
                System.out.print("set " + t * size + ", ");
    
                // check and unset all the values
                for (int i = 0; i < size; i++) {
                    valuePtr.bytesStore(store, i * Integer.BYTES, Integer.BYTES);
                    boolean set = value.compareAndSwapValue(i, 0);
                    assert set;
                }
                System.out.print("unset " + t * size);
                long time = (System.nanoTime() - start) / size;
                System.out.println(", avg time " + time+ " ns to set/unset.");
            }
        }
    }
    

    prints

    Run with -Xmx64m -verbose:gc -ea
    [main] INFO net.openhft.chronicle.core.Jvm - Chronicle core loaded from file:/C:/cygwin64/home/peter/Build-All/Chronicle-Core/target/classes/
    [GC (Allocation Failure)  16384K->2072K(62976K), 0.0014760 secs]
    [GC (Allocation Failure)  18456K->2392K(62976K), 0.0021388 secs]
    [GC (Allocation Failure)  18722K->5839K(62976K), 0.0043109 secs]
    [GC (Allocation Failure)  22223K->9738K(62976K), 0.0052064 secs]
    [main] INFO net.openhft.chronicle.bytes.MappedFile - Took 8506 us to grow file deleteme
    [main] INFO net.openhft.chronicle.bytes.MappedFile - Took 17 ms to add mapping for deleteme
    set 250000000, unset 250000000, avg time 14 ns to set/unset.
    set 500000000, unset 500000000, avg time 12 ns to set/unset.
    set 750000000, unset 750000000, avg time 13 ns to set/unset.
    set 1000000000, unset 1000000000, avg time 13 ns to set/unset.
    set 1250000000, unset 1250000000, avg time 13 ns to set/unset.
    set 1500000000, unset 1500000000, avg time 12 ns to set/unset.
    set 1750000000, unset 1750000000, avg time 12 ns to set/unset.
    set 2000000000, unset 2000000000, avg time 13 ns to set/unset.
    set 2250000000, unset 2250000000, avg time 12 ns to set/unset.
    set 2500000000, unset 2500000000, avg time 12 ns to set/unset.
    set 2750000000, unset 2750000000, avg time 12 ns to set/unset.
    set 3000000000, unset 3000000000, avg time 13 ns to set/unset.
    set 3250000000, unset 3250000000, avg time 13 ns to set/unset.
    set 3500000000, unset 3500000000, avg time 13 ns to set/unset.
    set 3750000000, unset 3750000000, avg time 13 ns to set/unset.
    set 4000000000, unset 4000000000, avg time 13 ns to set/unset.
    set 4250000000, unset 4250000000, avg time 13 ns to set/unset.
    set 4500000000, unset 4500000000, avg time 13 ns to set/unset.
    set 4750000000, unset 4750000000, avg time 12 ns to set/unset.
    set 5000000000, unset 5000000000, avg time 12 ns to set/unset.
    

    NOTE: that's just 13 nano-second on average to write data to a file AND read it back later.