As a part of the video editing app I’m working on, I want to efficiently copy a folder of resources on the same (local) filesystem. Because iOS is on APFS, cloning (CoW) is an option.
I read the documentation for clonefile(2)
which states that cloning a folder works but is strongly discouraged.
I did a small sample project which demonstrates that using clonefile
on a folder works correctly and is 10× faster than using FileManager
’s copyItem
method.
My questions:
- The main one I’m interested in: Why is using
clonefile
for a folder strongly discouraged? - Is
FileManager
using cloning behind the scenes? Or more exactly how guaranteed are we it will use it? (I know it does, I tried manuallycp
ing the resources and it was thousands of times slower.)
Things went long, so you get a Part 1...
Is FileManager using cloning behind the scenes?
Yes. More specifically, most of our copy APIs are actually built on top of copyfile, and copyfile is what actually handles file cloning.
Or more exactly how guaranteed are we it will use it?
It basically uses it "everywhere it can". That includes both the obvious case of copying within a volume but also includes less obvious cases like copying a hierarchy that includes clone between two APFS volumes. Generally speaking, you should get the same results as what you'd get copying using the Finder*.
*The one exception I can think of is copying files within an SMB server which will definitely be slower and which I believe will also bypass cloning. You can read more about that particular issue here.
(I know it does, I tried manually cping the resources and it was thousands of times slower.)
Just to clarify, I assume you meant manually copying the contents (using open/read/write), not using the "cp" command? cp also uses copyfile, so you should be getting the same results as NSFileManager.
The main one I’m interested in: Why is using clonefile for a folder strongly discouraged?
Expanding a bit on what I described here, you can basically think of cloning a directory as doing two things:
-
Pushing the copy operation into kernel, avoiding the syscall overhead of directory iteration, creation, and individual clonefile calls.
-
Preventing all changes to the source hierarchy while the operation is in progress, making the process atomic.
That second point is what makes this potentially dangerous as, in the worst case, you could theoretically panic the kernel by stalling all activity on critical locations long enough that the kernel "gives up" and panics.
So, if you're going to do this, there are basically two things you need to pay attention to:
- How big is the hierarchy?
The individual operations aren't slow, so you only really have issues once the hierarchy becomes "big enough" to matter.
- Where is the hierarchy and "who" has access to it?
The issue here isn't about the operation itself, it's about what that operation could delay or interfere with. As the most extreme case, if you're doing this on a secondary volume that's "set aside" for your apps own use, cloning even ENORMOUS hierarchies is unlikely to have any noticeable impact on the larger system.
Yes, you're blocking access to a large hierarchy for an extended period of time, but that only matter if someone else wants access to the same hierarchy.
As a part of the video editing app I’m working on, I want to efficiently copy a folder of resources on the same (local) filesystem. Because iOS is on APFS, cloning (CoW) is an option.
Even on macOS there are ways to "isolate" the file your working with well enough that I'd consider directory cloning fairly safe. Assuming your working out of your apps data container, that's about as isolated as it's possible to be. There maybe other factor at work that would make this problematic, but on the whole I don't see any real issue.
Having said that, two things I'd think about as you decide what approach to use here:
- (1) Are you actually looking at this in terms of who your app actually works?
For example, it's very likely that your app is going to be iterating and storing the full hierarchy anyway (so it knows what's there and can show it to the user), so that reduces some of the gain. More broadly, if the more "natural" pattern for your app has it manipulating different files on different threads, you may find trying to force "everything" to a single thread (so you can clone the hierarchy) hurt your overall app design more than it helps performance.
- (2) Absolute performance matters more than relative performance. copyItem maybe slower than clonefile, but that difference may not be worth bother with.
That 10x might sound huge but I suspect that this is case of comparing two numbers that are both REALLY small. The "best case" for an individual item implementation like "copyItem" is basically a slowly growing constant syscall cost (the overhead of the bulk directory iteration) + 1 syscall per object. So for 250 files you're comparing the cost of ~251 syscalls vs. 1 syscall. In that context, 10x is actually a pretty good showing for copyItem at, but regardless it shouldn't be THAT slow unless the file count is VERY high.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware