Added support for cache-line padding and alignment.
Made byteOffset() in MappedField optional. Misc fixes and improvements.
This commit is contained in:
parent
dc4fb854cb
commit
900eb4e4d2
|
@ -449,12 +449,42 @@ public class LWJGLUtil {
|
||||||
* Gets a boolean property as a privileged action.
|
* Gets a boolean property as a privileged action.
|
||||||
*/
|
*/
|
||||||
public static boolean getPrivilegedBoolean(final String property_name) {
|
public static boolean getPrivilegedBoolean(final String property_name) {
|
||||||
Boolean value = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
|
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
|
||||||
public Boolean run() {
|
public Boolean run() {
|
||||||
return Boolean.getBoolean(property_name);
|
return Boolean.getBoolean(property_name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return value;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an integer property as a privileged action.
|
||||||
|
*
|
||||||
|
* @param property_name the integer property name
|
||||||
|
*
|
||||||
|
* @return the property value
|
||||||
|
*/
|
||||||
|
public static Integer getPrivilegedInteger(final String property_name) {
|
||||||
|
return AccessController.doPrivileged(new PrivilegedAction<Integer>() {
|
||||||
|
public Integer run() {
|
||||||
|
return Integer.getInteger(property_name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an integer property as a privileged action.
|
||||||
|
*
|
||||||
|
* @param property_name the integer property name
|
||||||
|
* @param default_val the default value to use if the property is not defined
|
||||||
|
*
|
||||||
|
* @return the property value
|
||||||
|
*/
|
||||||
|
public static Integer getPrivilegedInteger(final String property_name, final int default_val) {
|
||||||
|
return AccessController.doPrivileged(new PrivilegedAction<Integer>() {
|
||||||
|
public Integer run() {
|
||||||
|
return Integer.getInteger(property_name, default_val);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -60,6 +60,16 @@ public class MappedObjectTests3 {
|
||||||
assert (addr2 - addr1 == 4);
|
assert (addr2 - addr1 == 4);
|
||||||
assert (mapped.capacity() == MappedSomething.SIZEOF - 4);
|
assert (mapped.capacity() == MappedSomething.SIZEOF - 4);
|
||||||
|
|
||||||
|
{
|
||||||
|
assert (some.shared == 0);
|
||||||
|
assert (mapped.getInt(8) == 0);
|
||||||
|
|
||||||
|
some.shared = 1234;
|
||||||
|
|
||||||
|
assert (some.shared == 1234);
|
||||||
|
assert (mapped.getInt(8) == 1234);
|
||||||
|
}
|
||||||
|
|
||||||
some.view++;
|
some.view++;
|
||||||
mapped = some.data; // creates new ByteBuffer instance
|
mapped = some.data; // creates new ByteBuffer instance
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,10 @@ package org.lwjgl.test.mapped;
|
||||||
import org.lwjgl.MemoryUtil;
|
import org.lwjgl.MemoryUtil;
|
||||||
import org.lwjgl.PointerBuffer;
|
import org.lwjgl.PointerBuffer;
|
||||||
import org.lwjgl.opengl.Display;
|
import org.lwjgl.opengl.Display;
|
||||||
import org.lwjgl.util.mapped.MappedObject;
|
import org.lwjgl.util.mapped.*;
|
||||||
import org.lwjgl.util.mapped.Pointer;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/** @author Riven */
|
/** @author Riven */
|
||||||
|
@ -145,4 +145,109 @@ public class MappedObjectTests4 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MappedType(cacheLinePadding = true)
|
||||||
|
public static class MappedCacheLinePadded extends MappedObject {
|
||||||
|
|
||||||
|
int foo;
|
||||||
|
int bar;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void testCacheLineAlignment() {
|
||||||
|
MappedCacheLinePadded data = MappedCacheLinePadded.malloc(10);
|
||||||
|
|
||||||
|
assert (data.backingByteBuffer().capacity() == 10 * CacheUtil.getCacheLineSize());
|
||||||
|
assert (MemoryUtil.getAddress(data.backingByteBuffer()) % CacheUtil.getCacheLineSize() == 0);
|
||||||
|
|
||||||
|
for ( int i = 0; i < 10; i++ ) {
|
||||||
|
data.view = i;
|
||||||
|
|
||||||
|
data.foo = i;
|
||||||
|
data.bar = i * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( int i = 0; i < 10; i++ ) {
|
||||||
|
data.view = i;
|
||||||
|
|
||||||
|
assert (data.foo == i);
|
||||||
|
assert (data.bar == i * 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MappedFieldCacheLinePadded extends MappedObject {
|
||||||
|
|
||||||
|
// If we assume CacheUtil.getCacheLineSize() == 64
|
||||||
|
// 0 - 63
|
||||||
|
@CacheLinePad long longBar;
|
||||||
|
// 64 - 71
|
||||||
|
long longFoo;
|
||||||
|
// 72 - 75
|
||||||
|
int intFoo;
|
||||||
|
// 128 - 131
|
||||||
|
@CacheLinePad(before = true) int intBar;
|
||||||
|
// 192 - 195
|
||||||
|
int foo;
|
||||||
|
// 256 - 267
|
||||||
|
@CacheLinePad(before = true, after = false)
|
||||||
|
@MappedField(byteLength = 12)
|
||||||
|
ByteBuffer buffer;
|
||||||
|
// 268 - 271
|
||||||
|
int bar;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void testCacheLinePadding() {
|
||||||
|
MappedFieldCacheLinePadded data = MappedFieldCacheLinePadded.map(CacheUtil.createByteBuffer(10 * MappedFieldCacheLinePadded.SIZEOF));
|
||||||
|
|
||||||
|
final int sizeof =
|
||||||
|
CacheUtil.getCacheLineSize()
|
||||||
|
+ 8
|
||||||
|
+ (CacheUtil.getCacheLineSize() - 8)
|
||||||
|
+ CacheUtil.getCacheLineSize()
|
||||||
|
+ 4
|
||||||
|
+ (CacheUtil.getCacheLineSize() - 4)
|
||||||
|
+ 12
|
||||||
|
+ 4;
|
||||||
|
|
||||||
|
assert (MappedFieldCacheLinePadded.SIZEOF == sizeof);
|
||||||
|
assert (data.backingByteBuffer().capacity() == sizeof * 10);
|
||||||
|
|
||||||
|
for ( int i = 0; i < 10; i++ ) {
|
||||||
|
data.view = i;
|
||||||
|
|
||||||
|
data.longFoo = i * 1000000000L;
|
||||||
|
data.longBar = i * 2000000000L;
|
||||||
|
data.intFoo = i * 1000;
|
||||||
|
data.intBar = i * 2000;
|
||||||
|
data.foo = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( int i = 0; i < 10; i++ ) {
|
||||||
|
data.view = i;
|
||||||
|
|
||||||
|
assert (data.longFoo == i * 1000000000L);
|
||||||
|
assert (data.longBar == i * 2000000000L);
|
||||||
|
assert (data.intFoo == i * 1000);
|
||||||
|
assert (data.intBar == i * 2000);
|
||||||
|
assert (data.foo == i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class POJOFieldCacheLinePadded {
|
||||||
|
|
||||||
|
@CacheLinePad long longBar;
|
||||||
|
long longFoo;
|
||||||
|
int intFoo;
|
||||||
|
@CacheLinePad(before = true) int intBar;
|
||||||
|
int foo;
|
||||||
|
@CacheLinePad boolean bool;
|
||||||
|
int bar;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void testCacheLinePaddingPOJO() {
|
||||||
|
Field[] fields = new POJOFieldCacheLinePadded().getClass().getDeclaredFields();
|
||||||
|
assert (fields.length == (1 + 7) + 1 + 1 + (15 + 1 + 15) + 1 + (1 + 63) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -42,9 +42,12 @@ public class MappedSomething extends MappedObject {
|
||||||
@MappedField(byteOffset = 0)
|
@MappedField(byteOffset = 0)
|
||||||
public int used;
|
public int used;
|
||||||
|
|
||||||
@MappedField(byteOffset = 4, byteLength = 64 - 4)
|
@MappedField(byteLength = 64 - 4) // optional byteOffset
|
||||||
public ByteBuffer data;
|
public ByteBuffer data;
|
||||||
|
|
||||||
|
@MappedField(byteOffset = 12) // inside data
|
||||||
|
public int shared;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "MappedSomething[" + used + "]";
|
return "MappedSomething[" + used + "]";
|
||||||
|
|
|
@ -52,6 +52,8 @@ public class TestMappedObject {
|
||||||
MappedObjectTransformer.register(MappedSomething.class);
|
MappedObjectTransformer.register(MappedSomething.class);
|
||||||
MappedObjectTransformer.register(MappedObjectTests3.Xyz.class);
|
MappedObjectTransformer.register(MappedObjectTests3.Xyz.class);
|
||||||
MappedObjectTransformer.register(MappedObjectTests4.MappedPointer.class);
|
MappedObjectTransformer.register(MappedObjectTests4.MappedPointer.class);
|
||||||
|
MappedObjectTransformer.register(MappedObjectTests4.MappedCacheLinePadded.class);
|
||||||
|
MappedObjectTransformer.register(MappedObjectTests4.MappedFieldCacheLinePadded.class);
|
||||||
|
|
||||||
if ( MappedObjectClassLoader.fork(TestMappedObject.class, args) ) {
|
if ( MappedObjectClassLoader.fork(TestMappedObject.class, args) ) {
|
||||||
return;
|
return;
|
||||||
|
@ -75,6 +77,9 @@ public class TestMappedObject {
|
||||||
MappedObjectTests4.testLocalView();
|
MappedObjectTests4.testLocalView();
|
||||||
//MappedObjectTests4.testLWJGL();
|
//MappedObjectTests4.testLWJGL();
|
||||||
MappedObjectTests4.testPointer();
|
MappedObjectTests4.testPointer();
|
||||||
|
MappedObjectTests4.testCacheLineAlignment();
|
||||||
|
MappedObjectTests4.testCacheLinePadding();
|
||||||
|
MappedObjectTests4.testCacheLinePaddingPOJO();
|
||||||
|
|
||||||
System.out.println("done");
|
System.out.println("done");
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,8 @@ public final class SpriteShootout {
|
||||||
private int texBigID;
|
private int texBigID;
|
||||||
private int texSmallID;
|
private int texSmallID;
|
||||||
|
|
||||||
|
long animateTime;
|
||||||
|
|
||||||
private SpriteShootout() {
|
private SpriteShootout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,6 +278,8 @@ public final class SpriteShootout {
|
||||||
long timeUsed = 5000 + (startTime - System.currentTimeMillis());
|
long timeUsed = 5000 + (startTime - System.currentTimeMillis());
|
||||||
startTime = System.currentTimeMillis() + 5000;
|
startTime = System.currentTimeMillis() + 5000;
|
||||||
System.out.println("FPS: " + (Math.round(fps / (timeUsed / 1000.0) * 10) / 10.0) + ", Balls: " + ballCount);
|
System.out.println("FPS: " + (Math.round(fps / (timeUsed / 1000.0) * 10) / 10.0) + ", Balls: " + ballCount);
|
||||||
|
System.out.println("\tAnimation: " + (animateTime / fps / 1000) + "us");
|
||||||
|
animateTime = 0;
|
||||||
fps = 0;
|
fps = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -582,7 +586,11 @@ public final class SpriteShootout {
|
||||||
if ( animate ) {
|
if ( animate ) {
|
||||||
final ByteBuffer buffer = animVBO.map(batchSize * (2 * 4));
|
final ByteBuffer buffer = animVBO.map(batchSize * (2 * 4));
|
||||||
|
|
||||||
|
long t0 = System.nanoTime();
|
||||||
animate(transform, buffer.asFloatBuffer(), ballSize, ballIndex, batchSize, delta);
|
animate(transform, buffer.asFloatBuffer(), ballSize, ballIndex, batchSize, delta);
|
||||||
|
long t1 = System.nanoTime();
|
||||||
|
|
||||||
|
animateTime += t1 - t0;
|
||||||
|
|
||||||
animVBO.unmap();
|
animVBO.unmap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,8 @@ public final class SpriteShootoutMapped {
|
||||||
private int texBigID;
|
private int texBigID;
|
||||||
private int texSmallID;
|
private int texSmallID;
|
||||||
|
|
||||||
|
long animateTime;
|
||||||
|
|
||||||
private SpriteShootoutMapped() {
|
private SpriteShootoutMapped() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,6 +314,8 @@ public final class SpriteShootoutMapped {
|
||||||
long timeUsed = 5000 + (startTime - System.currentTimeMillis());
|
long timeUsed = 5000 + (startTime - System.currentTimeMillis());
|
||||||
startTime = System.currentTimeMillis() + 5000;
|
startTime = System.currentTimeMillis() + 5000;
|
||||||
System.out.println("FPS: " + (Math.round(fps / (timeUsed / 1000.0) * 10) / 10.0) + ", Balls: " + ballCount);
|
System.out.println("FPS: " + (Math.round(fps / (timeUsed / 1000.0) * 10) / 10.0) + ", Balls: " + ballCount);
|
||||||
|
System.out.println("Animation: " + animateTime / fps);
|
||||||
|
animateTime = 0;
|
||||||
fps = 0;
|
fps = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -414,8 +418,8 @@ public final class SpriteShootoutMapped {
|
||||||
|
|
||||||
public static class Sprite extends MappedObject {
|
public static class Sprite extends MappedObject {
|
||||||
|
|
||||||
public float x, dx;
|
public float dx, x;
|
||||||
public float y, dy;
|
public float dy, y;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,8 +531,8 @@ public final class SpriteShootoutMapped {
|
||||||
final Sprite[] sprites = sprite.asArray();
|
final Sprite[] sprites = sprite.asArray();
|
||||||
final SpriteRender[] spritesRender = spriteRender.asArray();
|
final SpriteRender[] spritesRender = spriteRender.asArray();
|
||||||
for ( int b = ballIndex, r = 0, len = (ballIndex + batchSize); b < len; b++, r++ ) {
|
for ( int b = ballIndex, r = 0, len = (ballIndex + batchSize); b < len; b++, r++ ) {
|
||||||
float x = sprites[b].x;
|
|
||||||
float dx = sprites[b].dx;
|
float dx = sprites[b].dx;
|
||||||
|
float x = sprites[b].x;
|
||||||
|
|
||||||
x += dx * delta;
|
x += dx * delta;
|
||||||
if ( x < ballRadius ) {
|
if ( x < ballRadius ) {
|
||||||
|
@ -539,12 +543,12 @@ public final class SpriteShootoutMapped {
|
||||||
dx = -dx;
|
dx = -dx;
|
||||||
}
|
}
|
||||||
|
|
||||||
sprites[b].x = x;
|
|
||||||
sprites[b].dx = dx;
|
sprites[b].dx = dx;
|
||||||
|
sprites[b].x = x;
|
||||||
spritesRender[r].x = x;
|
spritesRender[r].x = x;
|
||||||
|
|
||||||
float y = sprites[b].y;
|
|
||||||
float dy = sprites[b].dy;
|
float dy = sprites[b].dy;
|
||||||
|
float y = sprites[b].y;
|
||||||
|
|
||||||
y += dy * delta;
|
y += dy * delta;
|
||||||
if ( y < ballRadius ) {
|
if ( y < ballRadius ) {
|
||||||
|
@ -555,8 +559,8 @@ public final class SpriteShootoutMapped {
|
||||||
dy = -dy;
|
dy = -dy;
|
||||||
}
|
}
|
||||||
|
|
||||||
sprites[b].y = y;
|
|
||||||
sprites[b].dy = dy;
|
sprites[b].dy = dy;
|
||||||
|
sprites[b].y = y;
|
||||||
spritesRender[r].y = y;
|
spritesRender[r].y = y;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -654,7 +658,11 @@ public final class SpriteShootoutMapped {
|
||||||
if ( animate ) {
|
if ( animate ) {
|
||||||
final ByteBuffer buffer = animVBO.map(batchSize * (2 * 4));
|
final ByteBuffer buffer = animVBO.map(batchSize * (2 * 4));
|
||||||
|
|
||||||
|
long t0 = System.nanoTime();
|
||||||
animate(sprites, SpriteRender.<SpriteRender>map(buffer), ballSize, ballIndex, batchSize, delta);
|
animate(sprites, SpriteRender.<SpriteRender>map(buffer), ballSize, ballIndex, batchSize, delta);
|
||||||
|
long t1 = System.nanoTime();
|
||||||
|
|
||||||
|
animateTime += t1 - t0;
|
||||||
|
|
||||||
animVBO.unmap();
|
animVBO.unmap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2011 LWJGL Project
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are
|
||||||
|
* met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* * Neither the name of 'LWJGL' nor the names of
|
||||||
|
* its contributors may be used to endorse or promote products derived
|
||||||
|
* from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package org.lwjgl.util.mapped;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When this annotation is used on a field, automatic cache-line-sized padding
|
||||||
|
* will be inserted around the field. This is useful in multi-threaded algorithms
|
||||||
|
* to avoid cache line false sharing. The annotation defaults to padding after
|
||||||
|
* the field, but can be changed to before or both before and after. It can be
|
||||||
|
* applied to both mapped object fields and POJO primitive fields.
|
||||||
|
*
|
||||||
|
* @author Spasi
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
public @interface CacheLinePad {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When true, cache-line padding will be inserted before the field.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean before() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When true, cache-line padding will be inserted after the field.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean after() default true;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,141 @@
|
||||||
|
package org.lwjgl.util.mapped;
|
||||||
|
|
||||||
|
import org.lwjgl.LWJGLUtil;
|
||||||
|
import org.lwjgl.MemoryUtil;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.IntBuffer;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorCompletionService;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import static org.lwjgl.util.mapped.MappedHelper.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This micro-benchmark tries to detect the CPU's cache line size. This is
|
||||||
|
* done by exploiting cache line false sharing in multi-threaded code:
|
||||||
|
* When 2 threads simultaneously access the same cache line (and at least
|
||||||
|
* 1 access is a write), performance drops considerably. We detect this
|
||||||
|
* performance drop while decreasing the memory padding in every test step.
|
||||||
|
*
|
||||||
|
* @author Spasi
|
||||||
|
*/
|
||||||
|
final class CacheLineSize {
|
||||||
|
|
||||||
|
private CacheLineSize() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getCacheLineSize() {
|
||||||
|
final int THREADS = 2;
|
||||||
|
final int REPEATS = 100000 * THREADS;
|
||||||
|
final int LOCAL_REPEATS = REPEATS / THREADS;
|
||||||
|
|
||||||
|
// Detection will start from CacheLineMaxSize bytes.
|
||||||
|
final int MAX_SIZE = LWJGLUtil.getPrivilegedInteger("org.lwjgl.util.mapped.CacheLineMaxSize", 1024) / 4; // in # of integers
|
||||||
|
// Detection will stop when the execution time increases by more than CacheLineTimeThreshold %.
|
||||||
|
final double TIME_THRESHOLD = 1.0 + LWJGLUtil.getPrivilegedInteger("org.lwjgl.util.mapped.CacheLineTimeThreshold", 50) / 100.0;
|
||||||
|
|
||||||
|
final ExecutorService executorService = Executors.newFixedThreadPool(THREADS);
|
||||||
|
final ExecutorCompletionService<Long> completionService = new ExecutorCompletionService<Long>(executorService);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// We need to use a NIO buffer in order to guarantee memory alignment.
|
||||||
|
final IntBuffer memory = getMemory(MAX_SIZE);
|
||||||
|
|
||||||
|
// -- WARMUP --
|
||||||
|
|
||||||
|
final int WARMUP = 10;
|
||||||
|
for ( int i = 0; i < WARMUP; i++ )
|
||||||
|
doTest(THREADS, LOCAL_REPEATS, 0, memory, completionService);
|
||||||
|
|
||||||
|
// -- CACHE LINE SIZE DETECTION --
|
||||||
|
|
||||||
|
long totalTime = 0;
|
||||||
|
int count = 0;
|
||||||
|
int cacheLineSize = 64; // fallback to the most common size these days
|
||||||
|
boolean found = false;
|
||||||
|
for ( int i = MAX_SIZE; i >= 1; i >>= 1 ) {
|
||||||
|
final long time = doTest(THREADS, LOCAL_REPEATS, i, memory, completionService);
|
||||||
|
if ( totalTime > 0 ) { // Ignore first run
|
||||||
|
final long avgTime = totalTime / count;
|
||||||
|
if ( (double)time / (double)avgTime > TIME_THRESHOLD ) { // Try to detect a noticeable jump in execution time
|
||||||
|
cacheLineSize = (i << 1) * 4;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalTime += time;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( LWJGLUtil.DEBUG ) {
|
||||||
|
if ( found )
|
||||||
|
LWJGLUtil.log("Cache line size detected: " + cacheLineSize + " bytes");
|
||||||
|
else
|
||||||
|
LWJGLUtil.log("Failed to detect cache line size, assuming " + cacheLineSize + " bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheLineSize;
|
||||||
|
} finally {
|
||||||
|
executorService.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
CacheUtil.getCacheLineSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
static long memoryLoop(final int index, final int repeats, final IntBuffer memory, final int padding) {
|
||||||
|
final long address = MemoryUtil.getAddress(memory) + (index * padding * 4);
|
||||||
|
|
||||||
|
final long time = System.nanoTime();
|
||||||
|
for ( int i = 0; i < repeats; i++ ) {
|
||||||
|
// Use volatile access to avoid server VM optimizations.
|
||||||
|
ivput(ivget(address) + 1, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return System.nanoTime() - time;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IntBuffer getMemory(final int START_SIZE) {
|
||||||
|
final int PAGE_SIZE = MappedObjectUnsafe.INSTANCE.pageSize();
|
||||||
|
|
||||||
|
final ByteBuffer buffer = ByteBuffer.allocateDirect((START_SIZE * 4) + PAGE_SIZE).order(ByteOrder.nativeOrder());
|
||||||
|
|
||||||
|
// Align to page and, consequently, to cache line. Otherwise results will be inconsistent.
|
||||||
|
if ( MemoryUtil.getAddress(buffer) % PAGE_SIZE != 0 ) {
|
||||||
|
// Round up to page boundary
|
||||||
|
buffer.position(PAGE_SIZE - (int)(MemoryUtil.getAddress(buffer) & (PAGE_SIZE - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.asIntBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long doTest(final int threads, final int repeats, final int padding, final IntBuffer memory, final ExecutorCompletionService<Long> completionService) {
|
||||||
|
for ( int i = 0; i < threads; i++ )
|
||||||
|
submitTest(completionService, i, repeats, memory, padding);
|
||||||
|
return waitForResults(threads, completionService);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void submitTest(final ExecutorCompletionService<Long> completionService, final int index, final int repeats, final IntBuffer memory, final int padding) {
|
||||||
|
completionService.submit(new Callable<Long>() {
|
||||||
|
public Long call() throws Exception {
|
||||||
|
return memoryLoop(index, repeats, memory, padding);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long waitForResults(final int count, final ExecutorCompletionService<Long> completionService) {
|
||||||
|
try {
|
||||||
|
long totalTime = 0;
|
||||||
|
for ( int i = 0; i < count; i++ )
|
||||||
|
totalTime += completionService.take().get();
|
||||||
|
return totalTime;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2002-2011 LWJGL Project
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are
|
||||||
|
* met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* * Neither the name of 'LWJGL' nor the names of
|
||||||
|
* its contributors may be used to endorse or promote products derived
|
||||||
|
* from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||||
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package org.lwjgl.util.mapped;
|
||||||
|
|
||||||
|
import org.lwjgl.LWJGLUtil;
|
||||||
|
import org.lwjgl.MemoryUtil;
|
||||||
|
import org.lwjgl.PointerBuffer;
|
||||||
|
|
||||||
|
import java.nio.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides utility methods for allocating cache-line-aligned
|
||||||
|
* NIO buffers. The CPU cache line size is detected using a micro-benchmark
|
||||||
|
* that exploits the performation degredation that occurs when different
|
||||||
|
* threads write to different locations of the same cache line. The detection
|
||||||
|
* should be reasonably robust on both the server and client VM, but there
|
||||||
|
* are a few system properties that can be used to tune it.
|
||||||
|
*
|
||||||
|
* @author Spasi
|
||||||
|
*/
|
||||||
|
public final class CacheUtil {
|
||||||
|
|
||||||
|
private static final int CACHE_LINE_SIZE;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final Integer size = LWJGLUtil.getPrivilegedInteger("org.lwjgl.util.mapped.CacheLineSize"); // forces a specific cache line size
|
||||||
|
|
||||||
|
if ( size != null ) {
|
||||||
|
if ( size < 1 )
|
||||||
|
throw new IllegalStateException("Invalid CacheLineSize specified: " + size);
|
||||||
|
CACHE_LINE_SIZE = size;
|
||||||
|
} else if ( Runtime.getRuntime().availableProcessors() == 1 ) { // We cannot use false sharing to detect it
|
||||||
|
/*
|
||||||
|
Spasi:
|
||||||
|
|
||||||
|
I have implemented a single-threaded benchmark for this, but it requires
|
||||||
|
lots of memory allocations and could not tune it for both the client and
|
||||||
|
server VM. It's not a big deal anyway, 64 bytes should be ok for any
|
||||||
|
single-core CPU.
|
||||||
|
*/
|
||||||
|
if ( LWJGLUtil.DEBUG )
|
||||||
|
LWJGLUtil.log("Cannot detect cache line size on single-core CPUs, assuming 64 bytes.");
|
||||||
|
CACHE_LINE_SIZE = 64;
|
||||||
|
} else
|
||||||
|
CACHE_LINE_SIZE = CacheLineSize.getCacheLineSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CacheUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the CPU cache line size, in number of bytes.
|
||||||
|
*
|
||||||
|
* @return the cache line size
|
||||||
|
*/
|
||||||
|
public static int getCacheLineSize() {
|
||||||
|
return CACHE_LINE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a direct, native-ordered and cache-line-aligned bytebuffer with the specified size.
|
||||||
|
*
|
||||||
|
* @param size The size, in bytes
|
||||||
|
*
|
||||||
|
* @return a ByteBuffer
|
||||||
|
*/
|
||||||
|
public static ByteBuffer createByteBuffer(int size) {
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocateDirect(size + CACHE_LINE_SIZE);
|
||||||
|
|
||||||
|
// Align to cache line.
|
||||||
|
if ( MemoryUtil.getAddress(buffer) % CACHE_LINE_SIZE != 0 ) {
|
||||||
|
// Round up to cache line boundary
|
||||||
|
buffer.position(CACHE_LINE_SIZE - (int)(MemoryUtil.getAddress(buffer) & (CACHE_LINE_SIZE - 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.limit(buffer.position() + size);
|
||||||
|
return buffer.slice().order(ByteOrder.nativeOrder());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a direct, native-ordered and cache-line-aligned shortbuffer with the specified number
|
||||||
|
* of elements.
|
||||||
|
*
|
||||||
|
* @param size The size, in shorts
|
||||||
|
*
|
||||||
|
* @return a ShortBuffer
|
||||||
|
*/
|
||||||
|
public static ShortBuffer createShortBuffer(int size) {
|
||||||
|
return createByteBuffer(size << 1).asShortBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a direct, native-ordered and cache-line-aligned charbuffer with the specified number
|
||||||
|
* of elements.
|
||||||
|
*
|
||||||
|
* @param size The size, in chars
|
||||||
|
*
|
||||||
|
* @return an CharBuffer
|
||||||
|
*/
|
||||||
|
public static CharBuffer createCharBuffer(int size) {
|
||||||
|
return createByteBuffer(size << 1).asCharBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a direct, native-ordered and cache-line-aligned intbuffer with the specified number
|
||||||
|
* of elements.
|
||||||
|
*
|
||||||
|
* @param size The size, in ints
|
||||||
|
*
|
||||||
|
* @return an IntBuffer
|
||||||
|
*/
|
||||||
|
public static IntBuffer createIntBuffer(int size) {
|
||||||
|
return createByteBuffer(size << 2).asIntBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a direct, native-ordered and cache-line-aligned longbuffer with the specified number
|
||||||
|
* of elements.
|
||||||
|
*
|
||||||
|
* @param size The size, in longs
|
||||||
|
*
|
||||||
|
* @return an LongBuffer
|
||||||
|
*/
|
||||||
|
public static LongBuffer createLongBuffer(int size) {
|
||||||
|
return createByteBuffer(size << 3).asLongBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a direct, native-ordered and cache-line-aligned floatbuffer with the specified number
|
||||||
|
* of elements.
|
||||||
|
*
|
||||||
|
* @param size The size, in floats
|
||||||
|
*
|
||||||
|
* @return a FloatBuffer
|
||||||
|
*/
|
||||||
|
public static FloatBuffer createFloatBuffer(int size) {
|
||||||
|
return createByteBuffer(size << 2).asFloatBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a direct, native-ordered and cache-line-aligned doublebuffer with the specified number
|
||||||
|
* of elements.
|
||||||
|
*
|
||||||
|
* @param size The size, in floats
|
||||||
|
*
|
||||||
|
* @return a FloatBuffer
|
||||||
|
*/
|
||||||
|
public static DoubleBuffer createDoubleBuffer(int size) {
|
||||||
|
return createByteBuffer(size << 3).asDoubleBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a cache-line-aligned PointerBuffer with the specified number
|
||||||
|
* of elements.
|
||||||
|
*
|
||||||
|
* @param size The size, in memory addresses
|
||||||
|
*
|
||||||
|
* @return a PointerBuffer
|
||||||
|
*/
|
||||||
|
public static PointerBuffer createPointerBuffer(int size) {
|
||||||
|
return new PointerBuffer(createByteBuffer(size * PointerBuffer.getPointerSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ public @interface MappedField {
|
||||||
*
|
*
|
||||||
* @return the field byte offset
|
* @return the field byte offset
|
||||||
*/
|
*/
|
||||||
long byteOffset();
|
long byteOffset() default -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specifies the field byte length. Required for {@link java.nio.ByteBuffer} fields.
|
* Specifies the field byte length. Required for {@link java.nio.ByteBuffer} fields.
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class MappedHelper {
|
||||||
mo.baseAddress = mo.viewAddress = addr;
|
mo.baseAddress = mo.viewAddress = addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkAddress(MappedObject mapped, long viewAddress) {
|
public static void checkAddress(long viewAddress, MappedObject mapped) {
|
||||||
mapped.checkAddress(viewAddress);
|
mapped.checkAddress(viewAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,7 +317,7 @@ public class MappedHelper {
|
||||||
return INSTANCE.getLong(addr);
|
return INSTANCE.getLong(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long lget(MappedObject mapped, int fieldOffset) {
|
public static long jget(MappedObject mapped, int fieldOffset) {
|
||||||
return INSTANCE.getLong(mapped.viewAddress + fieldOffset);
|
return INSTANCE.getLong(mapped.viewAddress + fieldOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +333,7 @@ public class MappedHelper {
|
||||||
return INSTANCE.getLongVolatile(null, addr);
|
return INSTANCE.getLongVolatile(null, addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long lvget(MappedObject mapped, int fieldOffset) {
|
public static long jvget(MappedObject mapped, int fieldOffset) {
|
||||||
return INSTANCE.getLongVolatile(null, mapped.viewAddress + fieldOffset);
|
return INSTANCE.getLongVolatile(null, mapped.viewAddress + fieldOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,11 +48,7 @@ import java.util.Arrays;
|
||||||
*/
|
*/
|
||||||
public class MappedObjectClassLoader extends URLClassLoader {
|
public class MappedObjectClassLoader extends URLClassLoader {
|
||||||
|
|
||||||
static final String MAPPEDOBJECT_PACKAGE_PREFIX;
|
static final String MAPPEDOBJECT_PACKAGE_PREFIX = MappedObjectClassLoader.class.getPackage().getName() + ".";
|
||||||
|
|
||||||
static {
|
|
||||||
MAPPEDOBJECT_PACKAGE_PREFIX = MappedObjectClassLoader.class.getPackage().getName() + ".";
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean FORKED;
|
static boolean FORKED;
|
||||||
|
|
||||||
|
@ -115,28 +111,28 @@ public class MappedObjectClassLoader extends URLClassLoader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||||
if ( name.startsWith("java.") )
|
if ( name.startsWith("java.")
|
||||||
return super.loadClass(name, resolve);
|
|| name.startsWith("javax.")
|
||||||
if ( name.startsWith("javax.") )
|
|| name.startsWith("sun.")
|
||||||
|
|| name.startsWith("sunw.")
|
||||||
|
|| name.startsWith("org.objectweb.asm.")
|
||||||
|
)
|
||||||
return super.loadClass(name, resolve);
|
return super.loadClass(name, resolve);
|
||||||
|
|
||||||
if ( name.startsWith("sun.") )
|
final String className = name.replace('.', '/');
|
||||||
return super.loadClass(name, resolve);
|
final boolean inThisPackage = name.startsWith(MAPPEDOBJECT_PACKAGE_PREFIX);
|
||||||
if ( name.startsWith("sunw.") )
|
|
||||||
return super.loadClass(name, resolve);
|
|
||||||
|
|
||||||
if ( name.startsWith("org.objectweb.asm.") )
|
if ( inThisPackage && (
|
||||||
|
name.equals(MappedObjectClassLoader.class.getName())
|
||||||
|
|| name.equals((MappedObjectTransformer.class.getName()))
|
||||||
|
|| name.equals((CacheUtil.class.getName()))
|
||||||
|
) )
|
||||||
return super.loadClass(name, resolve);
|
return super.loadClass(name, resolve);
|
||||||
|
|
||||||
if ( name.equals(MappedObjectClassLoader.class.getName()) || name.equals((MappedObjectTransformer.class.getName())) )
|
|
||||||
return super.loadClass(name, resolve);
|
|
||||||
|
|
||||||
String className = name.replace('.', '/');
|
|
||||||
|
|
||||||
byte[] bytecode = readStream(this.getResourceAsStream(className.concat(".class")));
|
byte[] bytecode = readStream(this.getResourceAsStream(className.concat(".class")));
|
||||||
|
|
||||||
// Classes in this package do not get transformed, but need to go through here because we have transformed MappedObject.
|
// Classes in this package do not get transformed, but need to go through here because we have transformed MappedObject.
|
||||||
if ( !(name.startsWith(MAPPEDOBJECT_PACKAGE_PREFIX) && name.substring(MAPPEDOBJECT_PACKAGE_PREFIX.length()).indexOf('.') == -1) ) {
|
if ( !(inThisPackage && name.substring(MAPPEDOBJECT_PACKAGE_PREFIX.length()).indexOf('.') == -1) ) {
|
||||||
long t0 = System.nanoTime();
|
long t0 = System.nanoTime();
|
||||||
final byte[] newBytecode = MappedObjectTransformer.transformMappedAPI(className, bytecode);
|
final byte[] newBytecode = MappedObjectTransformer.transformMappedAPI(className, bytecode);
|
||||||
long t1 = System.nanoTime();
|
long t1 = System.nanoTime();
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
*/
|
*/
|
||||||
package org.lwjgl.util.mapped;
|
package org.lwjgl.util.mapped;
|
||||||
|
|
||||||
|
import org.lwjgl.BufferUtils;
|
||||||
import org.lwjgl.LWJGLUtil;
|
import org.lwjgl.LWJGLUtil;
|
||||||
import org.lwjgl.MemoryUtil;
|
import org.lwjgl.MemoryUtil;
|
||||||
import org.objectweb.asm.*;
|
import org.objectweb.asm.*;
|
||||||
|
@ -79,6 +80,8 @@ public class MappedObjectTransformer {
|
||||||
static final String MAPPED_SET3_JVM = jvmClassName(MappedSet3.class);
|
static final String MAPPED_SET3_JVM = jvmClassName(MappedSet3.class);
|
||||||
static final String MAPPED_SET4_JVM = jvmClassName(MappedSet4.class);
|
static final String MAPPED_SET4_JVM = jvmClassName(MappedSet4.class);
|
||||||
|
|
||||||
|
static final String CACHE_LINE_PAD_JVM = "L" + jvmClassName(CacheLinePad.class) + ";";
|
||||||
|
|
||||||
// Public methods
|
// Public methods
|
||||||
static final String VIEWADDRESS_METHOD_NAME = "getViewAddress";
|
static final String VIEWADDRESS_METHOD_NAME = "getViewAddress";
|
||||||
static final String NEXT_METHOD_NAME = "next";
|
static final String NEXT_METHOD_NAME = "next";
|
||||||
|
@ -115,7 +118,7 @@ public class MappedObjectTransformer {
|
||||||
// => IADD
|
// => IADD
|
||||||
// => PUTFIELD MyMappedType.view
|
// => PUTFIELD MyMappedType.view
|
||||||
//
|
//
|
||||||
className_to_subtype.put(MAPPED_OBJECT_JVM, new MappedSubtypeInfo(MAPPED_OBJECT_JVM, null, -1, -1, -1));
|
className_to_subtype.put(MAPPED_OBJECT_JVM, new MappedSubtypeInfo(MAPPED_OBJECT_JVM, null, -1, -1, -1, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
final String vmName = System.getProperty("java.vm.name");
|
final String vmName = System.getProperty("java.vm.name");
|
||||||
|
@ -145,30 +148,44 @@ public class MappedObjectTransformer {
|
||||||
final String className = jvmClassName(type);
|
final String className = jvmClassName(type);
|
||||||
final Map<String, FieldInfo> fields = new HashMap<String, FieldInfo>();
|
final Map<String, FieldInfo> fields = new HashMap<String, FieldInfo>();
|
||||||
|
|
||||||
int advancingOffset = 0;
|
|
||||||
long sizeof = 0;
|
long sizeof = 0;
|
||||||
for ( Field field : type.getDeclaredFields() ) {
|
for ( Field field : type.getDeclaredFields() ) {
|
||||||
FieldInfo fieldInfo = registerField(mapped == null || mapped.autoGenerateOffsets(), className, advancingOffset, field);
|
FieldInfo fieldInfo = registerField(mapped == null || mapped.autoGenerateOffsets(), className, sizeof, field);
|
||||||
if ( fieldInfo == null )
|
if ( fieldInfo == null )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
fields.put(field.getName(), fieldInfo);
|
fields.put(field.getName(), fieldInfo);
|
||||||
|
|
||||||
advancingOffset += fieldInfo.length;
|
sizeof = Math.max(sizeof, fieldInfo.offset + fieldInfo.lengthPadded);
|
||||||
sizeof = Math.max(sizeof, fieldInfo.offset + fieldInfo.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final int align = mapped == null ? 4 : mapped.align();
|
int align = 4;
|
||||||
final int padding = mapped == null ? 0 : mapped.padding();
|
int padding = 0;
|
||||||
|
boolean cacheLinePadded = false;
|
||||||
|
|
||||||
|
if ( mapped != null ) {
|
||||||
|
align = mapped.align();
|
||||||
|
if ( mapped.cacheLinePadding() ) {
|
||||||
|
if ( mapped.padding() != 0 )
|
||||||
|
throw new ClassFormatError("Mapped type padding cannot be specified together with cacheLinePadding.");
|
||||||
|
|
||||||
|
final int cacheLineMod = (int)(sizeof % CacheUtil.getCacheLineSize());
|
||||||
|
if ( cacheLineMod != 0 )
|
||||||
|
padding = CacheUtil.getCacheLineSize() - cacheLineMod;
|
||||||
|
|
||||||
|
cacheLinePadded = true;
|
||||||
|
} else
|
||||||
|
padding = mapped.padding();
|
||||||
|
}
|
||||||
|
|
||||||
sizeof += padding;
|
sizeof += padding;
|
||||||
|
|
||||||
final MappedSubtypeInfo mappedType = new MappedSubtypeInfo(className, fields, (int)sizeof, align, padding);
|
final MappedSubtypeInfo mappedType = new MappedSubtypeInfo(className, fields, (int)sizeof, align, padding, cacheLinePadded);
|
||||||
if ( className_to_subtype.put(className, mappedType) != null )
|
if ( className_to_subtype.put(className, mappedType) != null )
|
||||||
throw new InternalError("duplicate mapped type: " + mappedType.className);
|
throw new InternalError("duplicate mapped type: " + mappedType.className);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FieldInfo registerField(final boolean autoGenerateOffsets, final String className, int advancingOffset, final Field field) {
|
private static FieldInfo registerField(final boolean autoGenerateOffsets, final String className, long advancingOffset, final Field field) {
|
||||||
if ( Modifier.isStatic(field.getModifiers()) ) // static fields are never mapped
|
if ( Modifier.isStatic(field.getModifiers()) ) // static fields are never mapped
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -188,7 +205,6 @@ public class MappedObjectTransformer {
|
||||||
throw new ClassFormatError("The volatile keyword is not supported for @Pointer or ByteBuffer fields. Volatile field found: " + className + "." + field.getName() + ": " + field.getType());
|
throw new ClassFormatError("The volatile keyword is not supported for @Pointer or ByteBuffer fields. Volatile field found: " + className + "." + field.getName() + ": " + field.getType());
|
||||||
|
|
||||||
// quick hack
|
// quick hack
|
||||||
long byteOffset = meta == null ? advancingOffset : meta.byteOffset();
|
|
||||||
long byteLength;
|
long byteLength;
|
||||||
if ( field.getType() == long.class || field.getType() == double.class ) {
|
if ( field.getType() == long.class || field.getType() == double.class ) {
|
||||||
if ( pointer == null )
|
if ( pointer == null )
|
||||||
|
@ -213,10 +229,36 @@ public class MappedObjectTransformer {
|
||||||
if ( field.getType() != ByteBuffer.class && (advancingOffset % byteLength) != 0 )
|
if ( field.getType() != ByteBuffer.class && (advancingOffset % byteLength) != 0 )
|
||||||
throw new IllegalStateException("misaligned mapped type: " + className + "." + field.getName());
|
throw new IllegalStateException("misaligned mapped type: " + className + "." + field.getName());
|
||||||
|
|
||||||
|
CacheLinePad pad = field.getAnnotation(CacheLinePad.class);
|
||||||
|
|
||||||
|
long byteOffset = advancingOffset;
|
||||||
|
if ( meta != null && meta.byteOffset() != -1 ) {
|
||||||
|
if ( meta.byteOffset() < 0 )
|
||||||
|
throw new ClassFormatError("Invalid field byte offset: " + className + "." + field.getName() + " [byteOffset=" + meta.byteOffset() + "]");
|
||||||
|
if ( pad != null )
|
||||||
|
throw new ClassFormatError("A field byte offset cannot be specified together with cache-line padding: " + className + "." + field.getName());
|
||||||
|
|
||||||
|
byteOffset = meta.byteOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
long byteLengthPadded = byteLength;
|
||||||
|
if ( pad != null ) {
|
||||||
|
// Pad before
|
||||||
|
if ( pad.before() && byteOffset % CacheUtil.getCacheLineSize() != 0 )
|
||||||
|
byteOffset += CacheUtil.getCacheLineSize() - (byteOffset & (CacheUtil.getCacheLineSize() - 1));
|
||||||
|
|
||||||
|
// Pad after
|
||||||
|
if ( pad.after() && (byteOffset + byteLength) % CacheUtil.getCacheLineSize() != 0 )
|
||||||
|
byteLengthPadded += CacheUtil.getCacheLineSize() - (byteOffset + byteLength) % CacheUtil.getCacheLineSize();
|
||||||
|
|
||||||
|
assert !pad.before() || (byteOffset % CacheUtil.getCacheLineSize() == 0);
|
||||||
|
assert !pad.after() || ((byteOffset + byteLengthPadded) % CacheUtil.getCacheLineSize() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
if ( PRINT_ACTIVITY )
|
if ( PRINT_ACTIVITY )
|
||||||
LWJGLUtil.log(MappedObjectTransformer.class.getSimpleName() + ": " + className + "." + field.getName() + " [type=" + field.getType().getSimpleName() + ", offset=" + byteOffset + "]");
|
LWJGLUtil.log(MappedObjectTransformer.class.getSimpleName() + ": " + className + "." + field.getName() + " [type=" + field.getType().getSimpleName() + ", offset=" + byteOffset + "]");
|
||||||
|
|
||||||
return new FieldInfo(byteOffset, byteLength, Type.getType(field.getType()), Modifier.isVolatile(field.getModifiers()), pointer != null);
|
return new FieldInfo(byteOffset, byteLength, byteLengthPadded, Type.getType(field.getType()), Modifier.isVolatile(field.getModifiers()), pointer != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Removes final from methods that will be overriden by subclasses. */
|
/** Removes final from methods that will be overriden by subclasses. */
|
||||||
|
@ -318,17 +360,12 @@ public class MappedObjectTransformer {
|
||||||
mv.visitInsn(I2L);
|
mv.visitInsn(I2L);
|
||||||
mv.visitInsn(LADD);
|
mv.visitInsn(LADD);
|
||||||
if ( MappedObject.CHECKS ) {
|
if ( MappedObject.CHECKS ) {
|
||||||
mv.visitVarInsn(LSTORE, 2);
|
mv.visitInsn(DUP2);
|
||||||
mv.visitVarInsn(ALOAD, 0);
|
mv.visitVarInsn(ALOAD, 0);
|
||||||
mv.visitVarInsn(LLOAD, 2);
|
mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "checkAddress", "(JL" + MAPPED_OBJECT_JVM + ";)V");
|
||||||
mv.visitMethodInsn(INVOKESTATIC, MAPPED_HELPER_JVM, "checkAddress", "(L" + MAPPED_OBJECT_JVM + ";J)V");
|
|
||||||
mv.visitVarInsn(LLOAD, 2);
|
|
||||||
}
|
}
|
||||||
mv.visitInsn(LRETURN);
|
mv.visitInsn(LRETURN);
|
||||||
if ( MappedObject.CHECKS )
|
mv.visitMaxs(3, 2);
|
||||||
mv.visitMaxs(3, 4);
|
|
||||||
else
|
|
||||||
mv.visitMaxs(3, 2);
|
|
||||||
mv.visitEnd();
|
mv.visitEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,7 +514,71 @@ public class MappedObjectTransformer {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.visitField(access, name, desc, signature, value);
|
if ( (access & ACC_STATIC) == 0 ) {
|
||||||
|
return new FieldNode(access, name, desc, signature, value) {
|
||||||
|
public void visitEnd() {
|
||||||
|
if ( visibleAnnotations == null ) { // early-out
|
||||||
|
accept(cv);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean before = false;
|
||||||
|
boolean after = false;
|
||||||
|
int byteLength = 0;
|
||||||
|
for ( AnnotationNode pad : visibleAnnotations ) {
|
||||||
|
if ( CACHE_LINE_PAD_JVM.equals(pad.desc) ) {
|
||||||
|
if ( "J".equals(desc) || "D".equals(desc) )
|
||||||
|
byteLength = 8;
|
||||||
|
else if ( "I".equals(desc) || "F".equals(desc) )
|
||||||
|
byteLength = 4;
|
||||||
|
else if ( "S".equals(desc) || "C".equals(desc) )
|
||||||
|
byteLength = 2;
|
||||||
|
else if ( "B".equals(desc) || "Z".equals(desc) )
|
||||||
|
byteLength = 1;
|
||||||
|
else
|
||||||
|
throw new ClassFormatError("The @CacheLinePad annotation cannot be used on non-primitive fields: " + className + "." + name);
|
||||||
|
|
||||||
|
transformed = true;
|
||||||
|
|
||||||
|
after = true;
|
||||||
|
if ( pad.values != null ) {
|
||||||
|
for ( int i = 0; i < pad.values.size(); i += 2 ) {
|
||||||
|
final boolean value = pad.values.get(i + 1).equals(Boolean.TRUE);
|
||||||
|
if ( "before".equals(pad.values.get(i)) )
|
||||||
|
before = value;
|
||||||
|
else
|
||||||
|
after = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
We make the fields public to force the JVM to keep the fields in the object.
|
||||||
|
Instead of using only longs or integers, we use the same type as the original
|
||||||
|
field. That's because modern JVMs usually reorder fields by type:
|
||||||
|
longs, then doubles, then integers, then booleans, etc. This way it's more
|
||||||
|
likely that the padding will work as expected.
|
||||||
|
*/
|
||||||
|
|
||||||
|
if ( before ) {
|
||||||
|
final int count = CacheUtil.getCacheLineSize() / byteLength - 1;
|
||||||
|
for ( int i = count; i >= 1; i-- )
|
||||||
|
cv.visitField(access | ACC_PUBLIC | ACC_SYNTHETIC, name + "$PAD_" + i, desc, signature, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
accept(cv);
|
||||||
|
|
||||||
|
if ( after ) {
|
||||||
|
final int count = CacheUtil.getCacheLineSize() / byteLength - 1;
|
||||||
|
for ( int i = 1; i <= count; i++ )
|
||||||
|
cv.visitField(access | ACC_PUBLIC | ACC_SYNTHETIC, name + "$PAD" + i, desc, signature, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else
|
||||||
|
return super.visitField(access, name, desc, signature, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -762,7 +863,7 @@ public class MappedObjectTransformer {
|
||||||
// stack: sizeof, count
|
// stack: sizeof, count
|
||||||
trg.add(new InsnNode(IMUL));
|
trg.add(new InsnNode(IMUL));
|
||||||
// stack: bytes
|
// stack: bytes
|
||||||
trg.add(new MethodInsnNode(INVOKESTATIC, jvmClassName(ByteBuffer.class), "allocateDirect", "(I)L" + jvmClassName(ByteBuffer.class) + ";"));
|
trg.add(new MethodInsnNode(INVOKESTATIC, mappedType.cacheLinePadded ? jvmClassName(CacheUtil.class) : jvmClassName(BufferUtils.class), "createByteBuffer", "(I)L" + jvmClassName(ByteBuffer.class) + ";"));
|
||||||
// stack: buffer
|
// stack: buffer
|
||||||
} else if ( mapDirectMethod ) {
|
} else if ( mapDirectMethod ) {
|
||||||
// stack: capacity, address
|
// stack: capacity, address
|
||||||
|
@ -1061,13 +1162,15 @@ public class MappedObjectTransformer {
|
||||||
|
|
||||||
final long offset;
|
final long offset;
|
||||||
final long length;
|
final long length;
|
||||||
|
final long lengthPadded;
|
||||||
final Type type;
|
final Type type;
|
||||||
final boolean isVolatile;
|
final boolean isVolatile;
|
||||||
final boolean isPointer;
|
final boolean isPointer;
|
||||||
|
|
||||||
FieldInfo(final long offset, final long length, final Type type, final boolean isVolatile, final boolean isPointer) {
|
FieldInfo(final long offset, final long length, final long lengthPadded, final Type type, final boolean isVolatile, final boolean isPointer) {
|
||||||
this.offset = offset;
|
this.offset = offset;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
|
this.lengthPadded = lengthPadded;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.isVolatile = isVolatile;
|
this.isVolatile = isVolatile;
|
||||||
this.isPointer = isPointer;
|
this.isPointer = isPointer;
|
||||||
|
@ -1083,14 +1186,15 @@ public class MappedObjectTransformer {
|
||||||
|
|
||||||
final String className;
|
final String className;
|
||||||
|
|
||||||
final int sizeof;
|
final int sizeof;
|
||||||
final int sizeof_shift;
|
final int sizeof_shift;
|
||||||
final int align;
|
final int align;
|
||||||
final int padding;
|
final int padding;
|
||||||
|
final boolean cacheLinePadded;
|
||||||
|
|
||||||
final Map<String, FieldInfo> fields;
|
final Map<String, FieldInfo> fields;
|
||||||
|
|
||||||
MappedSubtypeInfo(String className, Map<String, FieldInfo> fields, int sizeof, int align, int padding) {
|
MappedSubtypeInfo(String className, Map<String, FieldInfo> fields, int sizeof, int align, int padding, final boolean cacheLinePadded) {
|
||||||
this.className = className;
|
this.className = className;
|
||||||
|
|
||||||
this.sizeof = sizeof;
|
this.sizeof = sizeof;
|
||||||
|
@ -1100,6 +1204,7 @@ public class MappedObjectTransformer {
|
||||||
this.sizeof_shift = 0;
|
this.sizeof_shift = 0;
|
||||||
this.align = align;
|
this.align = align;
|
||||||
this.padding = padding;
|
this.padding = padding;
|
||||||
|
this.cacheLinePadded = cacheLinePadded;
|
||||||
|
|
||||||
this.fields = fields;
|
this.fields = fields;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ package org.lwjgl.util.mapped;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
import sun.misc.Unsafe;
|
import sun.misc.Unsafe;
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ final class MappedObjectUnsafe {
|
||||||
if ( address <= 0L || capacity < 0 )
|
if ( address <= 0L || capacity < 0 )
|
||||||
throw new IllegalStateException("you almost crashed the jvm");
|
throw new IllegalStateException("you almost crashed the jvm");
|
||||||
|
|
||||||
ByteBuffer buffer = global.duplicate();
|
ByteBuffer buffer = global.duplicate().order(ByteOrder.nativeOrder());
|
||||||
INSTANCE.putLong(buffer, BUFFER_ADDRESS_OFFSET, address);
|
INSTANCE.putLong(buffer, BUFFER_ADDRESS_OFFSET, address);
|
||||||
INSTANCE.putInt(buffer, BUFFER_CAPACITY_OFFSET, capacity);
|
INSTANCE.putInt(buffer, BUFFER_CAPACITY_OFFSET, capacity);
|
||||||
buffer.position(0);
|
buffer.position(0);
|
||||||
|
|
|
@ -61,12 +61,27 @@ public @interface MappedType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of bytes to add to the total byte size.
|
* The number of bytes to add to the total byte size.
|
||||||
* SIZEOF will be calculated as <code>SIZEOF = max(field_offset + field_length) + padding</code>
|
* SIZEOF will be calculated as <code>SIZEOF = max(field_offset + field_length) + padding</code>.
|
||||||
|
* <p/>
|
||||||
|
* Cannot be used with {@link #cacheLinePadding()}.
|
||||||
*
|
*
|
||||||
* @return the padding amount
|
* @return the padding amount
|
||||||
*/
|
*/
|
||||||
int padding() default 0;
|
int padding() default 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When true, SIZEOF will be increased (if necessary) so that it's a multiple of the CPU cache line size.
|
||||||
|
* Additionally, {@link MappedObject#malloc(int)} on the mapped object type will automatically use
|
||||||
|
* {@link CacheUtil#createByteBuffer(int)} instead of the unaligned {@link org.lwjgl.BufferUtils#createByteBuffer(int)}.
|
||||||
|
* <p/>
|
||||||
|
* Cannot be used with {@link #padding()}.
|
||||||
|
*
|
||||||
|
* @return if cache-line padding should be applied
|
||||||
|
*
|
||||||
|
* @see CacheUtil
|
||||||
|
*/
|
||||||
|
boolean cacheLinePadding() default false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The mapped data memory alignment, in bytes.
|
* The mapped data memory alignment, in bytes.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue