Skip to content

Commit

Permalink
Use MappedByteBuffer to load image.
Browse files Browse the repository at this point in the history
This simplifies the code, speeds up image loading, and reduces memory pressure at startup by mapping the image file into unmanaged memory.
  • Loading branch information
fniephaus committed Oct 20, 2024
1 parent 9ad5043 commit d3c8e9b
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
*/
package de.hpi.swa.trufflesqueak.image;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.HashSet;
import java.util.logging.Level;
Expand All @@ -31,7 +35,7 @@
import de.hpi.swa.trufflesqueak.util.ArrayUtils;
import de.hpi.swa.trufflesqueak.util.LogUtils;
import de.hpi.swa.trufflesqueak.util.MiscUtils;
import de.hpi.swa.trufflesqueak.util.VarHandleUtils;
import de.hpi.swa.trufflesqueak.util.UnsafeUtils;

public final class SqueakImageReader {
private static final byte[] EMPTY_BYTES = new byte[0];
Expand All @@ -40,14 +44,9 @@ public final class SqueakImageReader {

public final AddressToChunkMap chunkMap = new AddressToChunkMap();
public final SqueakImageContext image;
private final byte[] byteArrayBuffer = new byte[Long.BYTES];

private long oldBaseAddress;
private int headerSize;
private long specialObjectsPointer;
private long firstSegmentSize;
private int position;
private long currentAddressSwizzle;
private long dataSize;

private SqueakImageChunk freePageList;

Expand All @@ -72,9 +71,11 @@ private Object run() {
if (!truffleFile.isRegularFile()) {
throw SqueakException.create(MiscUtils.format("Image at '%s' does not exist.", image.getImagePath()));
}
try (BufferedInputStream inputStream = new BufferedInputStream(truffleFile.newInputStream())) {
readHeader(inputStream);
readBody(inputStream);
try (var channel = FileChannel.open(Path.of(image.getImagePath()), StandardOpenOption.READ)) {
final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
buffer.order(ByteOrder.LITTLE_ENDIAN);
readImage(buffer);
UnsafeUtils.invokeCleaner(buffer);
} catch (final IOException e) {
throw SqueakException.create("Failed to read Smalltalk image:", e.getMessage());
}
Expand All @@ -84,128 +85,86 @@ private Object run() {
return image.getSqueakImage();
}

private static void readBytes(final BufferedInputStream stream, final byte[] bytes, final int length) throws IOException {
final int readBytes = stream.read(bytes, 0, length);
assert readBytes == length : "Failed to read bytes";
private void skip(final MappedByteBuffer buffer, final int numBytes) {
buffer.position(buffer.position() + numBytes);
}

private long nextWord(final BufferedInputStream stream) throws IOException {
return nextLong(stream);
}

private short nextShort(final BufferedInputStream stream) throws IOException {
readBytes(stream, byteArrayBuffer, Short.BYTES);
position += Short.BYTES;
return VarHandleUtils.getShort(byteArrayBuffer, 0);
}

private int nextInt(final BufferedInputStream stream) throws IOException {
readBytes(stream, byteArrayBuffer, Integer.BYTES);
position += Integer.BYTES;
return VarHandleUtils.getInt(byteArrayBuffer, 0);
}

private long nextLong(final BufferedInputStream stream) throws IOException {
readBytes(stream, byteArrayBuffer, Long.BYTES);
position += Long.BYTES;
return VarHandleUtils.getLong(byteArrayBuffer, 0);
}

private byte[] nextObjectData(final BufferedInputStream stream, final int size, final int format) throws IOException {
if (size == 0) {
skipBytes(stream, SqueakImageConstants.WORD_SIZE); // skip trailing alignment word
return EMPTY_BYTES;
}
final int paddedObjectSize = size * SqueakImageConstants.WORD_SIZE;
final int padding = calculateObjectPadding(format);
final int objectDataSize = paddedObjectSize - padding;
final byte[] bytes = new byte[objectDataSize];
readBytes(stream, bytes, objectDataSize);
final long skipped = stream.skip(padding);
assert skipped == padding : "Failed to skip padding bytes";
position += paddedObjectSize;
return bytes;
}

private void skipBytes(final BufferedInputStream stream, final int count) throws IOException {
long pending = count;
while (pending > 0) {
final long skipped = stream.skip(pending);
assert skipped > 0 : "Nothing skipped, reached EOF?";
pending -= skipped;
}
position += count;
}

private void readHeader(final BufferedInputStream stream) throws IOException {
image.imageFormat = nextInt(stream);
private void readImage(final MappedByteBuffer buffer) {
// Read header
image.imageFormat = buffer.getInt();
if (!ArrayUtils.contains(SqueakImageConstants.SUPPORTED_IMAGE_FORMATS, image.imageFormat)) {
throw SqueakException.create(MiscUtils.format("Image format %s not supported. Please supply a compatible 64bit Spur image (%s).", image.imageFormat,
Arrays.toString(SqueakImageConstants.SUPPORTED_IMAGE_FORMATS)));
}

// Base header start
final int headerSize = nextInt(stream);
dataSize = nextWord(stream);
oldBaseAddress = nextWord(stream);
specialObjectsPointer = nextWord(stream);
nextWord(stream); // 1 word last used hash
final long snapshotScreenSize = nextWord(stream);
final long headerFlags = nextWord(stream);
nextInt(stream); // extraVMMemory
headerSize = buffer.getInt();
final long dataSize = buffer.getLong();
final long oldBaseAddress = buffer.getLong();
specialObjectsPointer = buffer.getLong();
// 1 word last used hash
buffer.getLong();
final long snapshotScreenSize = buffer.getLong();
final long headerFlags = buffer.getLong();
// extraVMMemory
buffer.getInt();

// Spur header start
nextShort(stream); // numStackPages
nextShort(stream); // cogCodeSize
assert position == 64 : "Wrong position";
nextInt(stream); // edenBytes
final short maxExternalSemaphoreTableSize = nextShort(stream);
nextShort(stream); // unused, realign to word boundary
assert position == 72 : "Wrong position";
firstSegmentSize = nextWord(stream);
nextWord(stream); // freeOldSpace
// numStackPages
buffer.getShort();
// cogCodeSize
buffer.getShort();
assert buffer.position() == 64 : "Wrong position";
// edenBytes
buffer.getInt();
final short maxExternalSemaphoreTableSize = buffer.getShort();
// unused, realign to word boundary
buffer.getShort();
assert buffer.position() == 72 : "Wrong position";
final long firstSegmentSize = buffer.getLong();
// freeOldSpace
buffer.getLong();

image.flags.initialize(oldBaseAddress, headerFlags, snapshotScreenSize, maxExternalSemaphoreTableSize);

skipBytes(stream, headerSize - position); // skip to body
}
skip(buffer, headerSize - buffer.position());
assert buffer.position() == headerSize;

private void readBody(final BufferedInputStream stream) throws IOException {
position = 0;
long segmentEnd = firstSegmentSize;
currentAddressSwizzle = oldBaseAddress;
while (position < segmentEnd) {
while (position < segmentEnd - SqueakImageConstants.IMAGE_BRIDGE_SIZE) {
final SqueakImageChunk chunk = readObject(stream);
// Read body
long segmentEnd = headerSize + firstSegmentSize;
long currentAddressSwizzle = oldBaseAddress;
while (buffer.position() < segmentEnd) {
while (buffer.position() < segmentEnd - SqueakImageConstants.IMAGE_BRIDGE_SIZE) {
final SqueakImageChunk chunk = readObject(buffer);
chunkMap.put(chunk.getPosition() + currentAddressSwizzle, chunk);
}
assert hiddenRootsChunk != null : "hiddenRootsChunk must be known from now on.";
final long bridge = nextLong(stream);
final long bridge = buffer.getLong();
long bridgeSpan = 0;
if ((bridge & SqueakImageConstants.SLOTS_MASK) != 0) {
bridgeSpan = bridge & ~SqueakImageConstants.SLOTS_MASK;
}
final long nextSegmentSize = nextLong(stream);
assert bridgeSpan >= 0 && nextSegmentSize >= 0 && position == segmentEnd;
final long nextSegmentSize = buffer.getLong();
assert bridgeSpan >= 0 && nextSegmentSize >= 0 && buffer.position() == segmentEnd;
if (nextSegmentSize == 0) {
break;
}
segmentEnd += nextSegmentSize;
currentAddressSwizzle += bridgeSpan * SqueakImageConstants.WORD_SIZE;
}
assert dataSize == position;
assert buffer.position() == headerSize + dataSize;
}

private SqueakImageChunk readObject(final BufferedInputStream stream) throws IOException {
int pos = position;
private SqueakImageChunk readObject(final MappedByteBuffer buffer) {
int pos = buffer.position() - headerSize;
assert pos % SqueakImageConstants.WORD_SIZE == 0 : "every object must be 64-bit aligned: " + pos % SqueakImageConstants.WORD_SIZE;
long headerWord = nextLong(stream);
long headerWord = buffer.getLong();
int numSlots = SqueakImageConstants.ObjectHeader.getNumSlots(headerWord);
if (numSlots == SqueakImageConstants.OVERFLOW_SLOTS) {
numSlots = (int) (headerWord & ~SqueakImageConstants.SLOTS_MASK);
assert numSlots >= SqueakImageConstants.OVERFLOW_SLOTS;
pos = position;
headerWord = nextLong(stream);
pos = buffer.position() - headerSize;
headerWord = buffer.getLong();
assert SqueakImageConstants.ObjectHeader.getNumSlots(headerWord) == SqueakImageConstants.OVERFLOW_SLOTS : "Objects with long header must have 255 in slot count";
}
final int size = numSlots;
Expand All @@ -215,10 +174,10 @@ private SqueakImageChunk readObject(final BufferedInputStream stream) throws IOE
if (ignoreObjectData(headerWord, classIndex, size)) {
/* Skip some hidden objects for performance reasons. */
objectData = null;
skipBytes(stream, size * SqueakImageConstants.WORD_SIZE);
skip(buffer, size * SqueakImageConstants.WORD_SIZE);
} else {
final int format = SqueakImageConstants.ObjectHeader.getFormat(headerWord);
objectData = nextObjectData(stream, size, format);
objectData = nextObjectData(buffer, size, format);
}
final SqueakImageChunk chunk = new SqueakImageChunk(this, headerWord, pos, objectData);
if (hiddenRootsChunk == null && isHiddenObject(classIndex)) {
Expand All @@ -234,6 +193,20 @@ private SqueakImageChunk readObject(final BufferedInputStream stream) throws IOE
return chunk;
}

private byte[] nextObjectData(final MappedByteBuffer buffer, final int size, final int format) {
if (size == 0) {
skip(buffer, SqueakImageConstants.WORD_SIZE); // skip trailing alignment word
return EMPTY_BYTES;
}
final int paddedObjectSize = size * SqueakImageConstants.WORD_SIZE;
final int padding = calculateObjectPadding(format);
final int objectDataSize = paddedObjectSize - padding;
final byte[] bytes = new byte[objectDataSize];
buffer.get(bytes);
skip(buffer, padding);
return bytes;
}

protected static boolean ignoreObjectData(final long headerWord, final int classIndex, final int size) {
return isFreeObject(classIndex) || isObjectStack(classIndex, size) || isHiddenObject(classIndex) && SqueakImageConstants.ObjectHeader.isPinned(headerWord);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package de.hpi.swa.trufflesqueak.util;

import java.lang.reflect.Field;
import java.nio.ByteBuffer;

import com.oracle.truffle.api.CompilerDirectives;

Expand Down Expand Up @@ -454,4 +455,8 @@ public static short[] toShorts(final long[] longs) {
UNSAFE.copyMemory(longs, Unsafe.ARRAY_LONG_BASE_OFFSET, shorts, Unsafe.ARRAY_SHORT_BASE_OFFSET, numBytes);
return shorts;
}

public static void invokeCleaner(final ByteBuffer directBuffer) {
UNSAFE.invokeCleaner(directBuffer);
}
}

1 comment on commit d3c8e9b

@TruffleSqueak-Bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance Report (d3c8e9b)

Benchmarks ran on 22.0.2-graal.

Steady (after 100 iterations)

Benchmark Name Min Geomean Median Mean Max Total (ms) Total (min)
Bounce 516 525 518.24 517 518.23 103647 1.73
CD 487 498 491.59 490 491.57 98317 1.64
DeltaBlue 276 477 412.82 407 411.45 82563 1.38
Havlak 1131 1183 1160.66 1165 1160.6 232132 3.87
Json 384 399 386.8 385 386.78 77359 1.29
List 307 320 308.15 308 308.15 61631 1.03
Mandelbrot 127 140 127.81 127.5 127.8 25562 0.43
NBody 251 266 254.19 252 254.16 50837 0.85
Permute 154 172 155.63 155 155.61 31125 0.52
Queens 229 240 231.55 231 231.54 46309 0.77
Richards 1236 1251 1239.08 1238 1239.08 247816 4.13
Sieve 177 187 178.16 178 178.15 35632 0.59
Storage 141 155 142.76 141 142.73 28551 0.48
Towers 197 220 197.85 197 197.83 39569 0.66
5613 6033 5805.25 5791.5 5803.68 1161050 19.35

d3c8e9b-2-steady.svg

Warmup (first 100 iterations)

d3c8e9b-3-warmup.svg

Please sign in to comment.