dotSwift 2019 - Johannes Weiss - High-performance systems in Swift

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
yeah [Applause] [Music] [Applause] hey so first of all don't get intimidated by my by my titles what we're really talking about here is how Swift programs can be slow or any program really and then how not to do that and there's so much to say and that we learn from Swift neo and other and other Swift programs that I can only focus on one thing today which is demystifying class versus struct for performance because that's something I have heard from the very beginning in the Swift community and I always had problems with it and today we'll cut through a chase and see what's going on underneath when we talk about performance obviously we want to understand what makes programs go slow by far the most driving factor is probably the algorithm complexity as you can see here if you by accident use an algorithm that should be if there's an auger is available that isn't like constant time or n the accident you use one of this and oh if N squared then you'll lose and nothing will see later on today will really help you you need to get your algorithms right because that is a very driving factor for performance of your program however if you got everything right your program can still not be as fast as you would like it to be and one of the reasons for that can be overhead some of the overhead can be introduced by you and you will well understand why that happens for example if you sprinkle print statements everywhere sure they'll make it run your program runs slow and that's just constant overhead that's sprinkled everywhere but in Swift there's also pretty much in any language there is also other overhead that the compiler and the runtime do for you without necessarily the programmer understanding what's going on and Swift is quite like a specific language there so there's a lot to add with the algorithmic complexities with this I guess similar to all the other languages right because that's an algorithmic thing the overhead can often be slightly closer to the language despite working with Swift neo team I'm not giving a networking talk today however the examples are still picked from from Swift neo so DS today's example is an HTTP request there's not too much really interesting to see we have got a structure here that is the HTTP request they go to method get pulse and you name it target just like the path and URL everything that comes after the hostname HTTP version couldn't be more boring than that the headers you know what I'm talking about the body in this case represented fairly simply just an array of bytes and a trailer so that's a less unknown feature it's just like the headers but at the end and this structure is not directly taken from Swift neo because it's with new we need to stream everything because we want to support all use cases where the body looks different but it's close enough the next most important thing is that HTTP requests here's a value type and that's really close to my heart and I think the Swift community has also learned over the time that value types add a lot of benefit because it makes your life easier because you know you you're dealing with basically your own independent value you can do to it whatever you want no one else will be affected that's great this example is a very silly one let's assume we we add a dot conferences we're writing some app and like we have one URL for the dot Swift conference and then we want to cover the dot AI conference we make a new variable a I assign Swift because we bootstrap everything from Swift and then we just change the target to dot AI and obviously we want add the target of Swift target still refers to dot Swift we don't want the AI to take over Swift but that's really just added we want to talk about performance here and in this case we want to talk about the performance of calling a function transform that transforms a HTTP request that's something that happens in many web frameworks some middleware might get an HTP request might transform at Hana to the next middleware and so on so basically the HTTP request gets passed around in your program quite a lot to see what effect on performance our HTTP request structure has I wrote the most simple transform function you can imagine is the one that doesn't transform it just returns it and then I timed how long it takes to run it 52 nanoseconds the absolute number here 50 nanoseconds has absolutely no significance that is on my computer on that particular day with that particular snapshot of the Swift compiler don't really doesn't really matter what matters here let's just state it 252 and when benchmarking things you often need a baseline what is what is what am i competing with and I like to benchmark against something that I cannot achieve like the absolute limit that is basically all maybe absolutely unachievable so I guess it's pretty obvious that one of the most simple types in Swift is an integer and a transform function adjust transform one transforms one integer to another we'll probably be faster to call than our HTTP request structure because a lot more stuff needs to happen and we'll see later what but this turn difference is quite big the factor he is 52 I think it's reasonable to expect that it will be slower to call a function with a HTTP request and returning it but 52 sounds a little bit much to me to understand why that is let's look what a compiler and the runtime will do first let's start with the integer the green box here is our value 23 our integer if I call a function the only thing that a run that really needs to happen is the function the value the integer needs to be copied in the right processor register and we can jump in the function how does it look like this 21:23 gets copied and we're really done but now we want to understand what happens if I call this very similar function with my HTTP request to understand what's going on there we need to understand that they hate that a struct in Swift is really like a veneer over a collection of other things like over a properties the store properties so really what you should compare is that function is at runtime much like a function that has all my members as individual parameters and a long list of return values so now it becomes already already apparent why maybe some more work is going on here the Swift you can also ask Swift about the size of any type and for this particular structure will tell you 64 that means 64 bytes to make it even worse however much of the many of these fields have a variable like length types like strings and arrays for the body and for the headers in the trailers and obviously a compile time we don't know the length of them so their actual values the underlying strings and and-and-and-and headers and body bytes are stored on the heap significant part is Rev the significant signified by the read books that's the heap and the gray ones are the pointers from these structures the values to they're backing store on the heap where they store the bytes and all these things to manage the lifetime of these backing stores we need a reference count that's signified by this little pound symbol here oh by the four little pound symbols indeed and now let's have a look what happens if we copy this HTTP request around so we pass it one function and then back again we only look at the path to enter function not the one to return the first of all we copy the string struct around and the other thing that needs to happen we need to bump the reference count you might have seen this little blue arrow appearing next thing that happens we copied a version around that's again two words because it's two integer no reference count this time because it's just two integers and to speed it up animated the last four ones together we copy another four words and we bump three reference counts so what happens in total is we copy eight words so eight green boxes get moved into this blue thing and we increase for reference counts and sure enough if you increase the reference count somewhere somewhere else you will need to decrease it and I guess that explains now why does it take a longer to pass this HTTP truck HTTP request around then it does take the interim because the end of which is just one box and then we need to move a move eight boxes and for reference counts and a lot of the Swift server community has seen this problem as well they did some measurements a number came out and they did some other measurements that is not a good idea now like into why that is not a good idea but they just changed the words truck to the word class and they're the same benchmark again what happens at runtime destructively just the box that goes on a heap and the only thing we need to copy around is the pointer to this class and we need to increase only one reference count it doesn't matter what you put in a class because basically it's get get box up put on a heap and it's there they do the maximum everyone did their measurements and 50 nanoseconds that looks a hell of a lot better than 52 is almost factor 4 and that is where the whole fallacy started that people say oh somehow classes are faster than structs and that is not true but you can see how the argument goes that we have seen a proof we done a benchmark it must be the truth but it's not quite let's see what so first let's have a look why do we not want to have a Rach TV request as a class because it's a value type it should have value semantics if we did not do that so if we implement the HTTP requests as seen before as a class to be able to pass it around faster because that might be important to our program we lose the value of semantics so in this case even the despite the fact we have two variables AI and Swift they share they share the same class reference so if I change the target of the AI I'll unfortunately also change the target of the Swift and that's not what we want that introduces a lot of problems with threading you need to think about when to copy it and a lot of things like that so what we really want to have is a value value semantics but for this particular case we're going to be as fast as the class to pass around because we saw that it was faster and now we'll learn how we can achieve that so we want to have a struct HTTP request again because that's the semantics we want but we wanted at runtime the copying to look like the class in this particular case because we saw it was faster now can we achieve that the answer is yes we can and the set one is really very simple we create a struct HTTP request as before and we move all the stored properties into in a class we call underscore Scott Authority the underscore doesn't really mean anything that it's like is just the containers like to maybe makes makes the program easier to read I don't know but at the very moment our struct HTTP request is very very useless because it doesn't have any properties I can't get them I can set them I can't even construct it that's a step one so now we get need to get these properties back we want to read to target and set a target idea one is okay that's easy let's create a setter and if we I want to get the target I just reach into the storage pull the target out and return it same thing if I want to set it I take the new target and put it into that storage on the heap right yeah no not great idea at the beginning it looks like yeah works fine you know I got got an a my HTTP request I can assign it read everything works fine because in memory it looks like that I got my one struct and I got my one storage one-to-one relationship great now some programmer it's a second variable how Daddy and pointed to the same a and change the target and now we run into a big problem because again we have this situation we have these two structs point to the same backing storage since changing one struct over writes the other structs data as well which is not what we want because we always just reach into the storage so that's a problem so we have seen if we have this one-to-one relationship one the struct points to one class then we can modify it straight away because no one else holds a holds a reference to this backing store we can just mutate it along reading and out is always simple we just go to our storage read out the problem rises when we have two structs to values pointer to same backing store and we want to mutate one of them that is a problem what we really want to do then is copy the backing store so we have to again and then we can modify luckily enough the Swift Sanna library provides a function with the slightly long name is known uniquely referenced to which you can pass a class reference and it will tell you if you're the only person holding this reference or if there's others you are sharing it with and that sounds like exactly what we want because the Left situation good we can just right through and everything is fine on the right situation where non uniquely reference will say false we need to do a copy that we get into the left situation how'd that make some sense and implementing that is actually not that hard again as I said to get her we just reach out into the storage get whatever I want in this case a target from the storage but when we set we need this to three extra lines we need to ask are we the unique owner of this backing store if we are great it doesn't do anything we fall through self storage target equals new value we just changed it perfect no one else has a reference to that no one else will see if we should we not be the only owner also pretty simple we just create a new storage by copying the old storage but only when we need it the coffee method is very very simple because values compose very nicely my struct was built out of values and to copy them I just need to assign them to a new storage so the only thing that happens here I locate a new storage in a heap copy all the properties over and that only happens when I need to when and that is the case when we have to referring to the same backing store and you might have heard the phrase copy-on-write for arrays dictionaries strings and many others for theta types and that's exactly what to implement in exactly this very same way the cool thing is we copy-on-write if necessary we never copy and read we only copy on right if we are actually sharing the backing store with someone else the cool thing is now we're back to what we wanted we have our struct HTTP requests and many slides back I told you a strike is really like a thin veneer over whatever distort properties are in this new struct HP request I only have one store property which is a which the storage would happen to be a class so exactly that's exactly what we get we only need to copy one word around we need to bump exactly one reference count and we still have the value semantics because through copy-on-write we can we can have exactly the same properties back that we had before but the copying of this HTTP request is just as fast as copying the class around without losing the semantics we want which are the values semantics again I did a measurement and sure enough 50 nanoseconds pow bang on there is nothing there's nothing different than passing a class around but to the programmer there's a massive difference we have the semantics that we want with the performance that we want by controlling what Swift does under the hood in the API between the first short HTTP Chris and this one is exactly the same the semantics exactly the same we don't even notice the best thing is you can always start out with a simple version and if performance is a problem you can change it later no one will see it we'll just work a little bit faster some closing thoughts SAS class versus struct is about semantics not performance when you choose what a type should be you should go after the semantics if you want a value type it's a struct if you want it can also be an enum if you want reference semantics it will be a class you can also make structs have reference amanda's as we saw before but usually that's that's what you what you aim for the next point the next point we should cover is should I didn't cow cow copy-on-write should a cow box all my structs should I apply this this recipe to all the struct I have please don't that is one tool in the toolbox that we might use if it if it proves to be necessary but please don't go ahead and make everything a cow box first of all you must have a problem and then you must find that this is the right tool the next question we should address is could a compiler help us could a compiler make it automatic for example could it automatically box all the structures for us and we wouldn't even wouldn't even see that and well it could but I would argue that's not a good idea because the cool thing about a struct is it has exactly the same performance at passing everything around that's in the struct and sometimes that can be faster at the very very beginning we saw this in example it happens to be a struct as well and we saw it was even faster than passed in a class around because we don't need any reference counts so if the compiler decided oh I'll just make everything a cow box it would make many programs slower so we need to have this control but only if we see that as a performance problem so to wrap that up first of all make sure your code works that's by far the most important thing if you if it turns out that you have a proof of a performance problem go and check your algorithms that will by bike by and large that will make the biggest impact on the performance of your problem if you still have a problem then identify the eye area and measure write a benchmark and I like to also have a baseline as we had before to see what is like the limit how far can we push right because I had no idea is it 50 times slow is it two times slow is a 1 million times slower you'd like I don't at least myself I don't have a good feeling so setting a pen baseline is good and if it actually turns out that is the problem like in this case this HTTP structure HTTP request structure needs to be copied around and the whole program and that unfortunately performed worse than this the class then optimize it and this is one trick in the toolbox and I hope you now don't believe anyone anymore that says classes fathil instruct because a class can always be made to be exactly as fast as a class by putting a class into a struct potentially adding even the semantics for value type depending if the type you're have at hand is supposed to be a value type but not right that's ever again everything I got I hope that was useful and thank you very much for your attention [Applause] [Music] [Applause] [Music]
Info
Channel: dotconferences
Views: 3,372
Rating: undefined out of 5
Keywords: dotSwift, Conference, Swift
Id: iLDldae64xE
Channel Id: undefined
Length: 18min 8sec (1088 seconds)
Published: Fri Feb 22 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.