📜 ⬆️ ⬇️

Tuning Swift compiler. Part 2

image


Continuing research into ways to speed up Swift compilation. Mockery of the semantic analyzer and unexpected project settings.


Link to the first part for those who missed.



Introduction


Good day, gentlemen, developers. I want to thank all those who did not avoid the last post, it was extremely nice to get feedback. I hope you enjoy this article as much. Without long preludes I will say: today there will be no analysis of the speed of various operands of the guard and if-else types, it will not be a boring chase for nano-seconds in a cycle of 100,000 iterations. We found something more interesting.


Old bug is better than new two


Apple, I repaired everything, it's going again 12 hours!


image


Just kidding, the type is just wrong. There is not an array, but a dictionary. However, the compiler goes into a rage from such tricks. This is a bad indicator, but in the end we ourselves were mistaken.


Correct the error. Put the Dictionary instead of Array.


image


Who turned off the lights? Segmentation error, backlight off, compiler stalled. With such symptoms, we have a type-checker crash that can never figure out which key value we are waiting for. Going into the report from the assembly, we see the stacktrace, which tells us about it:


image


Good. What does this give us and how can it be used? To answer this question you will have to load you with a bit of theory:


The Swift compiler consists of several modules:



Graphically, this sequence looks like this:


image


Do not rush to google every term, we just need to get an idea that the Swift compiler consists of modules.


@Johan in the comment to the previous article correctly suggested that the problem is associated with type inference. And this task is just a semantic analyzer. All the brakes that we saw in the last article apply to it.


So, we found out that Swift painfully perceives type casting. Now that we know where our legs grow from, let's take advantage of this and clearly indicate the format of our dictionary:


let myCompany: [String: [String: [String: String]]] = [ "employees": [ "employee 1": ["attribute": "value"], "employee 2": ["attribute": "value"], "employee 3": ["attribute": "value"], "employee 4": ["attribute": "value"], "employee 5": ["attribute": "value"], "employee 6": ["attribute": "value"], "employee 7": ["attribute": "value"], "employee 8": ["attribute": "value"], "employee 9": ["attribute": "value"], "employee 10": ["attribute": "value"], "employee 11": ["attribute": "value"], "employee 12": ["attribute": "value"], "employee 13": ["attribute": "value"], "employee 14": ["attribute": "value"], "employee 15": ["attribute": "value"], "employee 16": ["attribute": "value"], "employee 17": ["attribute": "value"], "employee 18": ["attribute": "value"], "employee 19": ["attribute": "value"], "employee 20": ["attribute": "value"], ] ] 

Compile time : 30 ms. It was 90 ms. Accelerate three times.


Success. We conclude that it is better to always explicitly specify types , and not to rely on the compiler wit. By the way, there is a downside. If you type the wrong type, it will take a long time before the compiler understands where you sent it ¯ \ _ (ツ) _ / ¯


Another question may arise, is there a difference between the literal and the explicit indication of the Dictionary / Array class? I will say right away - this is one and the same, it does not affect the compilation and execution time. But for readability I recommend to use literal.


Some fun code

Since nested dictionaries in Swift are optional, you can get the following punctuation:


 var myCompany: [String: [String: String]?] = [ "employees": [ "attribute" : "value"] ] let  = myCompany["employees"] print(?!) 

And it compiles.


More speed


There is a stereotype that the inclusion of optimization slows down the assembly. However, if you read the documentation of the compiler , it turns out the opposite.


By default, the compiler collects each file individually. If you open the build log with default settings, you will see a report on each Swift file separately:


image


In the optimization settings there is a flag like whole-module-optimization. With this flag, the compiler considers the project as a whole, sees all the available functions in its entirety and saves on unnecessary casting of types.


In cases of compiling with this flag, the assembly of all Swift classes is combined into one operation:


image


Let's compare the speed now. Take some abstract test project . Let it be one of the open source creations of our company. It is not distinguished by the purity of the code and the genius of the solutions, which is what we need.


We first collect without optimization:


image


Compile time : 84 seconds.


Now enable whole-module-optimization:


image


Compile time : 52 seconds. Win 40%!


Naturally, this also has a positive effect on performance. In my personal experience, it gives ~ 10% increase to the overall speed.


As usual, it is worth asking the question: "What is the catch?". When you start the application and park at breakpoint, the debugger tells us the following:


image


Font braille: "Project was compiled with optimization - stepping may behave oddly; variables may not be available."


Translation: "The project was compiled with optimizations - debugging steps may behave strangely; some variables may be missing."


The point is that the optimization removes unused variables and dead code, so there may be strange failures in debugging. Indeed, situations often occur with suddenly disappearing variables or the debugger’s reluctance to stop at breakpoint.


The “Fast” parameter is to blame for everything, which invariably comes with whole-module-optimization in the Xcode optimization settings. Need to get rid of it. Let's return the optimization to 'None', but now we will try to enable WMO through the project settings.


To do this, we just need to add the SWIFT_WHOLE_MODULE_OPTIMIZATION = YES flag in the user-defined-settings.


To do this, go to the project’s build settings and select a plus on the top panel. In the drop-down menu, click on the user-defined settings and set the value in the usual line:


image


Result:


image


Compile time : 19 seconds. Initially it was 1.5 minutes.


The build speed jumped significantly and the warning during debugging was gone. However, full module optimization imposes certain limitations. For example, you cannot use the flag 'HEADERMAP_USES_VFS' (suggested by GxocT ), which solves the issue with incremental compilation in some cases. However, in terms of speed then it comes out.


Conclusion : whole-module-optimization significantly speeds up the compilation process if you don’t use your incompatible flags.


Bonus

On the Rights of jokes.


In the comments to the previous article, they wrote that Swift has an incremental compilation.
For the experiment, I pre-assembled the project and added a single print to the existing method. That's what came out of it:



Who can not see: 50 seconds build from scratch, 45 seconds incremental.


If you believe stackoverflow , then in Xcode 8.2 these problems will be fixed. Unfortunately, my project on the beta version is not going to. So we will wait for the official release.




On this second part we summarize. In it, we found out the main cause of the compiler inhibition, found the optimizer flags to speed up the build, and also observed the imperfection of the incremental compilation in Swift.


In the next article we will look at ways to fix incremental compilation and general ways to speed up a project.


Thanks:
GrimMaple - for advice on the structure of the compiler and help in the selection of formulations.


')

Source: https://habr.com/ru/post/317298/


All Articles