Post

Replies

Boosts

Views

Activity

Reply to Get Application Scripts directory for app group
But NSUserScriptTask won’t find them there Then it's more like a limitation of how application scripts are designed to work, but I don't get it when you say that it doesn't make sense to have application scripts in an app group. App groups are meant to share data between apps, so why not application scripts as well? The fact that the Application Scripts directory is created automatically even for app groups adds to the confusion.
Topic: App & System Services SubTopic: General Tags:
Feb ’24
Reply to Making filecopy faster by changing block size
By the way, I just had to learn the hard way that this code var blockSize = UInt32(16_777_216) if copyfile_state_set(state, UInt32(COPYFILE_STATE_BSIZE), &blockSize) != 0 { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) } always throws on macOS 11 or older with a POSIX error 22: Invalid argument regardless of the provided block size. There's no mention of this in the documentation and I assumed that it would just work, but got bug reports from users running older systems.
Topic: App & System Services SubTopic: General Tags:
Jan ’24
Reply to Making filecopy faster by changing block size
It’s important when the buffer size is less than the file size because it keeps all transfers aligned within the file, which facilitates uncached I/O. Good, that's what I was aiming at: use a block size equal to the file size up to a maximum power of 2. I don't think I was really trying to optimize the buffer allocation time; rather it didn't feel right to allocate an arbitrarily large buffer. After all, space is constrained and there is a reason why we can specify the block size, or it would automatically be set to infinity... right? it’s time to benchmark and use that to guide your optimisation Here are my results when copying a file with a given size using varying block sizes and 5 repetitions. (Explicitly writing random data to the file makes the file creation quite slow, but simply writing a buffer with uninitialized data seems to work as well.) Different file sizes seem to follow a similar trend. Using a multiple of 1000 or a multiple of the page size also doesn't seem to make a difference in the overall trend. The lowest block sizes, 1'024 and 2'048, seem to be special and cause a very fast copy. From 4'096 upwards, the time decreases... ...up to 65'536, where it suddenly increases again, but from then it definitely decreases. The bigger the file, the higher the block size needs to be to notice a difference. With a 100 MB file, increasing the block size from 1'048'576 to 2'097'152 makes the operation about twice as fast, with little improvements above that block size. With a 1 GB file, increasing the block size from 1'048'576 to 8'388'608 makes the operation about twice as fast, with little improvements above that block size. Without using F_NOCACHE, the operation gets slowly faster when increasing the block size from 1'048'576, and then gets slower again from 8'388'608 upwards. Not sure if that means anything. Here are the graphs for a 100 MB and a 1 GB file. Copying a 100 MB file: Copying a 1 GB file: Copying a 100 MB file created without F_NOCACHE: And here is the code: class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { print("page size", getpagesize()) let openPanel = NSOpenPanel() openPanel.canChooseDirectories = true openPanel.runModal() test(url: openPanel.urls[0]) } func test(url: URL) { let source = url.appendingPathComponent("file source") let destination = url.appendingPathComponent("file destination") let fileSizes = [1_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000, 10_000_000_000, Int(getpagesize()) * 10_000] let blockSizes: [Int32] = (10..<31).map({ 1 << $0 }) let repetitions = 5 var times = [[TimeInterval]](repeating: [TimeInterval](repeating: 0, count: repetitions), count: blockSizes.count) for fileSize in fileSizes { print("fileSize", fileSize) for (i, blockSize) in blockSizes.enumerated() { print("blockSize", blockSize) let date = Date() for j in 0..<repetitions { try? FileManager.default.removeItem(at: destination) var date = Date() print("create", terminator: " ") createFile(source: source, size: fileSize) print(-date.timeIntervalSinceNow) date = Date() print("copy", terminator: " ") do { try copy(source: source, destination: destination, blockSize: blockSize) } catch { preconditionFailure(error.localizedDescription) } let time = -date.timeIntervalSinceNow times[i][j] = time print(time) } let average = -date.timeIntervalSinceNow / Double(repetitions) print("average copy", average) print() } let header = blockSizes.map({ NumberFormatter.localizedString(from: $0 as NSNumber, number: .decimal) }).joined(separator: "\t") try! Data(([header] + (0..<repetitions).map { j in (["\(j)"] + (0..<blockSizes.count).map { i in return timeToString(times[i][j]) }).joined(separator: "\t") }).joined(separator: "\n").utf8).write(to: url.appendingPathComponent("results \(fileSize).tsv")) } } func timeToString(_ time: TimeInterval) -> String { return String(format: "%.6f", time) } func createFile(source: URL, size: Int) { var buffer = UnsafeMutableRawBufferPointer.allocate(byteCount: size, alignment: Int(getpagesize())) // for i in 0..<size { // buffer[i] = UInt8.random(in: 0...255) // } let fp = fopen(source.path, "w") let success = fcntl(fileno(fp), F_NOCACHE, 1) assert(success == 0) let bytes = fwrite(buffer.baseAddress!, 1, size, fp) assert(bytes == size) fclose(fp) } func copy(source: URL, destination: URL, blockSize: Int32) throws { try source.withUnsafeFileSystemRepresentation { sourcePath in try destination.withUnsafeFileSystemRepresentation { destinationPath in let state = copyfile_state_alloc() defer { copyfile_state_free(state) } var blockSize = blockSize if copyfile_state_set(state, UInt32(COPYFILE_STATE_BSIZE), &blockSize) != 0 || copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CB), unsafeBitCast(copyfileCallback, to: UnsafeRawPointer.self)) != 0 || copyfile_state_set(state, UInt32(COPYFILE_STATE_STATUS_CTX), unsafeBitCast(self, to: UnsafeRawPointer.self)) != 0 || copyfile(sourcePath, destinationPath, state, copyfile_flags_t(COPYFILE_ALL | COPYFILE_NOFOLLOW | COPYFILE_EXCL)) != 0 { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) } } } } private let copyfileCallback: copyfile_callback_t = { what, stage, state, src, dst, ctx in if what == COPYFILE_COPY_DATA { if stage == COPYFILE_ERR { return COPYFILE_QUIT } } return COPYFILE_CONTINUE } }
Topic: App & System Services SubTopic: General Tags:
Jan ’24
Reply to Making filecopy faster by changing block size
Do you imagine that it somehow reads beyond the end of the file on the disk until it reaches the block size you have specified? In case you're wondering why I don't simply use a fixed block size of 16_777_216: I thought that the larger the allocated space, the more time it would take to allocate it, and the less space would be available to the rest of the system while the copy is in progress. It may be a negligible time difference, but since I can avoid it with a very simple calculation, why not do it?
Topic: App & System Services SubTopic: General Tags:
Jan ’24
Reply to Making filecopy faster by changing block size
Things will go better if your block size matches the allocation block size of the underlying volume Thanks again for your input. To clarify the "things will go better" part: does that mean that providing a block size that doesn't match can impact performance? Should I make sure to calculate the next multiple of two bigger than the file size to copy (with a maximum threshold of course), or can I simply do this: var bsize = Int32(16_777_216) if let size = Int32(exactly: file.size) { bsize = min(size, bsize) }
Topic: App & System Services SubTopic: General Tags:
Jan ’24
Reply to getattrlistbulk returns [ERANGE] (34) when supplied a 16K buffer; does that make sense?
I just had a customer reporting the same issue with getattrlistbulk and ERANGE: Result too large. According to them, it happens on macOS 10.13 with APFS but doesn't happen on macOS 10.14 anymore. I can also confirm that it only happens when the last call exactly fills the buffer. Running FileManager.contentsOfDirectory(atPath:) returns the cumulative amount of entries processed with getattrlistbulk and throws no error.
Topic: App & System Services SubTopic: Core OS Tags:
Jan ’24
Reply to Making filecopy faster by changing block size
Why do you say preferred 1KB, when in the same sentence you say the preferred block size is 1MB? Sorry, my mistake. Let me give you some actual results: when using the "preferred" 1_048_576 block size (returned by URLResourceKey.preferredIOBlockSizeKey) it takes about 6.15 seconds to copy a 7 GB file from my Mac to another folder on the same Mac. When using 16_777_216 block size, I get about 3 seconds instead. If I don't set the block size option for filecopy, I get about the same time as with the preferred block size, so I guess it's probably the same value. My question is: what makes the 1_048_576 block size "preferred", since using 16_777_216 drastically increases the filecopy performance? And can we assume that increasing the block size will always give better performance for filecopy?
Topic: App & System Services SubTopic: General Tags:
Jan ’24
Reply to Making filecopy faster by changing block size
Thanks a lot for your valuable input! Things will go better if your block size matches the allocation block size of the underlying volume When you say "match", do you mean equal, or a multiple, or something else? I'm asking because the value returned by URLResourceKey.preferredIOBlockSizeKey for my Macintosh HD is 1048576, but significantly increasing the block size of filecopy from the "preferred" 1KB to 1MB or higher usually performs better.
Topic: App & System Services SubTopic: General Tags:
Jan ’24
Reply to NEFilterDataProvider.handleNewFlow(_:) gets called with same flow ids multiple times
Are only seeing this with mDNSResponder flows? The majority is /usr/sbin/mDNSResponder, but I also get others, such as /usr/sbin/netbiosd. (r. 115822797) Can I look this somewhere up, or is it just for your own reference? Do you think this behaviour is a bug, or does it mean that the earlier flow is invalid once the double one comes in, or what does it mean?
Dec ’23
Reply to Xcode warning for call to DispatchQueue.main.sync: Call to method 'asd' in closure requires explicit use of 'self' to make capture semantics explicit
It’s probably worth filing a bug about that. Please post your bug number, just for the record. I created FB13454498. First, I recommend against using Dispatch global concurrent queues, as explained in Avoid Dispatch Global Concurrent Queues. Thank you. While the second WWDC link on the linked page correctly leads to a video, the first link (WWDC 2015 Session 718 Building Responsive and Efficient Apps with GCD) leads to a list of videos where that video cannot be found. Does it still exist? Second, synchronously dispatching to the main queue is a concern because it can block the current thread for long periods of time. I know, but that thread needs access to a resource possibly occupied by the main thread before being able to continue with its work. I think you’d be much better off investing in Swift concurrency. I'm still supporting macOS 10.13, while Swift concurrency is only available on macOS 10.15.
Topic: Programming Languages SubTopic: Swift Tags:
Dec ’23