Why is fcopyfile faster than copyfile?

In my previous post I asked why copyfile is slower than the cp Terminal command. In this other post I asked how I can make copyfile faster by changing the block size.

Now I discovered that the cp implementation on macOS is open source and that when copying regular files it doesn't use copyfile but fcopyfile. In a test I noticed that fcopyfile by default seems to be faster than copyfile.

When copying a 7 GB file I get about the same results I observed when comparing filecopy to cp:

  • copyfile: 4.70 s
  • fcopyfile: 3.44 s

When setting a block size of 16_777_216, copyfile becomes faster than fcopyfile:

  • copyfile: 3.20 s
  • fcopyfile: 3.53 s

Is this expected and why is it so? I would have expected that they both have the same performance, and when changing the block size they would still have the same performance.

Here is the test code. Change #if true to #if false to switch from fcopyfile to copyfile:

import Foundation
import System

let source = "/path/to/source"
let destination = "/path/to/destination"

#if true

let state = copyfile_state_alloc()
defer {
    copyfile_state_free(state)
}
//var bsize = 16_777_216
//copyfile_state_set(state, UInt32(COPYFILE_STATE_BSIZE), &bsize)
let sourceFd = try! FileDescriptor.open(source, .readOnly)
let destinationFd = try! FileDescriptor.open(destination, .writeOnly)
if fcopyfile(sourceFd.rawValue, destinationFd.rawValue, state, copyfile_flags_t(COPYFILE_ALL | COPYFILE_NOFOLLOW | COPYFILE_EXCL | COPYFILE_UNLINK)) != 0 {
    print(NSError(domain: NSPOSIXErrorDomain, code: Int(errno)))
}
try! sourceFd.close()
try! destinationFd.close()

#else

source.withCString { sourcePath in
    destination.withCString { destinationPath in
        let state = copyfile_state_alloc()
        defer {
            copyfile_state_free(state)
        }
//        var bsize = 16_777_216
//        copyfile_state_set(state, UInt32(COPYFILE_STATE_BSIZE), &bsize)
        if copyfile(sourcePath, destinationPath, state, copyfile_flags_t(COPYFILE_ALL | COPYFILE_NOFOLLOW | COPYFILE_EXCL | COPYFILE_UNLINK)) != 0 {
            print(NSError(domain: NSPOSIXErrorDomain, code: Int(errno)))
        }
    }
}

#endif