Skip to content

Commit

Permalink
Optional unsafe array access and endian-specific accessors
Browse files Browse the repository at this point in the history
  • Loading branch information
szeiger committed Nov 25, 2024
1 parent b11fd32 commit f405a59
Show file tree
Hide file tree
Showing 10 changed files with 669 additions and 142 deletions.
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@ Another source of performance is not just making the available abstractions fast

- Binary formats often use length-prefixed blocks. perfIO provides length-limited views for reading them (at essentially zero cost), and advanced buffer management for writing a length prefix after the content without double buffering or manual buffer management.

## Setup

Add the dependency to your project. Check the [Maven Central page](https://central.sonatype.com/artifact/com.novocode/perfio) for the latest versions and other dependency formats.

```
<dependency>
<groupId>com.novocode</groupId>
<artifactId>perfio</artifactId>
<version>0.1.0</version>
</dependency>
```

The minimum required JDK version is 21 with `--enable-preview` (for the FFM API), or 22 without. There are no other dependencies.

- The Vector incubator API will be used automatically if it has been enabled with `--add-modules jdk.incubator.vector` and the JDK and CPU have appropriate support. Use of the Vector API can be disabled with `-Dperfio.disableVectorized=true`.

- JDK-internal String features will be used automatically if the `java.lang` package has been made accessible with `--add-opens java.base/java.lang=ALL-UNNAMED`. This can be disabled with `-Dperfio.disableStringInternals=true`.

- Unsafe memory access is disabled by default. It can improve the performance in some cases but result in less optimized code in others. Both `-Dperfio.enableUnsafe=true` and `--enable-native-access=ALL-UNNAMED` are required to enable it.

## Usage

A top-level `BufferedInput` or `BufferedOutput` object is instantiated by calling one of the static factory methods in the respective class. It should be closed after use by calling `close()`.
Expand Down Expand Up @@ -76,7 +96,9 @@ Since Java does not have unsigned integers, the main methods for reading and wri
| float32 | 32 bits floating-point | float |
| float64 | 64 bits floating-point | double |

The methods for reading and writing multi-byte numeric values require a byte order. All factory methods set it to `BIG_ENDIAN` by default, but it can be changed at any time with the `order` method. This is consistent with `ByteBuffer` but different from the FFM API (which is mostly intended for interacting with native code and consequently uses the native byte order by default).
The methods for reading and writing multi-byte numeric values require a byte order. Most factory methods set it to `BIG_ENDIAN` by default, but it can be changed at any time with the `order` method. This is consistent with `ByteBuffer` but different from the FFM API (which is mostly intended for interacting with native code and consequently uses the native byte order by default).

All methods except `int8` have additional variants ending in `n` (e.g. `int32n`), `b` and `l` for native, big endian and little endian byte order respectively. These methods are independent of the `BufferedInput`'s or `BufferedOutput`'s current byte order and slightly faster.

### Views

Expand Down
64 changes: 58 additions & 6 deletions core/src/main/java/perfio/BufferedInput.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package perfio;

import perfio.internal.BufferUtil;
import perfio.internal.MemoryAccessor;

import java.io.Closeable;
import java.io.EOFException;
Expand Down Expand Up @@ -241,23 +242,74 @@ public boolean hasMore() throws IOException {
return pos < lim;
}

/// Read a signed 8-bit integer (`byte`).
public abstract byte int8() throws IOException;

/// Read an unsigned 8-bit integer into the lower 8 bits of an `int`.
public final int uint8() throws IOException { return int8() & 0xFF; }

/// Read a signed 16-bit integer (`short`) in the current byte [#order()].
public abstract short int16() throws IOException;

/// Read a signed 16-bit integer (`short`) in the native byte order.
public abstract short int16n() throws IOException;
/// Read a signed 16-bit integer (`short`) in big endian byte order.
public abstract short int16b() throws IOException;
/// Read a signed 16-bit integer (`short`) in little endian byte order.
public abstract short int16l() throws IOException;

/// Read an unsigned 16-bit integer (`char`) in the current byte [#order()].
public abstract char uint16() throws IOException;

/// Read an unsigned 16-bit integer (`char`) in the native byte order.
public abstract char uint16n() throws IOException;
/// Read an unsigned 16-bit integer (`char`) in big endian byte order.
public abstract char uint16b() throws IOException;
/// Read an unsigned 16-bit integer (`char`) in little endian byte order.
public abstract char uint16l() throws IOException;

/// Read a signed 32-bit integer (`int`) in the current byte [#order()].
public abstract int int32() throws IOException;

/// Read a signed 32-bit integer (`int`) in the native byte order.
public abstract int int32n() throws IOException;
/// Read a signed 32-bit integer (`int`) in big endian byte order.
public abstract int int32b() throws IOException;
/// Read a signed 32-bit integer (`int`) in little endian byte order.
public abstract int int32l() throws IOException;

/// Read an unsigned 32-bit integer into the lower 32 bits of a `long` in the current byte [#order()].
public final long uint32() throws IOException { return int32() & 0xFFFFFFFFL; }

/// Read an unsigned 32-bit integer into the lower 32 bits of a `long` in the native byte order.
public final long uint32n() throws IOException { return int32n() & 0xFFFFFFFFL; }
/// Read an unsigned 32-bit integer into the lower 32 bits of a `long` in the big endian byte order.
public final long uint32b() throws IOException { return int32b() & 0xFFFFFFFFL; }
/// Read an unsigned 32-bit integer into the lower 32 bits of a `long` in the little endian byte order.
public final long uint32l() throws IOException { return int32l() & 0xFFFFFFFFL; }

/// Read a signed 64-bit integer (`long`) in the current byte [#order()].
public abstract long int64() throws IOException;

/// Read a signed 64-bit integer (`long`) in the native byte order.
public abstract long int64n() throws IOException;
/// Read a signed 64-bit integer (`long`) in big endian byte order.
public abstract long int64b() throws IOException;
/// Read a signed 64-bit integer (`long`) in little endian byte order.
public abstract long int64l() throws IOException;

/// Read a 32-bit IEEE-754 floating point value (`float`) in the current byte [#order()].
public abstract float float32() throws IOException;

/// Read a 32-bit IEEE-754 floating point value (`float`) in the native byte order.
public abstract float float32n() throws IOException;
/// Read a 32-bit IEEE-754 floating point value (`float`) in big endian byte order.
public abstract float float32b() throws IOException;
/// Read a 32-bit IEEE-754 floating point value (`float`) in little endian byte order.
public abstract float float32l() throws IOException;

/// Read a 64-bit IEEE-754 floating point value (`double`) in the current byte [#order()].
public abstract double float64() throws IOException;
/// Read a 64-bit IEEE-754 floating point value (`double`) in the native byte order.
public abstract double float64n() throws IOException;
/// Read a 64-bit IEEE-754 floating point value (`double`) in big endian byte order.
public abstract double float64b() throws IOException;
/// Read a 64-bit IEEE-754 floating point value (`double`) in little endian byte order.
public abstract double float64l() throws IOException;

/// Close this BufferedInput and mark it as closed. Calling [#close()] again has no effect,
/// calling most other methods after closing results in an [IOException].
Expand Down
174 changes: 150 additions & 24 deletions core/src/main/java/perfio/BufferedOutput.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package perfio;

import perfio.internal.MemoryAccessor;
import perfio.internal.StringInternals;

import java.io.*;
Expand All @@ -12,7 +13,7 @@
import java.util.Arrays;
import java.util.Objects;

import static perfio.internal.BufferUtil.*;
import static perfio.internal.BufferUtil.growBuffer;


/// BufferedOutput provides buffered streaming writes to an OutputStream or similar data sink.
Expand Down Expand Up @@ -197,56 +198,177 @@ public final BufferedOutput write(byte[] a, int off, int len) throws IOException

/// Write a signed 8-bit integer (`byte`).
public final BufferedOutput int8(byte b) throws IOException {
// if(pos >= lim) {
// flushAndGrow(1);
// if(pos >= lim) throw new EOFException();
// }
// MemoryAccessor.INSTANCE.int8(buf, pos++, b);
// return this;

var p = fwd(1);
buf[p] = b;
MemoryAccessor.INSTANCE.int8(buf, p, b);
return this;
}

/// Write the lower 8 bits of the given `int` as an unsigned 8-bit integer.
public final BufferedOutput uint8(int b) throws IOException { return int8((byte)b); }

/// Write a signed 16-bit integer (`short`) in the current [#order()].
/// Write a signed 16-bit integer (`short`) in the current byte [#order()].
public final BufferedOutput int16(short s) throws IOException {
var p = fwd(2);
(bigEndian ? BA_SHORT_BIG : BA_SHORT_LITTLE).set(buf, p, s);
MemoryAccessor.INSTANCE.int16(buf, p, s, bigEndian);
return this;
}
/// Write a signed 16-bit integer (`short`) in the native byte order.
public final BufferedOutput int16n(short s) throws IOException {
var p = fwd(2);
MemoryAccessor.INSTANCE.int16n(buf, p, s);
return this;
}
/// Write a signed 16-bit integer (`short`) in big endian byte order.
public final BufferedOutput int16b(short s) throws IOException {
var p = fwd(2);
MemoryAccessor.INSTANCE.int16b(buf, p, s);
return this;
}
/// Write a signed 16-bit integer (`short`) in little endian byte order.
public final BufferedOutput int16l(short s) throws IOException {
var p = fwd(2);
MemoryAccessor.INSTANCE.int16l(buf, p, s);
return this;
}

/// Write an unsigned 16-bit integer (`char`) in the current [#order()].
/// Write an unsigned 16-bit integer (`char`) in the current byte [#order()].
public final BufferedOutput uint16(char c) throws IOException {
var p = fwd(2);
(bigEndian ? BA_CHAR_BIG : BA_CHAR_LITTLE).set(buf, p, c);
MemoryAccessor.INSTANCE.uint16(buf, p, c, bigEndian);
return this;
}
/// Write an unsigned 16-bit integer (`char`) in the native byte order.
public final BufferedOutput uint16n(char c) throws IOException {
var p = fwd(2);
MemoryAccessor.INSTANCE.uint16n(buf, p, c);
return this;
}
/// Write an unsigned 16-bit integer (`char`) in big endian byte order.
public final BufferedOutput uint16b(char c) throws IOException {
var p = fwd(2);
MemoryAccessor.INSTANCE.uint16b(buf, p, c);
return this;
}
/// Write an unsigned 16-bit integer (`char`) in little endian byte order.
public final BufferedOutput uint16l(char c) throws IOException {
var p = fwd(2);
MemoryAccessor.INSTANCE.uint16l(buf, p, c);
return this;
}

/// Write a signed 32-bit integer (`int`) in the current [#order()].
/// Write a signed 32-bit integer (`int`) in the current byte [#order()].
public final BufferedOutput int32(int i) throws IOException {
var p = fwd(4);
(bigEndian ? BA_INT_BIG : BA_INT_LITTLE).set(buf, p, i);
MemoryAccessor.INSTANCE.int32(buf, p, i, bigEndian);
return this;
}
/// Write a signed 32-bit integer (`int`) in the native byte order.
public final BufferedOutput int32n(int i) throws IOException {
var p = fwd(4);
MemoryAccessor.INSTANCE.int32n(buf, p, i);
return this;
}
/// Write a signed 32-bit integer (`int`) in big endian byte order.
public final BufferedOutput int32b(int i) throws IOException {
var p = fwd(4);
MemoryAccessor.INSTANCE.int32b(buf, p, i);
return this;
}
/// Write a signed 32-bit integer (`int`) in little endian byte order.
public final BufferedOutput int32l(int i) throws IOException {
var p = fwd(4);
MemoryAccessor.INSTANCE.int32l(buf, p, i);
return this;
}

/// Write the lower 32 bits of the given `long` as an unsigned 32-bit integer in the current [#order()].
/// Write the lower 32 bits of the given `long` as an unsigned 32-bit integer in the current byte [#order()].
public final BufferedOutput uint32(long i) throws IOException { return int32((int)i); }

/// Write a signed 64-bit integer (`long`) in the current [#order()].
/// Write the lower 32 bits of the given `long` as an unsigned 32-bit integer in the native byte order.
public final BufferedOutput uint32n(long i) throws IOException { return int32n((int)i); }
/// Write the lower 32 bits of the given `long` as an unsigned 32-bit integer in big endian byte order.
public final BufferedOutput uint32b(long i) throws IOException { return int32b((int)i); }
/// Write the lower 32 bits of the given `long` as an unsigned 32-bit integer in little endian byte order.
public final BufferedOutput uint32l(long i) throws IOException { return int32l((int)i); }

/// Write a signed 64-bit integer (`long`) in the current byte [#order()].
public final BufferedOutput int64(long l) throws IOException {
var p = fwd(8);
(bigEndian ? BA_LONG_BIG : BA_LONG_LITTLE).set(buf, p, l);
MemoryAccessor.INSTANCE.int64(buf, p, l, bigEndian);
return this;
}
/// Write a signed 64-bit integer (`long`) in the native byte order.
public final BufferedOutput int64n(long l) throws IOException {
var p = fwd(8);
MemoryAccessor.INSTANCE.int64n(buf, p, l);
return this;
}
/// Write a signed 64-bit integer (`long`) in big endian byte order.
public final BufferedOutput int64b(long l) throws IOException {
var p = fwd(8);
MemoryAccessor.INSTANCE.int64b(buf, p, l);
return this;
}
/// Write a signed 64-bit integer (`long`) in little endian byte order.
public final BufferedOutput int64l(long l) throws IOException {
var p = fwd(8);
MemoryAccessor.INSTANCE.int64l(buf, p, l);
return this;
}

/// Write a 32-bit IEEE-754 floating point value (`float`) in the current [#order()].
/// Write a 32-bit IEEE-754 floating point value (`float`) in the current byte [#order()].
public final BufferedOutput float32(float f) throws IOException {
var p = fwd(4);
(bigEndian ? BA_FLOAT_BIG : BA_FLOAT_LITTLE).set(buf, p, f);
MemoryAccessor.INSTANCE.float32(buf, p, f, bigEndian);
return this;
}
/// Write a 32-bit IEEE-754 floating point value (`float`) in the native byte order.
public final BufferedOutput float32n(float f) throws IOException {
var p = fwd(4);
MemoryAccessor.INSTANCE.float32n(buf, p, f);
return this;
}
/// Write a 32-bit IEEE-754 floating point value (`float`) in big endian byte order.
public final BufferedOutput float32b(float f) throws IOException {
var p = fwd(4);
MemoryAccessor.INSTANCE.float32b(buf, p, f);
return this;
}
/// Write a 32-bit IEEE-754 floating point value (`float`) in little endian byte order.
public final BufferedOutput float32l(float f) throws IOException {
var p = fwd(4);
MemoryAccessor.INSTANCE.float32l(buf, p, f);
return this;
}

/// Write a 64-bit IEEE-754 floating point value (`double`) in the current [#order()].
/// Write a 64-bit IEEE-754 floating point value (`double`) in the current byte [#order()].
public final BufferedOutput float64(double d) throws IOException {
var p = fwd(8);
(bigEndian ? BA_DOUBLE_BIG : BA_DOUBLE_LITTLE).set(buf, p, d);
MemoryAccessor.INSTANCE.float64(buf, p, d, bigEndian);
return this;
}
/// Write a 64-bit IEEE-754 floating point value (`double`) in the native byte order.
public final BufferedOutput float64n(double d) throws IOException {
var p = fwd(8);
MemoryAccessor.INSTANCE.float64n(buf, p, d);
return this;
}
/// Write a 64-bit IEEE-754 floating point value (`double`) in big endian byte order.
public final BufferedOutput float64b(double d) throws IOException {
var p = fwd(8);
MemoryAccessor.INSTANCE.float64b(buf, p, d);
return this;
}
/// Write a 64-bit IEEE-754 floating point value (`double`) in little endian byte order.
public final BufferedOutput float64l(double d) throws IOException {
var p = fwd(8);
MemoryAccessor.INSTANCE.float64l(buf, p, d);
return this;
}

Expand Down Expand Up @@ -714,7 +836,17 @@ void closeUpstream() throws IOException {
}

void flushBlocks(boolean forceFlush) throws IOException {
while(next != this) {
if(next != this) flushPrefix(forceFlush);
var len = pos-start;
if((forceFlush && len > 0) || len > initialBufferSize/2) {
writeToOutput(buf, start, len);
totalFlushed += len;
pos = start;
}
}

private void flushPrefix(boolean forceFlush) throws IOException {
do {
var b = next;
var blen = b.pos - b.start;
if(!b.closed) {
Expand All @@ -739,13 +871,7 @@ void flushBlocks(boolean forceFlush) throws IOException {
else if(blen > 0) writeToOutput(b.buf, b.start, blen);
}
b.unlinkAndReturn();
}
var len = pos-start;
if((forceFlush && len > 0) || len > initialBufferSize/2) {
writeToOutput(buf, start, len);
totalFlushed += len;
pos = start;
}
} while(next != this);
}

private boolean maybeMergeToRight(BufferedOutput b) {
Expand Down
Loading

0 comments on commit f405a59

Please sign in to comment.