Skip to content

Instantly share code, notes, and snippets.

@mjgp2
Created February 5, 2025 10:19
Show Gist options
  • Save mjgp2/a7e10c293453ef9586e62d5326a22bac to your computer and use it in GitHub Desktop.
Save mjgp2/a7e10c293453ef9586e62d5326a22bac to your computer and use it in GitHub Desktop.
Benchmarking different durability options in Java like fsync, channel.force, direct JNA calls, ...
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.stream.IntStream;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.Memory;
import sun.misc.Unsafe;
public class FsyncBenchmark {
private static final int TEST_ITERATIONS = 100; // Number of writes
private static final int DATA_SIZE = 500; // 500 bytes per write
private static final byte[] TEST_BYTES = "X".repeat(DATA_SIZE).getBytes();
private static final Path FILE_PATH = Paths.get("fsync_benchmark.dat");
private static Unsafe unsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("Unable to get Unsafe instance", e);
}
}
public static int getFD(FileDescriptor fd) {
try {
long fdOffset = unsafe.objectFieldOffset(FileDescriptor.class.getDeclaredField("fd"));
return unsafe.getInt(fd, fdOffset);
} catch (NoSuchFieldException e) {
throw new RuntimeException("Unable to access fd field", e);
}
}
public interface Constants extends Library {
Constants INSTANCE = Native.load("constants", Constants.class);
int getConstant(String name);
static int constant(String name) {
return INSTANCE.getConstant(name);
}
}
public interface CLib extends Library {
CLib INSTANCE = Native.load("c", CLib.class);
// Open a file with flags
int open(String path, int flags, int mode);
// Close the file
int close(int fd);
// Write to file
int write(int fd, Pointer buffer, int count);
// Sync (fsync)
int fsync(int fd);
int fdatasync(int fd);
int fcntl(int fd, int command, int arg);
}
// Get the file descriptor using JNA
public static class NativeDescriptor {
static {
Native.register("c"); // Link against standard C library
}
public static native int fileno(FileDescriptor fd);
}
public static boolean isMac() {
return System.getProperty("os.name").toLowerCase().contains("mac");
}
public static void main(String[] args) throws Exception {
Files.deleteIfExists(FILE_PATH);
Files.createFile(FILE_PATH);
System.out.println("\nBenchmarking O_DSYNC...");
benchmark_jna_O_DSYNC();
System.out.println("\nBenchmarking O_SYNC...");
benchmark_jna_O_SYNC();
System.out.println("\nBenchmarking StandardOpenOption.DSYNC...");
benchmark_dsync();
System.out.println("\nBenchmarking StandardOpenOption.SYNC()...");
benchmark_sync();
System.out.println("\nBenchmarking benchmark_fsync...");
benchmark_fsync();
System.out.println("\nBenchmarking JNA fsync()...");
benchmark_jna_fsync();
System.out.println("\nBenchmarking JNA fdatasync()...");
benchmark_fdatasync();
System.out.println("\nBenchmarking force(true)...");
benchmark_force(true);
System.out.println("\nBenchmarking force(false)...");
benchmark_force(false);
if ( isMac() ) {
System.out.println("\nBenchmarking F_FULLFSYNC...");
benchmark_F_FULLFSYNC();
}
}
private static void benchmark_force(boolean meta) throws Exception {
try (FileChannel channel =
FileChannel.open(FILE_PATH, StandardOpenOption.APPEND, StandardOpenOption.WRITE);
OutputStream outputStream = Channels.newOutputStream(channel)) {
long totalTime =
IntStream.range(0, TEST_ITERATIONS)
.mapToLong(
i -> {
return measureTime(
() -> {
try {
outputStream.write(TEST_BYTES); // Append 500 bytes
outputStream.flush();
channel.force(meta); // Calls fsync()
} catch (IOException e) {
e.printStackTrace();
}
});
})
.sum();
System.out.printf("force("+meta+") Avg Time: %.2f ms%n", (totalTime / (double) TEST_ITERATIONS));
}
}
private static void benchmark_dsync() throws Exception {
try (FileChannel channel =
FileChannel.open(FILE_PATH, StandardOpenOption.APPEND, StandardOpenOption.WRITE, StandardOpenOption.DSYNC);
OutputStream outputStream = Channels.newOutputStream(channel)) {
long totalTime =
IntStream.range(0, TEST_ITERATIONS)
.mapToLong(
i -> {
return measureTime(
() -> {
try {
outputStream.write(TEST_BYTES); // Append 500 bytes
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
});
})
.sum();
System.out.printf("StandardOpenOption.DSYNC Avg Time: %.2f ms%n", (totalTime / (double) TEST_ITERATIONS));
}
}
private static void benchmark_sync() throws Exception {
try (FileChannel channel =
FileChannel.open(FILE_PATH, StandardOpenOption.APPEND, StandardOpenOption.WRITE, StandardOpenOption.SYNC);
OutputStream outputStream = Channels.newOutputStream(channel)) {
long totalTime =
IntStream.range(0, TEST_ITERATIONS)
.mapToLong(
i -> {
return measureTime(
() -> {
try {
outputStream.write(TEST_BYTES); // Append 500 bytes
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
});
})
.sum();
System.out.printf("StandardOpenOption.SYNC Avg Time: %.2f ms%n", (totalTime / (double) TEST_ITERATIONS));
}
}
private static void benchmark_jna_O_DSYNC() throws Exception {
int fd = CLib.INSTANCE.open(FILE_PATH.toFile().getAbsolutePath(), Constants.constant("O_WRONLY") | Constants.constant("O_CREAT") | Constants.constant("O_DSYNC") , 0644);
if (fd < 0) {
System.err.println("Failed to open file");
return;
}
File file = new File("/dev/fd/" + fd);
try (FileOutputStream outputStream = new FileOutputStream( file)) {
if (fd < 0) {
throw new RuntimeException("Failed to get file descriptor");
}
long totalTime =
IntStream.range(0, TEST_ITERATIONS)
.mapToLong(
i -> {
return measureTime(
() -> {
try {
Pointer buffer = new Memory(TEST_BYTES.length);
buffer.write(0, TEST_BYTES, 0, TEST_BYTES.length); // Copy Java byte array into JNA Memory
CLib.INSTANCE.write(fd, buffer, TEST_BYTES.length);
} catch (Exception e) {
e.printStackTrace();
}
});
})
.sum();
System.out.printf("O_DSYNC Avg Time: %.2f ms%n", (totalTime / (double) TEST_ITERATIONS));
}
}
private static void benchmark_jna_O_SYNC() throws Exception {
int fd = CLib.INSTANCE.open(FILE_PATH.toFile().getAbsolutePath(), Constants.constant("O_WRONLY") | Constants.constant("O_CREAT") | Constants.constant("O_SYNC") , 0644);
if (fd < 0) {
System.err.println("Failed to open file");
return;
}
File file = new File("/dev/fd/" + fd);
try (FileOutputStream outputStream = new FileOutputStream( file)) {
if (fd < 0) {
throw new RuntimeException("Failed to get file descriptor");
}
long totalTime =
IntStream.range(0, TEST_ITERATIONS)
.mapToLong(
i -> {
return measureTime(
() -> {
try {
Pointer buffer = new Memory(TEST_BYTES.length);
buffer.write(0, TEST_BYTES, 0, TEST_BYTES.length); // Copy Java byte array into JNA Memory
CLib.INSTANCE.write(fd, buffer, TEST_BYTES.length);
} catch (Exception e) {
e.printStackTrace();
}
});
})
.sum();
System.out.printf("O_DSYNC Avg Time: %.2f ms%n", (totalTime / (double) TEST_ITERATIONS));
}
}
private static void benchmark_F_FULLFSYNC() throws Exception {
int fd = CLib.INSTANCE.open(FILE_PATH.toFile().getAbsolutePath(), Constants.constant("O_WRONLY") | Constants.constant("O_CREAT") , 0644);
if (fd < 0) {
System.err.println("Failed to open file");
return;
}
File file = new File("/dev/fd/" + fd);
try (FileOutputStream outputStream = new FileOutputStream( file)) {
if (fd < 0) {
throw new RuntimeException("Failed to get file descriptor");
}
long totalTime =
IntStream.range(0, TEST_ITERATIONS)
.mapToLong(
i -> {
return measureTime(
() -> {
try {
outputStream.write(TEST_BYTES); // Append 500 bytes
outputStream.flush();
CLib.INSTANCE.fcntl(fd, Constants.constant("F_FULLFSYNC"), 0); // Calls F_FULLFSYNC
} catch (IOException e) {
e.printStackTrace();
}
});
})
.sum();
System.out.printf("F_FULLFSYNC Avg Time: %.2f ms%n", (totalTime / (double) TEST_ITERATIONS));
}
}
private static void benchmark_fdatasync() throws Exception {
int fd = CLib.INSTANCE.open(FILE_PATH.toFile().getAbsolutePath(), Constants.constant("O_WRONLY") | Constants.constant("O_CREAT") , 0644);
if (fd < 0) {
System.err.println("Failed to open file");
return;
}
File file = new File("/dev/fd/" + fd);
try (FileOutputStream outputStream = new FileOutputStream( file)) {
if (fd < 0) {
throw new RuntimeException("Failed to get file descriptor");
}
long totalTime =
IntStream.range(0, TEST_ITERATIONS)
.mapToLong(
i -> {
return measureTime(
() -> {
try {
outputStream.write(TEST_BYTES); // Append 500 bytes
outputStream.flush();
CLib.INSTANCE.fdatasync(fd); // Calls F_FULLFSYNC
} catch (IOException e) {
e.printStackTrace();
}
});
})
.sum();
System.out.printf("JNA fdatasync Avg Time: %.2f ms%n", (totalTime / (double) TEST_ITERATIONS));
}
}
private static void benchmark_jna_fsync() throws Exception {
int fd = CLib.INSTANCE.open(FILE_PATH.toFile().getAbsolutePath(), Constants.constant("O_WRONLY") | Constants.constant("O_CREAT") , 0644);
if (fd < 0) {
System.err.println("Failed to open file");
return;
}
File file = new File("/dev/fd/" + fd);
try (FileOutputStream outputStream = new FileOutputStream( file)) {
if (fd < 0) {
throw new RuntimeException("Failed to get file descriptor");
}
long totalTime =
IntStream.range(0, TEST_ITERATIONS)
.mapToLong(
i -> {
return measureTime(
() -> {
try {
outputStream.write(TEST_BYTES); // Append 500 bytes
outputStream.flush();
CLib.INSTANCE.fsync(fd); // Calls F_FULLFSYNC
} catch (IOException e) {
e.printStackTrace();
}
});
})
.sum();
System.out.printf("JNA fsync Avg Time: %.2f ms%n", (totalTime / (double) TEST_ITERATIONS));
}
}
private static void benchmark_fsync() throws Exception {
try (RandomAccessFile file = new RandomAccessFile(FILE_PATH.toFile(), "rw");
FileOutputStream outputStream = new FileOutputStream(file.getFD())) {
long totalTime =
IntStream.range(0, TEST_ITERATIONS)
.mapToLong(
i -> {
return measureTime(
() -> {
try {
outputStream.write(TEST_BYTES); // Append 500 bytes
outputStream.flush();
file.getFD().sync(); // Calls fsync()
} catch (IOException e) {
e.printStackTrace();
}
});
})
.sum();
System.out.printf("file.getFD().sync() Avg Time: %.2f ms%n", (totalTime / (double) TEST_ITERATIONS));
}
}
private static long measureTime(Runnable action) {
long start = System.nanoTime();
action.run();
return (System.nanoTime() - start) / 1_000_000; // Convert to ms
}
}
#include <stdio.h>
#include <fcntl.h> // For O_* flags
#include <string.h>
#ifdef __APPLE__
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#endif
// Function to get the value of a constant by name
int getConstant(const char *name) {
if (strcmp(name, "O_RDONLY") == 0) {
return O_RDONLY;
} else if (strcmp(name, "O_WRONLY") == 0) {
return O_WRONLY;
} else if (strcmp(name, "O_RDWR") == 0) {
return O_RDWR;
} else if (strcmp(name, "O_CREAT") == 0) {
return O_CREAT;
} else if (strcmp(name, "O_EXCL") == 0) {
return O_EXCL;
} else if (strcmp(name, "O_NOCTTY") == 0) {
return O_NOCTTY;
} else if (strcmp(name, "O_TRUNC") == 0) {
return O_TRUNC;
} else if (strcmp(name, "O_APPEND") == 0) {
return O_APPEND;
} else if (strcmp(name, "O_NONBLOCK") == 0) {
return O_NONBLOCK;
} else if (strcmp(name, "O_SYNC") == 0) {
return O_SYNC;
} else if (strcmp(name, "O_DSYNC") == 0) {
return O_DSYNC;
} else if (strcmp(name, "O_DIRECT") == 0) {
#ifdef O_DIRECT
return O_DIRECT; // Available on Linux, not macOS
#else
return -1; // Not available
#endif
} else if (strcmp(name, "O_DIRECTORY") == 0) {
return O_DIRECTORY;
} else if (strcmp(name, "O_NOFOLLOW") == 0) {
return O_NOFOLLOW;
} else if (strcmp(name, "O_CLOEXEC") == 0) {
return O_CLOEXEC;
} else if (strcmp(name, "O_ASYNC") == 0) {
return O_ASYNC;
}
#ifdef __APPLE__
else if (strcmp(name, "F_FULLFSYNC") == 0) {
return F_FULLFSYNC; // macOS only
}
#endif
return -1; // Return -1 if the constant is not found
}
// Test function to print all constants
void printConstants() {
char *constants[] = {
"O_RDONLY", "O_WRONLY", "O_RDWR", "O_CREAT", "O_EXCL",
"O_NOCTTY", "O_TRUNC", "O_APPEND", "O_NONBLOCK", "O_SYNC",
"O_DSYNC", "O_RSYNC", "O_DIRECT", "O_DIRECTORY", "O_NOFOLLOW",
"O_CLOEXEC", "O_ASYNC",
#ifdef __APPLE__
"F_FULLFSYNC",
#endif
NULL
};
for (int i = 0; constants[i] != NULL; i++) {
printf("%s = 0x%x\n", constants[i], getConstant(constants[i]));
}
}
// Main function for testing
int main() {
printConstants();
return 0;
}
Benchmarking O_DSYNC...
O_DSYNC Avg Time: 1.71 ms
Benchmarking O_SYNC...
O_DSYNC Avg Time: 2.03 ms
Benchmarking StandardOpenOption.DSYNC...
StandardOpenOption.DSYNC Avg Time: 1.62 ms
Benchmarking StandardOpenOption.SYNC()...
StandardOpenOption.SYNC Avg Time: 1.52 ms
Benchmarking benchmark_fsync...
file.getFD().sync() Avg Time: 0.15 ms
Benchmarking JNA fsync()...
JNA fsync Avg Time: 1.52 ms
Benchmarking JNA fdatasync()...
JNA fdatasync Avg Time: 1.41 ms
Benchmarking force(true)...
force(true) Avg Time: 1.47 ms
Benchmarking force(false)...
force(false) Avg Time: 1.58 ms
Benchmarking O_DSYNC...
O_DSYNC Avg Time: 0.00 ms
Benchmarking O_SYNC...
O_DSYNC Avg Time: 0.00 ms
Benchmarking StandardOpenOption.DSYNC...
StandardOpenOption.DSYNC Avg Time: 0.00 ms
Benchmarking StandardOpenOption.SYNC()...
StandardOpenOption.SYNC Avg Time: 0.00 ms
Benchmarking benchmark_fsync...
file.getFD().sync() Avg Time: 0.00 ms
Benchmarking JNA fsync()...
JNA fsync Avg Time: 0.00 ms
Benchmarking JNA fdatasync()...
JNA fdatasync Avg Time: 0.00 ms
Benchmarking force(true)...
force(true) Avg Time: 4.70 ms
Benchmarking force(false)...
force(false) Avg Time: 4.31 ms
Benchmarking F_FULLFSYNC...
F_FULLFSYNC Avg Time: 4.24 ms
@mjgp2
Copy link
Author

mjgp2 commented Feb 5, 2025

On Mac, fsync doesn't do the same as on Linux... F_FULLFSYNC is required and it is incredibly slow.

I don't quite understand why the FileDescriptor.sync() is the best performing by an order of magnitude as it is calling fsync() under the hood.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment