Add 'io.netty.tryAllocateUninitializedArray' system property which allows to allocate byte[] without memset in Java9+
Motivation: Java9 added a new method to Unsafe which allows to allocate a byte[] without memset it. This can have a massive impact in allocation times when the byte[] is big. This change allows to enable this when using Java9 with the io.netty.tryAllocateUninitializedArray property when running Java9+. Please note that you will need to open up the jdk.internal.misc package via '--add-opens java.base/jdk.internal.misc=ALL-UNNAMED' as well. Modifications: Allow to allocate byte[] without memset on Java9+ Result: Better performance when allocate big heap buffers and using java9.
This commit is contained in:
parent
fb113dce3a
commit
e482d933f7
@ -672,6 +672,10 @@ abstract class PoolArena<T> implements PoolArenaMetric {
|
|||||||
directMemoryCacheAlignment);
|
directMemoryCacheAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static byte[] newByteArray(int size) {
|
||||||
|
return PlatformDependent.allocateUninitializedArray(size);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isDirect() {
|
boolean isDirect() {
|
||||||
return false;
|
return false;
|
||||||
@ -679,12 +683,12 @@ abstract class PoolArena<T> implements PoolArenaMetric {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PoolChunk<byte[]> newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) {
|
protected PoolChunk<byte[]> newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) {
|
||||||
return new PoolChunk<byte[]>(this, new byte[chunkSize], pageSize, maxOrder, pageShifts, chunkSize, 0);
|
return new PoolChunk<byte[]>(this, newByteArray(chunkSize), pageSize, maxOrder, pageShifts, chunkSize, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PoolChunk<byte[]> newUnpooledChunk(int capacity) {
|
protected PoolChunk<byte[]> newUnpooledChunk(int capacity) {
|
||||||
return new PoolChunk<byte[]>(this, new byte[capacity], capacity, 0);
|
return new PoolChunk<byte[]>(this, newByteArray(capacity), capacity, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -29,6 +29,11 @@ class UnpooledUnsafeHeapByteBuf extends UnpooledHeapByteBuf {
|
|||||||
super(alloc, initialCapacity, maxCapacity);
|
super(alloc, initialCapacity, maxCapacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
byte[] allocateArray(int initialCapacity) {
|
||||||
|
return PlatformDependent.allocateUninitializedArray(initialCapacity);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte getByte(int index) {
|
public byte getByte(int index) {
|
||||||
checkIndex(index);
|
checkIndex(index);
|
||||||
|
@ -97,6 +97,7 @@ public final class PlatformDependent {
|
|||||||
private static final long DIRECT_MEMORY_LIMIT;
|
private static final long DIRECT_MEMORY_LIMIT;
|
||||||
private static final ThreadLocalRandomProvider RANDOM_PROVIDER;
|
private static final ThreadLocalRandomProvider RANDOM_PROVIDER;
|
||||||
private static final Cleaner CLEANER;
|
private static final Cleaner CLEANER;
|
||||||
|
private static final int UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD;
|
||||||
|
|
||||||
public static final boolean BIG_ENDIAN_NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
public static final boolean BIG_ENDIAN_NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
|
||||||
|
|
||||||
@ -160,7 +161,13 @@ public final class PlatformDependent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DIRECT_MEMORY_LIMIT = maxDirectMemory;
|
DIRECT_MEMORY_LIMIT = maxDirectMemory;
|
||||||
logger.debug("io.netty.maxDirectMemory: {} bytes", maxDirectMemory);
|
logger.debug("-Dio.netty.maxDirectMemory: {} bytes", maxDirectMemory);
|
||||||
|
|
||||||
|
int tryAllocateUninitializedArray =
|
||||||
|
SystemPropertyUtil.getInt("io.netty.uninitializedArrayAllocationThreshold", 1024);
|
||||||
|
UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD = javaVersion() >= 9 && PlatformDependent0.hasAllocateArrayMethod() ?
|
||||||
|
tryAllocateUninitializedArray : -1;
|
||||||
|
logger.debug("-Dio.netty.uninitializedArrayAllocationThreshold: {}", UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD);
|
||||||
|
|
||||||
MAYBE_SUPER_USER = maybeSuperUser0();
|
MAYBE_SUPER_USER = maybeSuperUser0();
|
||||||
|
|
||||||
@ -177,6 +184,11 @@ public final class PlatformDependent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] allocateUninitializedArray(int size) {
|
||||||
|
return UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD < 0 || UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD > size ?
|
||||||
|
new byte[size] : PlatformDependent0.allocateUninitializedArray(size);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if and only if the current platform is Android
|
* Returns {@code true} if and only if the current platform is Android
|
||||||
*/
|
*/
|
||||||
|
@ -40,7 +40,9 @@ final class PlatformDependent0 {
|
|||||||
private static final long BYTE_ARRAY_BASE_OFFSET;
|
private static final long BYTE_ARRAY_BASE_OFFSET;
|
||||||
private static final Constructor<?> DIRECT_BUFFER_CONSTRUCTOR;
|
private static final Constructor<?> DIRECT_BUFFER_CONSTRUCTOR;
|
||||||
private static final boolean IS_EXPLICIT_NO_UNSAFE = explicitNoUnsafe0();
|
private static final boolean IS_EXPLICIT_NO_UNSAFE = explicitNoUnsafe0();
|
||||||
|
private static final Method ALLOCATE_ARRAY_METHOD;
|
||||||
|
|
||||||
|
private static final Object INTERNAL_UNSAFE;
|
||||||
static final Unsafe UNSAFE;
|
static final Unsafe UNSAFE;
|
||||||
|
|
||||||
// constants borrowed from murmur3
|
// constants borrowed from murmur3
|
||||||
@ -59,12 +61,15 @@ final class PlatformDependent0 {
|
|||||||
static {
|
static {
|
||||||
final ByteBuffer direct;
|
final ByteBuffer direct;
|
||||||
Field addressField = null;
|
Field addressField = null;
|
||||||
|
Method allocateArrayMethod = null;
|
||||||
Unsafe unsafe;
|
Unsafe unsafe;
|
||||||
|
Object internalUnsafe = null;
|
||||||
|
|
||||||
if (isExplicitNoUnsafe()) {
|
if (isExplicitNoUnsafe()) {
|
||||||
direct = null;
|
direct = null;
|
||||||
addressField = null;
|
addressField = null;
|
||||||
unsafe = null;
|
unsafe = null;
|
||||||
|
internalUnsafe = null;
|
||||||
} else {
|
} else {
|
||||||
direct = ByteBuffer.allocateDirect(1);
|
direct = ByteBuffer.allocateDirect(1);
|
||||||
|
|
||||||
@ -187,6 +192,7 @@ final class PlatformDependent0 {
|
|||||||
BYTE_ARRAY_BASE_OFFSET = -1;
|
BYTE_ARRAY_BASE_OFFSET = -1;
|
||||||
UNALIGNED = false;
|
UNALIGNED = false;
|
||||||
DIRECT_BUFFER_CONSTRUCTOR = null;
|
DIRECT_BUFFER_CONSTRUCTOR = null;
|
||||||
|
ALLOCATE_ARRAY_METHOD = null;
|
||||||
} else {
|
} else {
|
||||||
Constructor<?> directBufferConstructor;
|
Constructor<?> directBufferConstructor;
|
||||||
long address = -1;
|
long address = -1;
|
||||||
@ -237,7 +243,6 @@ final class PlatformDependent0 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DIRECT_BUFFER_CONSTRUCTOR = directBufferConstructor;
|
DIRECT_BUFFER_CONSTRUCTOR = directBufferConstructor;
|
||||||
|
|
||||||
ADDRESS_FIELD_OFFSET = objectFieldOffset(addressField);
|
ADDRESS_FIELD_OFFSET = objectFieldOffset(addressField);
|
||||||
BYTE_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
|
BYTE_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
|
||||||
boolean unaligned;
|
boolean unaligned;
|
||||||
@ -279,8 +284,65 @@ final class PlatformDependent0 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UNALIGNED = unaligned;
|
UNALIGNED = unaligned;
|
||||||
|
|
||||||
|
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object run() {
|
||||||
|
try {
|
||||||
|
// Java9 has jdk.internal.misc.Unsafe and not all methods are propergated to
|
||||||
|
// sun.misc.Unsafe
|
||||||
|
Class<?> internalUnsafeClass = getClassLoader(PlatformDependent0.class)
|
||||||
|
.loadClass("jdk.internal.misc.Unsafe");
|
||||||
|
Method method = internalUnsafeClass.getDeclaredMethod("getUnsafe");
|
||||||
|
return method.invoke(null);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!(maybeException instanceof Throwable)) {
|
||||||
|
internalUnsafe = maybeException;
|
||||||
|
final Object finalInternalUnsafe = internalUnsafe;
|
||||||
|
maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
|
||||||
|
@Override
|
||||||
|
public Object run() {
|
||||||
|
try {
|
||||||
|
return finalInternalUnsafe.getClass().getDeclaredMethod(
|
||||||
|
"allocateUninitializedArray", Class.class, int.class);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
return e;
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (maybeException instanceof Method) {
|
||||||
|
try {
|
||||||
|
Method m = (Method) maybeException;
|
||||||
|
byte[] bytes = (byte[]) m.invoke(finalInternalUnsafe, byte.class, 8);
|
||||||
|
assert bytes.length == 8;
|
||||||
|
allocateArrayMethod = m;
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
maybeException = e;
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
maybeException = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ALLOCATE_ARRAY_METHOD = allocateArrayMethod;
|
||||||
|
|
||||||
|
if (maybeException instanceof Throwable) {
|
||||||
|
logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable",
|
||||||
|
(Throwable) maybeException);
|
||||||
|
} else {
|
||||||
|
logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): available");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INTERNAL_UNSAFE = internalUnsafe;
|
||||||
|
|
||||||
logger.debug("java.nio.DirectByteBuffer.<init>(long, int): {}",
|
logger.debug("java.nio.DirectByteBuffer.<init>(long, int): {}",
|
||||||
DIRECT_BUFFER_CONSTRUCTOR != null ? "available" : "unavailable");
|
DIRECT_BUFFER_CONSTRUCTOR != null ? "available" : "unavailable");
|
||||||
}
|
}
|
||||||
@ -343,6 +405,20 @@ final class PlatformDependent0 {
|
|||||||
return newDirectBuffer(UNSAFE.allocateMemory(capacity), capacity);
|
return newDirectBuffer(UNSAFE.allocateMemory(capacity), capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean hasAllocateArrayMethod() {
|
||||||
|
return ALLOCATE_ARRAY_METHOD != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte[] allocateUninitializedArray(int size) {
|
||||||
|
try {
|
||||||
|
return (byte[]) ALLOCATE_ARRAY_METHOD.invoke(INTERNAL_UNSAFE, byte.class, size);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
throw new Error(e);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static ByteBuffer newDirectBuffer(long address, int capacity) {
|
static ByteBuffer newDirectBuffer(long address, int capacity) {
|
||||||
ObjectUtil.checkPositiveOrZero(capacity, "capacity");
|
ObjectUtil.checkPositiveOrZero(capacity, "capacity");
|
||||||
|
|
||||||
|
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package io.netty.microbench.internal;
|
||||||
|
|
||||||
|
import io.netty.microbench.util.AbstractMicrobenchmark;
|
||||||
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
import org.openjdk.jmh.annotations.Benchmark;
|
||||||
|
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||||
|
import org.openjdk.jmh.annotations.Fork;
|
||||||
|
import org.openjdk.jmh.annotations.Level;
|
||||||
|
import org.openjdk.jmh.annotations.Measurement;
|
||||||
|
import org.openjdk.jmh.annotations.Mode;
|
||||||
|
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||||
|
import org.openjdk.jmh.annotations.Param;
|
||||||
|
import org.openjdk.jmh.annotations.Scope;
|
||||||
|
import org.openjdk.jmh.annotations.Setup;
|
||||||
|
import org.openjdk.jmh.annotations.State;
|
||||||
|
import org.openjdk.jmh.annotations.Warmup;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||||
|
@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||||
|
@Fork(2)
|
||||||
|
@BenchmarkMode(Mode.AverageTime)
|
||||||
|
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||||
|
@State(Scope.Benchmark)
|
||||||
|
public class UnitializedArrayBenchmark extends AbstractMicrobenchmark {
|
||||||
|
|
||||||
|
@Param({ "1", "10", "100", "1000", "10000", "100000" })
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
@Setup(Level.Trial)
|
||||||
|
public void setupTrial() {
|
||||||
|
if (PlatformDependent.javaVersion() < 9) {
|
||||||
|
throw new IllegalStateException("Needs Java9");
|
||||||
|
}
|
||||||
|
if (!PlatformDependent.hasUnsafe()) {
|
||||||
|
throw new IllegalStateException("Needs Unsafe");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] jvmArgs() {
|
||||||
|
// Ensure we minimize the GC overhead for this benchmark and also open up required package.
|
||||||
|
// See also https://shipilev.net/jvm-anatomy-park/7-initialization-costs/
|
||||||
|
return new String[] { "-XX:+UseParallelOldGC", "-Xmx8g", "-Xms8g",
|
||||||
|
"-Xmn6g", "--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED" };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public byte[] allocateInitializedByteArray() {
|
||||||
|
return new byte[size];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Benchmark
|
||||||
|
public byte[] allocateUninitializedByteArray() {
|
||||||
|
return PlatformDependent.allocateUninitializedArray(size);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user