Skip to content

Perf: Remove unnecessary fsync from file I/O (6x speedup)#309

Merged
fglock merged 2 commits into
masterfrom
perf/remove-unnecessary-fsync
Mar 13, 2026
Merged

Perf: Remove unnecessary fsync from file I/O (6x speedup)#309
fglock merged 2 commits into
masterfrom
perf/remove-unnecessary-fsync

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Mar 13, 2026

Summary

JFR profiling revealed that 94% of native method time in I/O-heavy workloads (like Image::ExifTool) was spent in FileChannel.force() calls - synchronous disk flushes (fsync).

The previous implementation incorrectly called force(true) on every flush() and close(), which is extremely slow (10-100ms per call).

Changes

Core fix (CustomFileChannel.java)

  • close(): Remove force() - OS flushes kernel buffers on close anyway
  • flush(): Now a no-op (FileChannel is unbuffered, writes go directly to OS)
  • sync(): NEW - explicit fsync when truly needed (maps to IO::Handle->sync)
  • fileno(): Return synthetic fd (was undef, which broke defined fileno($fh) checks)

Interface updates

  • IOHandle.java (interface): Added comprehensive Javadoc explaining flush vs sync semantics; added default sync() method
  • LayeredIOHandle.java: Delegate sync() to underlying handle

Perl bindings

  • IOHandle.java (perlmodule): Wire up _sync() to call ioHandle.sync()
  • GlobalContext.java: Initialize IOHandle methods at startup
  • IO/Handle.pm: Fix Java backend detection ($has_java_backend)

Perl Semantics Preserved

Method Behavior
flush() Flushes app buffers to OS kernel buffer (like fflush())
sync() Calls fsync to write to physical disk (like IO::Handle->sync)

Performance

Test Before After Speedup
ExifTool.t 16.7s 2.9s 5.8x

Test Plan

  • All unit tests pass (./gradlew test)
  • ExifTool test suite passes (113/113 tests, 597/597 assertions)
  • Terminal I/O still works correctly (print "prompt: "; $v = <>)
  • IO::Handle->sync returns "0 but true" per Perl convention

Generated with Devin

fglock and others added 2 commits March 13, 2026 15:34
JFR profiling revealed that 94% of native method time in I/O-heavy
workloads (like Image::ExifTool) was spent in FileChannel.force()
calls - synchronous disk flushes (fsync).

The previous implementation incorrectly called force(true) on every
flush() and close(), which is extremely slow (10-100ms per call).

Changes:
- CustomFileChannel.close(): Remove force() - OS flushes on close
- CustomFileChannel.flush(): Now no-op (FileChannel is unbuffered)
- CustomFileChannel.sync(): NEW - explicit fsync when truly needed
- CustomFileChannel.fileno(): Return synthetic fd (was undef)
- IOHandle interface: Add sync() with documentation on flush vs sync
- LayeredIOHandle: Delegate sync() to underlying handle
- IOHandle perlmodule: Wire up _sync() to call ioHandle.sync()
- GlobalContext: Initialize IOHandle methods at startup
- IO/Handle.pm: Fix Java backend detection

Perl semantics preserved:
- flush() flushes app buffers to OS kernel buffer
- sync() calls fsync to write to physical disk (IO::Handle->sync)

Performance:
- ExifTool.t: 16.7s -> 2.9s (5.8x faster)

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <noreply@cognition.ai>
The synthetic fileno implementation broke tests that relied on
undef == undef comparison (io/perlio_leaks.t) and consistent
fd numbers.

Changes:
- CustomFileChannel.fileno(): Return undef (matches master behavior)
- IO/Handle.pm sync()/flush(): Remove `defined fileno()` checks
  since Java backend handles invalid handles internally

Test results restored to master baseline:
- io/perlio_leaks.t: 12/12 (was 0/12)
- io/dup.t: 26/29 (was 17/29)
- op/require_37033.t: 7/10 (unchanged)

Performance still improved: ExifTool.t 3.2s (was 16.7s)

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <noreply@cognition.ai>
@fglock fglock merged commit 4710e55 into master Mar 13, 2026
2 checks passed
@fglock fglock deleted the perf/remove-unnecessary-fsync branch March 13, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant