vfs: read-only Windows streams on their own handle by default

Read-only streams on Windows NT take their own handle, position and
buffer over the C runtime's FILE by default; VFS_WIN32_NO_NATIVE keeps
the C runtime. Measured with vfs_native_bench on Windows: 1-byte
sequential reads 16.0 -> 6.8 ns, 16-byte 16.6 -> 7.5 ns, 256-byte 40.0
-> 30.4 ns; 4 KiB and up, random reads, reads at offsets, threads and
small-file opens the same within run-to-run spread.

The cross-libc Windows job's second VFS pass is now the C runtime one
(make VFS_NATIVE=0); vfs_native_bench_native is the default build and
vfs_native_bench the C runtime one.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-12 19:46:42 +01:00
1 parent cf816b8940
commit 9ce7ef2311
7 files changed
+41 -36

No files matched your search

+4 -4
View File
@@ -235,11 +235,11 @@ jobs:
./vfs_positional_nommap_test.exe
./vfs_positional_ntverify_test.exe
./vfs_positional_pathreopen_test.exe
# The same with read-only streams on a handle of their own
# (VFS_WIN32_NATIVE, opt-in): the stream contract, the
# path an open takes, large files, offsets from threads.
# The same with read-only streams on the C runtime
# (VFS_WIN32_NO_NATIVE): the stream contract, the path an
# open takes, large files, offsets from threads.
make clean MMAP=0
make MMAP=0 VFS_NATIVE=1
make MMAP=0 VFS_NATIVE=0
./vfs_bulk_read_test.exe
./vfs_seek_contract_test.exe
./vfs_large_file_test.exe
@@ -547,8 +547,8 @@ jobs:
# self-contained regression tests came to be compiled every
# run and never executed.
declare -a NORUN_TARGETS=(
# Benchmarks: C runtime streams against VFS_WIN32_NATIVE,
# for timing on Windows.
# Benchmarks: C runtime streams (VFS_WIN32_NO_NATIVE)
# against the stream's own handle, for timing on Windows.
vfs_native_bench
vfs_native_bench_native
# Fixture-driven checks run in Linux-samples-record.yml.
+1 -1
View File
@@ -199,7 +199,7 @@ struct libretro_vfs_implementation_file
HANDLE fh;
/* CreateFileMapping handle backing `mapped`; NULL when unmapped */
HANDLE map_handle;
/* A read-only stream on a handle of its own (VFS_WIN32_NATIVE): its
/* A read-only stream on a handle of its own (not VFS_WIN32_NO_NATIVE): its
* position, the part of the file `buf` holds, and a read error */
uint64_t npos;
uint64_t nbuf_at;
+17 -15
View File
@@ -136,11 +136,12 @@ ifeq ($(DESCRIPTOR_IO),1)
CFLAGS += -DVFS_HAVE_DESCRIPTOR_IO=1
endif
# 'make VFS_NATIVE=1' builds the suite with read-only Windows streams on
# a handle of their own (VFS_WIN32_NATIVE); elsewhere it changes nothing.
# 'make VFS_NATIVE=0' builds the suite with read-only Windows streams on
# the C runtime (VFS_WIN32_NO_NATIVE) rather than a handle of their own;
# elsewhere it changes nothing.
VFS_NATIVE ?=
ifeq ($(VFS_NATIVE),1)
CFLAGS += -DVFS_WIN32_NATIVE
ifeq ($(VFS_NATIVE),0)
CFLAGS += -DVFS_WIN32_NO_NATIVE
endif
# FAKE_CFR=1 stands in a copy_file_range that copies to EOF whatever
@@ -258,22 +259,23 @@ $(TARGET_TEST18): vfs_positional_required.o $(NTVERIFY_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) -lpthread
# vfs_native_bench / vfs_native_bench_native: read-only stream timings
# through the C runtime, and through VFS_WIN32_NATIVE - run both on
# Windows and compare. Elsewhere the two are the same build.
NATIVE_VFS_OBJ := vfs_implementation_native.o
BENCH_OBJS := $(filter-out %/vfs_implementation.o,$(COMMON_OBJS)) \
# through the C runtime (VFS_WIN32_NO_NATIVE), and through the stream's
# own handle - run both on Windows and compare. Elsewhere the two are
# the same build.
CRT_VFS_OBJ := vfs_implementation_crt.o
BENCH_OBJS := $(filter-out %/vfs_implementation.o,$(COMMON_OBJS)) \
rthreads_ra.o retro_eventcount_ra.o
$(NATIVE_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
$(CC) -c -o $@ $< $(CFLAGS) -DVFS_WIN32_NATIVE
$(CRT_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c
$(CC) -c -o $@ $< $(CFLAGS) -DVFS_WIN32_NO_NATIVE
vfs_native_bench_native.o: vfs_native_bench.c
$(CC) -c -o $@ $< $(CFLAGS) -DVFS_WIN32_NATIVE
vfs_native_bench_crt.o: vfs_native_bench.c
$(CC) -c -o $@ $< $(CFLAGS) -DVFS_WIN32_NO_NATIVE
$(BENCH_CRT): vfs_native_bench.o $(COMMON_OBJS) rthreads_ra.o retro_eventcount_ra.o
$(BENCH_CRT): vfs_native_bench_crt.o $(BENCH_OBJS) $(CRT_VFS_OBJ)
$(CC) -o $@ $^ $(LDFLAGS) $(RA_LIBS) -lpthread
$(BENCH_NATIVE): vfs_native_bench_native.o $(BENCH_OBJS) $(NATIVE_VFS_OBJ)
$(BENCH_NATIVE): vfs_native_bench.o $(COMMON_OBJS) rthreads_ra.o retro_eventcount_ra.o
$(CC) -o $@ $^ $(LDFLAGS) $(RA_LIBS) -lpthread
$(TARGET_TEST10): vfs_view_test.o $(VIEW_OBJS)
@@ -355,7 +357,7 @@ clean:
$(TARGET_TEST4) $(TARGET_TEST5) $(TARGET_TEST6) \
$(TARGET_TEST7) $(TARGET_TEST8) $(TARGET_TEST9) $(TARGET_TEST10) \
$(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) $(TARGET_TEST14) $(TARGET_TEST15) $(TARGET_TEST16) $(TARGET_TEST17) $(TARGET_TEST18) $(BENCH_CRT) $(BENCH_NATIVE) \
vfs_native_bench.o vfs_native_bench_native.o $(NATIVE_VFS_OBJ) $(CLOSE_VFS_OBJ) \
vfs_native_bench.o vfs_native_bench_crt.o $(CRT_VFS_OBJ) $(CLOSE_VFS_OBJ) \
$(OBJS) $(FAULT_FS_OBJ) $(RENAME_VFS_OBJ) \
$(RA_VFS_OBJ) $(HOOK_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o \
$(NOMMAP_VFS_OBJ) vfs_positional_required.o $(OFF32_VFS_OBJ) \
@@ -272,13 +272,13 @@ static void test_path_selection(void)
}
/* No hint: unchanged, buffered - on a handle of its own where
* VFS_WIN32_NATIVE takes read-only streams off the C runtime. */
* Windows takes read-only streams off the C runtime. */
s = retro_vfs_file_open_impl(path, RETRO_VFS_FILE_ACCESS_READ,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(s != NULL, "plain open failed");
if (s)
{
#if defined(VFS_WIN32_NATIVE) && defined(_WIN32) && !defined(_XBOX)
#if !defined(VFS_WIN32_NO_NATIVE) && defined(_WIN32) && !defined(_XBOX)
CHECK(s->fp == NULL && s->fh != NULL,
"plain read-only open is not on its own handle");
#else
@@ -21,9 +21,9 @@
*/
/* Read-only stream throughput through filestream: the C runtime path
* (vfs_native_bench) against VFS_WIN32_NATIVE (vfs_native_bench_native),
* the same source built twice. On anything but Windows the two are the
* same build.
* (vfs_native_bench, VFS_WIN32_NO_NATIVE) against the stream's own
* handle (vfs_native_bench_native), the same source built twice. On
* anything but Windows the two are the same build.
*
* sequential reads of 1, 16, 256, 4096 and 65536 bytes, and 1 MiB
* 4 KiB at random offsets by seek and read, and by filestream_read_at
@@ -273,10 +273,10 @@ int main(int argc, char **argv)
if (size < 8192)
return 1;
#ifdef VFS_WIN32_NATIVE
printf("VFS_WIN32_NATIVE, %lld bytes\n", (long long)size);
#else
#ifdef VFS_WIN32_NO_NATIVE
printf("C runtime streams, %lld bytes\n", (long long)size);
#else
printf("native streams, %lld bytes\n", (long long)size);
#endif
sequential(path, 1);
sequential(path, 16);
+9 -6
View File
@@ -339,15 +339,18 @@ static void vfs_unix_to_filetime(int64_t unix_s, FILETIME *ft)
#define RFILE_HINT_UNBUFFERED (1 << 8)
/* VFS_WIN32_NATIVE: a read-only stream on Windows NT opens a handle of
* its own rather than a C runtime FILE, keeps its own position and a
* 64 KiB buffer, and reads at offsets - with no CRT lock or buffer on
* the way, and its reads at offsets on the same handle. Opt-in until
* measured; see samples/file/vfs/vfs_native_bench.c. */
#if defined(VFS_WIN32_NATIVE) && defined(_WIN32) && !defined(_XBOX) \
/* A read-only stream on Windows NT opens a handle of its own rather
* than a C runtime FILE, keeps its own position and a 64 KiB buffer,
* and reads at offsets - with no CRT lock or buffer on the way, and its
* reads at offsets on the same handle. VFS_WIN32_NO_NATIVE keeps the C
* runtime; samples/file/vfs/vfs_native_bench.c times the two. */
#if !defined(VFS_WIN32_NO_NATIVE) && defined(_WIN32) && !defined(_XBOX) \
&& !defined(__WINRT__) && defined(VFS_HAVE_AT_HANDLE)
#define VFS_NATIVE 1
#define RFILE_HINT_WIN32_NATIVE (1 << 9)
#ifndef INVALID_FILE_SIZE
#define INVALID_FILE_SIZE ((DWORD)0xFFFFFFFF)
#endif
#define VFS_NATIVE_BUF 0x10000
static bool vfs_native_open(libretro_vfs_implementation_file *stream,
const char *path);