CryptoKit: SHA256 much much slower than `CryptoSwift`

Hey, CryptoKit's SHA256 (using it twice), is 3 times slower than CryptoSwift's implementation. I was hoping it would be the other way around.

Is this poor performance something being address currently? I know CryptoKit is in beta.

Same poor performance using optimization level Fastest, Fast or None.

Can you post an example of how you're measuring the performance?

2 Likes

Of course, how silly of me, I will fix a self-contained example, wait for it :)

Here is a Xcode 11 project with Unit test comparing performance. I got slightly different results, not 3 times as slow but between 40-80% slower.

And BitcoinKit which uses OpenSSL performs almost identical to CryptoSwift. However that test is not included in the demo project above.

Assuming I'm reading your project correctly (and I've only skimmed it, sorry), you're comparing performance on the iOS Simulator? That's not really an environment where performance comparisons are meaningful. In particular, many OS components use default implementations when running on the simulator for a variety of reasons, while they use tuned implementations when running on iOS or macOS. Have you benchmarked against the native libraries at all?

Note that I'm very much not saying that it's not a problem, rather that simulator performance is significantly lower priority than iOS or macOS performance for most teams. In any event, your best course of action is to file a bug report via Feedback Assistant, since CryptoKit is an Apple framework, not part of the Swift project umbrella (meaning that the team responsible doesn't generally look for feedback here).

1 Like

Yes sorry for not making that clear, I've only done performance tests using Unit Tests on iOS Simulator, that is correct.

Do you mean that is completely irrelevant? IMO it is relevant, most of my Swift dev time I use the simulator.

I changed to macOS SDK and added an example macOS app (no GUI, just initiating background task from AppDelegate) and running it on 2016 Macbook Pro yields ~ same result everytime, CryptoKit being 60% slower than CryptoSwift.

Checkout the branch named macOSApp

1 Like

Ok, that's a much more interesting issue then. I would encourage you to report it as a bug via Feedback Assistant, and attach your sample project.

1 Like

Thanks! I created an ssue using Feedback Assistant, not sure if those are visible to you (you need to sign in), but posting link to it anyway, for reference: Feedback Assistant

1 Like

I created an ssue using Feedback Assistant

Thanks! I had a quick look and it’s definitely landed in the right place.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

4 Likes

I tried adding a CommonCrypto-based implementation:

import CommonCrypto
public func sha256sha256(of data: Data) -> Data {
	var buffer = UnsafeMutableRawPointer.allocate(byteCount: Int(CC_SHA256_DIGEST_LENGTH), alignment: 8)
	var buffer2 = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(CC_SHA256_DIGEST_LENGTH))
	defer {
		buffer.deallocate()
		buffer2.deallocate()
	}
	data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> () in
		CC_SHA256(ptr.baseAddress!, CC_LONG(data.count), buffer.assumingMemoryBound(to: UInt8.self))
	}
	CC_SHA256(buffer, CC_LONG(CC_SHA256_DIGEST_LENGTH), buffer2)
	return Data(buffer: UnsafeBufferPointer(start: buffer2, count: Int(CC_SHA256_DIGEST_LENGTH)))
}

It ran in 1.094s compared 3.847 for CryptoSwift on my computer (I'm still on 10.14 so I couldn't test the CryptoKit one)
Worse, according to Instruments, only 279ms of that was actually spent in CC_SHA256, the rest was spent allocating and throwing away buffers, so if you spent a bit of time optimizing you should be able to do things much faster.

Based on this, I think that your issues are just as likely to be caused by you making a few extra data copies (for example when you call Data(digest)) or other things. I would recommend profiling your code in Instruments to see how much of this time is due to the library compared to how much of the time is due to your handling of its results.

Also you should probably turn on Whole Module Optimization

1 Like

Yes I did profile, and profiler told me 71% of the execution time of my simple POW function was spent on SHA256 calls

I think you’re over-releasing buffer2 (don’t have the docs handy, but I don’t think that initialiser copies the data)

IMO the cleanest way to do this would be to create a Data object with the required size and use its withUnsafeMutableBytes method to get the internal pointer to populate it.

I agree that writing directly into a data would be a better idea, but I don't think the current code double frees. IIRC there's a data initializer with "NoCopy" in the name that doesn't copy, and the rest do. Also double-frees usually blow up pretty quickly (free will trap) so I think I would have noticed.

Thanks a lot Karl for suggested performance optimization, much appriciated. In fact I knew the implementation of the POW is not optimized, I haven't got around to it yet.

But that is irrelevant to this thread I believe? It is independent? Since the fact remains, that the same code using either CryptoKit or CryptoSwift results in a big difference in performance...

Anyway, Apple followed up a couple of days ago, so awaiting response from them.

Thx guys!