Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize bottlenecks #529

Merged
merged 11 commits into from
Apr 2, 2020
Merged

Optimize bottlenecks #529

merged 11 commits into from
Apr 2, 2020

Conversation

michaeleisel
Copy link
Contributor

Short description 📝

Optimizes a few of the big bottlenecks. I can split it up into smaller PRs if wanted.

Solution 📦

Replace high-level Swift ways of doing things, which in these cases cause significant overhead, with fast and low-level ways of doing things

CExts.c Outdated
@@ -0,0 +1,59 @@
//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you remove the headers from the file to be consistent with the rest of the codebase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

let MD5Calculator = MD5(message)
let MD5Data = MD5Calculator.calculate()
private func toHex(_ char: UInt8) -> UInt8 {
if char < 10 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind adding a comment explaining the rationale behind comparing char with 10?

CExts.c Outdated Show resolved Hide resolved
CExts.c Outdated Show resolved Hide resolved
for c in MD5Data {
MD5String += String(format: "%02x", c)
var md5: String {
guard let data = data(using: .utf8, allowLossyConversion: true) else { abort() }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aborting with no message is a terrible user experience for the user. What about passing a string with a explanatory comment of what happened?

var md5: String {
guard let data = data(using: .utf8, allowLossyConversion: true) else { abort() }
return data.withUnsafeBytes { bufferPointer in
let buffer = malloc(Int(CC_MD5_DIGEST_LENGTH))!.assumingMemoryBound(to: UInt8.self)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic here is hard to read. To simplify the understanding and maintenance of it in the future, I'd add a brief explanation of what it does.

Copy link
Contributor

@pepicrft pepicrft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much @michaeleisel for improving the performance of XcodeProj. Overall it looks good. I just left some minor comments that are worth addressing before merging the PR.

@pepicrft
Copy link
Contributor

Also, would you mind adding an entry to the CHANGELOG?

@michaeleisel
Copy link
Contributor Author

The big question I'd like to discuss is, how to deal with the .c file? SwiftPM doesn't support mixed-language projects, and other package managers have issues as well. This is the first non-Swift file, right?

Here are some options:

  • Translate the C to Swift where it makes extensive use of unsafe pointers. This may incur some performance overhead
  • Create a separate package, e.g. XcodeProjCExt, whose version is synced with that of XcodeProj. This is what I do for https://github.com/michaeleisel/ZippyJSON, which puts its non-Swift code in https://github.com/michaeleisel/ZippyJSONCFamily . This is a pain, but also a good long-term investment if it will be good to have more code like this in the future. XcodeProj is used in large projects where it has a large workload, so I think it could be worth it. Just as a thought experiment, if the whole project were written in C++ (which I am not actually suggesting), it could easily be 10x faster.
  • ?

@pepicrft
Copy link
Contributor

I lean more towards the second option, having an independent package with those extensions. I added you to the contributors team, which has access to this repository that I just created to host that package. Feel free to push the extension there and the Package.swift to define the dependency from this PR.

Thanks a lot @michaeleisel for bringing this improvement. Performance is something we've disregarded for a long time and it's great having your experience with C and your contributions to make XcodeProj as fast as possible.

@michaeleisel
Copy link
Contributor Author

🎉

@michaeleisel
Copy link
Contributor Author

just pushed the package. note that the first branch i pushed was not master, it was me-init. so, that's now the default branch, which you may want to change in the settings

@michaeleisel
Copy link
Contributor Author

michaeleisel commented Mar 16, 2020

also, now that we're using C and unsafe Swift, in case we're not already, it would be good to run unit tests pre-release with:

  • address sanitizer
  • thread sanitizer
  • memory leaks
  • guard malloc

var MD5String = String()
for c in MD5Data {
MD5String += String(format: "%02x", c)
var md5: String {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can rewrite the meat of this function in C as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be faster (swift is not very good at stack allocation of primitive arrays

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. If you think it'll be faster let's go for it!

@pepicrft
Copy link
Contributor

image

😛Just changed it to be master the default. Would you mind pushing your changes to master?

@pepicrft
Copy link
Contributor

also, now that we're using C and unsafe Swift, in case we're not already, it would be good to run unit tests pre-release with:

Haven't done it before. Is there any flag that we can pass to the SPM to run the tests with those things enabled? Feel free to adjust the GitHub Actions workflow as needed.

@michaeleisel
Copy link
Contributor Author

ptal. fyi, i'm having issues with doing .package(url: "https://github.com/tuist/XcodeProjCExt", .exact("7.8.0")) (although .package(path: "../XcodeProjCExt") works). i get "invalidAccess". any ideas?

@michaeleisel
Copy link
Contributor Author

it looks like the github workflow doesn't do any unit tests? are unit tests run in some other way, pre-release?

@michaeleisel
Copy link
Contributor Author

Got the invalidAccess thing fixed (just a cache issue). For swiftpm you can add --sanitize=<sanitizer>, e.g. swift test --sanitize=address

@pepicrft
Copy link
Contributor

Are we good to merge @michaeleisel ?

@michaeleisel
Copy link
Contributor Author

i have one more change i want to make, but i want to make sure it doesn't cause any perf regressions. are there are any perf tests, preferably end-to-end?

@pepicrft
Copy link
Contributor

i have one more change i want to make, but i want to make sure it doesn't cause any perf regressions. are there are any perf tests, preferably end-to-end?

We don't have at the moment. Do you want to add them as part of this PR?

@michaeleisel
Copy link
Contributor Author

OK it looks good to me now. No perf tests, but that could be for another day

@michaeleisel
Copy link
Contributor Author

Are we good to merge?

@pepicrft pepicrft merged commit b722cee into tuist:master Apr 2, 2020
@elliottwilliams elliottwilliams mentioned this pull request Nov 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants