Hello,
I am trying to implement some sort of minimal I/O benchmarking feature. I can calculate the write speeds with the following code:
guard var handle = FileHandle(forUpdatingAtPath: url.path) else { return }
var writtenData: UInt64 = 0
var totalWrittenData: UInt64 = 0
var writeSpeed: UInt64 = 0
let data = Data(repeating: 0, count: 16 * 1024 * 1024) // 16 MB test block
let startTime = Date.now.timeIntervalSince1970
while Date.now.timeIntervalSince1970 - startTime < 5.0
{
handle.write(data)
try? handle.synchronize()self.totalWrittenData += blockSize
let duration = Date.timestamp - self.startTime
writeSpeed = UInt64(Double(self.totalWrittenData) / duration)
if writtenData > blockSize * 16
{
writtenDara = 0
try? handle.seek(toOffset: 0)
}
}
// On my PCE-Express SSD this results in roughly 1800MB/s. This value matches the value reported by other benchmarking apps.
// Remove everything at the end of the file
try? handle.truncate(atOffset: blockSize)
try? handle.synchronize()
try? handle.seek(toOffset: 0)
var index = 0
while index < 5
{
autoreleasepool
{
var startTime = Date.timestamp
let bytes = try? handle.readToEnd()
let duration = Date.timestamp - startTime
Swift.print(bytes?.count, duration)
try? handle.seek(toOffset: 0)
}
index += 1
}
The code works - in theory. Although I really like that the operating system and drivers do some cacheing to optimise performance - in my use case I don't want files to be cached. The read results are always around 8GB/s for my PCE-Express SSD and also for some older USB Sticks, so the data seems to come directly from the memory.
I found a couple of other threads which led me to some older C functions, specifically this code:
// Open file.
let fd = fopen(fileUrl.path, "r")
// Find the end
fseek(fd, 0, SEEK_END)
// Count bytes
let fileByteSize = ftell(fd)
// Return to start
fseek(fd2, 0, SEEK_SET)
// Disable cache
setvbuf(fd2, nil, _IONBF, 0)
// Find the end
fseek(fd2, 0, SEEK_END)
let readBytes = fread(pointer, 1, fileByteSize, fd2)
readBytes += UInt64(readBytes.count)
When executing this code (or parts of it), it also works fine - but the data is also coming from the cache.
What am I missing? I also tried the FileManager. Of course I am not an expert, however I think that the FileManager is "just" a wrapper around all those C functions.
Is there any way to ignore the cacheing mechanism and tell macOS / the driver to read the data directly from the drive?
Regards, Sascha
The droid you’re looking for here is F_NOCACHE
, as documented in the fcntl
man page.
If you’re using this to profile I/O performance, make sure that:
-
Your memory buffer is page aligned.
-
The offset in the file is a multiple of the page size.
-
The length is a multiple of the page size.
Also, if you’re doing a write test, pre-allocate your storage using F_PREALLOCATE
.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"