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 } }