 So I'm going to go ahead and get started. So first off, thank you everybody for being here. My joke committed to go aside. It's always really nice to have people actually combine, want to listen to what you have to say, especially when you've not actually given this talk before. So this is my first time giving this talk, hopefully. I think it's some very interesting stuff, and hopefully people learn a few things from it. But the obviously inflammatory title, which was clearly intended to make a statement, everything you know about the guilt is wrong. Now I'm sure that there's at least one person in the room sitting over there who knows way more about the guilt than I do, but for the most part, a lot of people don't understand this particular thing. So we're going to talk about it. So my name is Jerry DeAntonio. I am from Akron, Ohio. How many people here have ever heard of Akron? Now how many people have ever heard of LeBron James? A local kid lives with the high school just down the street from me, pretty good at basketball. I worked at Test Double. So many of you have probably heard of Test Double. Some of you probably went inside Justin's talk yesterday morning, how to stop pating your, excuse me, how to stop pating your test suite. So Justin's one of the founders of Test Double. And of course, we're a consulting company out of Columbus, Ohio, and I work there. So beyond that, the probably most relevant thing about me with respect to this particular conversation is I created this thing here. It is a gem called Concurrent Ruby. Okay, who's heard of this? Anybody just like to ask me, okay cool, that's cool. So Concurrent Ruby is a Ruby gem that has intended to provide some suite of concurrency tools for Ruby to extend our options of building concurrent applications. So Concurrent Ruby's being used by a number of projects, some of these you may have heard of, Rails, Sidekick, Logstash, Dyingflow, Volt, Amster, Pacquiao, Microsoft Azure, who uses it in their cloud. I know Sucker Punch is considering using it. So it's really humbling to see these projects on the list saying that they're using our work. But there's a sad unfortunate truth to all of this and that is that I've actually been wasting my time. That the whole thing about trying to build a concurrency gem for Ruby, this is the fool's area, right? There's a complete and total absolute waste of time. Why? Because everybody knows, let's say it, that Ruby can't do concurrency, right? Raise your hand if you've ever heard somebody say, Ruby can't do concurrency. All right, so let's just get that out of the way. So for those of you who have not heard it, clearly you don't have Twitter accounts, right? Because if you follow Twitter, you will find that apparently Ruby cannot do concurrency. And if I heard it out of the internet, it clearly must be true, right? So being that I know how to Google and I know how to use the internet thing, I thought, well, before I give this presentation about the GIL, how about if I actually look up a few Fat Toys about the GIL? That'd be fun, right? So let's talk about what this thing the GIL is. So I did some Googling and I came up with a couple of Fat Toys. So according to the internet, here are a few things. First, Ruby has this thing called a global interpreter lock. Also called a global virtual machine lock, or GVL, right? Have you ever heard that before? Here's a couple of Fat Toys I picked up on the internet about the GIL. The GIL is a soulless, heartless, and pure evil. The GIL hates you and it wants you personally to be miserable. The GIL eats babies for breakfast. No, seriously, I read this, right? It eats babies for breakfast, kids for dessert, and puppies for midnight snack, right? I'm pretty sure that the GIL is the sole cause of climate change and I've also think I did somewhere I saw that if there was no GIL, there would be no more, right? So pretty much you guys have all heard those, right? That's pretty much, all right, so. But you know, this is just so for fun. I mean, you're here, you're already sitting down, I've got like 40 more minutes, so let's take a look some code, all right? So we're gonna look some code. This is a quick sample program. Hopefully everybody can see this all right. What this is, is this is sort of one of my go-tos for showing concurrency stuff, right? What we're gonna do is, basically, I'm going to go out and hit an API and I'm going to the... I can't because this is actually PowerPoint and I apologize for that. So normally when I do this color scheme, it shows very well and so I'm sorry it's not, so I will try and explain it the best I can and I'll put this up on the web later on so I apologize for that. But what I'm gonna do here is I'm gonna go out and hit Yahoo Finance API, right? And I'm going to, I picked 20 stock ticker symbols. I got those from Bloomberg, I had to pick 20 and they had this list of here's 20 that really did well. And so I'm gonna go and I'm gonna hit this Yahoo Finance API, I'm gonna bring back for all 20 of these ticker symbols, I'm gonna bring back the data and I'm gonna pull out of that what the stock price was at the end of 2014, right? This is an arbitrary thing, that's never really meaning. So I've got two functions, so my top function is called get year-end closing and it just does that, right? It's just that, right? And then I've got a function called serial get year-end closing. Now what I'm gonna do with that is in there I'm gonna take that list of stock ticker symbols, I'm gonna iterate over that using the Ruby collect method and I'm going to retrieve those one at a time and put those in an array, right? So at the end of this I'll have an array with 20 prices that I pulled from that API. Now the next method is called concurrent get year-end closing, I'm gonna do the same thing but in this case rather than doing it serially I'm gonna do it concurrently, right? Now that thing I'm using is called a future, it's from the concurrent Ruby library, this is not meant to be a sales pitch for concurrent Ruby but I can do that in one line of code and it's very easy to read. So what's happening is I'm gonna do this thing called concurrent future, I'm gonna fire this thing off and say go get this thing and I'm gonna fire 20 of these things off, they're gonna go on to background threads, they're running a thread pool and then what's gonna happen is I'm gonna collect up those future objects, they're stateful objects, they will have their state updated when the task is done and then I'm gonna do another collect statement to go and actually retrieve those and get the array. So at the end of that I will have the same array that I'll have for the serial one, right? One extra line of code in order to do this but I'm gonna do it concurrently, right? So the second part of this script is going to be some benchmarking. Has anybody here used the benchmark before? Right, it's really cool, right? So for those of you who haven't, what it's gonna do is this particular one benchmark.bm will actually do a rehearsal phase, it'll run a bunch of the things I give it, it'll then determine how many times it has to do that in order to get adequate data and it'll then run it again and it'll give me the output and I can compare these things, right? So I'm gonna compare the execution of the serial method to the execution of the concurrent method, right? Now don't say anything but think to yourself right now what you should expect to see, right? Because as we know, Ruby can't do concurrency, right? Ruby has a gill, it's a lock, it prevents us from doing anything really fun and interesting and having like nice lives and so what we should expect here is that despite the fact that in the second case I'm gonna fire off all of these things, asynchronous on a thread pool that the amount of time it takes for each of these should be the same. Is that reasonable? I do it serially and then I get a certain amount of time and I do it concurrently because since we can't do concurrency in Ruby, it's gonna take the same amount of time, right? That's what we expect, okay? So just because we're here, we've got some time, let's run that and see what happens. And so when you look at the output of this, clearly you can see right there that it took about four seconds to do that serially and it took about four seconds to do that concurrently because Ruby can't do concurrency, right? Does everybody see that? Does everybody see that on there? Raise your hand if that's not what you see in that output, all right? I should see every hand in the room raised, okay. So what happened here is it took roughly four seconds to do that serially and then concurrently it took less than three, or 0.3 seconds, right? It took about one-tenth of the time. Clearly something is wrong with my test, right? So just for fun, let's compare this same thing to, I don't know, run times that can actually do concurrency, like I don't know, JRuby. So let's run that same thing on JRuby. This is JRuby 90.1010, right? That took about four seconds to do it serially, that makes sense, right? It's still Ruby code, it's still IO, but actually to go over a second to do concurrently. Interesting. What about Rubenius, right? Rubenius, right? That runs in LLVM, it doesn't have a GIL, it should be able to do similar, right? Well, it took that about four seconds to do it serially and okay, that makes sense, that seems pretty consistent, but it took that about 0.4 seconds to do that concurrently. So MRI Ruby took a little time for the last time to do that concurrently than the two run times that are actually supposed to be good at that. And they are good at that, don't get me wrong, but what I saw on the internet through all those tweets was that MRI Ruby was not actually any good at that, and yet in this particular case, it seemed to do okay. Apparently the internet lied to me, and I did not see that coming, or actually I did see it coming because I know what's going on, but let me ask you this question, be honest with me, was there anybody here that was surprised to see that MRI Ruby was able to perform that concurrently that fast? Anybody? Anybody? All right, so thank you very much for being honest, all right? So, but let's actually talk about why that is because how many people, let me ask this question, how many people would like to see a 10 times performance improvement in their applications? All right, I think everybody in the room should say that. So let's explain why that is because that really goes against the storyline that we're hearing all the time, and let's get into that, explain why that is and why we were able to do that. So I've got a lot of stuff in here, I'm gonna try and go through it as reasonably pace as possible, but I may have to do a Justin Searle's impression and fly really quickly, but we're gonna start by talking about the obligatory concurrency versus parallelism talk, right? How many people, raise your hands, have ever been subjected to a concurrency versus parallelism talk or blog post? Okay, so you see what's coming. So too long, didn't read, concurrency is not parallelism. All right, so over the next few slides, I'm gonna channel a gentleman named Rob Pike. Rob Pike is, among other things, one of the creators of the Go programming language. Go, as many of you might know, has built in some very, very fast and efficient concurrency mechanisms, and so he's actually been going around over the past few years and giving a lot of talks in this kind of concurrency versus parallelism. They're actually very, very good. He doesn't get into a lot of Go in them, he talks about these things conceptually, and so I'm gonna reference him a lot over the next few slides because he's done a really good job of that. So quoting him here, and in fact his, this presentation is called concurrency is not parallelism, concurrency, programming as the composition of independently executing of processes. Think about that code we saw. We fired off each of those futures as an independently executing process and then we composed them into an application that did something useful. Now parallelism is the simultaneous execution of possible related components, computations. Simultaneous execution, concurrency is not necessarily about simultaneous execution, parallelism is. So concurrency is about dealing with lots of things at once. We did that, we set up 10, 20 futures, that is lots of things at once, and parallelism is about doing lots of things at once, or to put it in my terms. So parallelism requires two processor cores. It requires it. If I only have one processor core, I cannot do parallelism, right? Because a processor can only handle one instruction at a time. So if I have only one processor, I do not have parallelism, maybe I have concurrency, but no parallelism, okay? However concurrency can be done on one or more processor cores, right? So concurrency is really about design. Concurrency is this idea that I'm going to design my program around these independently executing things, these things that don't have to be sterilized. If I get improved performance, that's a side effect. It's a good side effect, it's a desirable side effect, but it is in fact that side effect really concurrences about that design, okay? So here's the thing, non-concurrent program is gaining no benefit from running multiple processors. No benefit at all. If I do not write my code concurrently, I can run in many processes that I want, it's not gonna get faster. But if I write my program's concurrency concurrently, then when I do have parallelism available, right? I will get a benefit, okay? And this is sort of the point about concurrency versus parallelism, right? If I program my things concurrently, at worst I get no benefit. If I program concurrently at best, I get a huge benefit. If I don't write them concurrently, it doesn't matter how many processors you write. All right, so let's talk about the gil. So that's sort of some background, let's talk about the gil. So the l in gil stands for lock, right? So lock in computer science terms is I have a resource and I wanna protect it from multiple threads accessing it at once. So I create a lock, right? It's a very, very common thing. So basically what happens is a thread wants to gain access to a resource and that resource is locked. So the thread asks, can I get the lock? And if the lock is available, it gets that the lock is now acquired and has access to the resource. If it's not available, then what happens is that thread will normally block and wait for it to come available. Now, yes, you can make non-blocking calls and say, please, can I get the lock? And it may say no and you can move on. But generally speaking, you block waiting for the lock to come available, okay? So let's talk about what threads are. So a thread, according to Wikipedia, a thread of execution is the smallest sequence of programming instructions to be managed independently by a scheduler. All right, what does that mean? It means I can have multiple sequence instructions that are running potentially simultaneously, potentially not, and I have a scheduler that manages those. Normally that's gonna happen inside the operating system. All modern operating system provides threads and they have a scheduler and the operating system manages those threads across all of your processes. Every application has at least one thread. It may spawn more and the schedule in the operating system will manage that. So now the number of threads you have running at any given time may vastly exceed the number of processes you have. So for example, earlier I pulled up this on my particular MacBook Pro. If you look down the right-hand corner, did anybody ever look at the thread count running on their system at any given time? There are over 1,000 threads active on this system when I actually took this screen cap. Dropbox in the background is running 60 threads. What the hell does Dropbox need 50 threads for? I don't know, it's a cool application to let it have it, but there's over 1,000 threads running right now. Now I'm pretty sure you already know that my MacBook Pro does not have 1,000 processes, right? Clearly we have more processes than there are course. So, more about threads. Many programming languages like Ruby and Java, they actually map language constructs to these operating system threads. Okay, Ruby does that. If I tell you to do thread.new and Ruby, it creates an operating system thread. Java, same thing, C sharp, same thing. Now some languages actually don't do that. But look, languages like Erlang and Go, they actually create their own internal concurrency mechanism and they manage the multiplexing across operating system threads internally, right? In fact, if you read about a Go routine in the official Go docs, it says the runtime multiplexes these things across multiple operating system threads, okay? But you always have to have a scheduler. The scheduler's gonna be there, right? So, regardless of the language however, we still have threads in the OS. So even Erlang and Go and languages like that still have to manage the operating system threads, right? So they're gonna be there. So, within the operating system itself, what's gonna happen is the OS and the scheduler is going to schedule different threads within the operating system across the different processors that are available, okay? Remember, each core can only one thread at a time, okay? So whenever the scheduler takes a thread away from the processor and gives it another one, we have a context switch, right? Basically, if we take the execution context off of the processor, we put another execution context on and that happens. And when you have a thousand threads running at your system at one time, you have context switches happening all the time. Probably more than a thousand per second, right? Must be several thousand per second, okay? So, no programming language could ever really preempt the context which is from the operating system, okay? Operating system does what the operating system wants to do. We can give it hints. We can ask it to treat us nicely and give us favors. But ultimately, the operating system decides when it's going to context switch our stuff out. We have no control over that at all, okay? So let's get back to the gill, right? So because we are going to have context switches within the operating system and because that means our code is going to get pulled off the processor, every language must within its runtime, whether it be an interpreted runtime like Ruby or is like a compiled runtime like in language like Go, everything must have within the language itself the ability to protect itself from those context switches and make sure that when those context switches occur, that the runtime itself maintains a consistent internal state, okay? Now, some languages try and aim for having one thread per processor and they do the switching internally themselves, others don't, but ultimately this is going to happen either in your runtime or in the operating system or both, right? We're gonna have this happen. So, Ruby uses the gill to protect its internal state across those OS context switches, right? This is important. Ruby uses the gill to protect its internal state, right? So let's go into very, very simplified in the description what the gill does. After I pitched this talk and gave it this title, somebody like a couple weeks ago pointed out some really, really great blog posts by Jesse Stormier called Nobody Understands the Gill and those are fantastic blog posts and I highly recommend you read those because he does some really great stuff and read the comments, there's really great discussion. So I'm gonna do a simplified version of what he talks about because he goes into much more deeply and I'm gonna cover a bunch of different topics. So, basically what happens is thread A is doing some work, right? So Ruby locks the gill when thread A is doing that work so that way when it comes, because it needs to protect its internal state. At some point the operating system pulls thread A away, throws in thread B, which is another Ruby thread. Well, at that point thread B says, oh wait, I can't access the lock, right, because it's been locked. So it says to the OS, hey, thank you very much, but I'm sort of not able to do something so why don't you give this, you know, let somebody else work. And then eventually thread A gets switched back in and thread A does its thing and it releases the lock and eventually thread B gets switched in and now the gill has not been locked and thread A can run, three, or it can be thread B can run, right? So the idea is I can, right, so those context switches happen all the time. Whenever a thread needs to do something, it locks the gill and unlocks it when it's done and so we still have those context switches but a lot of those context switches end up in these sort of no-op operations where I just can't get the gill. So what does that mean? Now it's highly simplified but it has some implications. So what does that really mean? Simplications of this, only one unit of Ruby code can run at any given time. You heard this before, right? And I'm gonna say unit here and I'm gonna put in quotes because we get into, you know, where the context switch boundaries can be and, you know, methods versus various other things. It gets really complicated but we're just gonna say a unit of Ruby code. One unit of pure Ruby code can exit at any given time, okay, we may have multiple threads running, we may have these context switches but one has the lock, the other ones don't and if a context switch occurs, what's gonna happen is it's gonna end up with sort of this no-op operation where one says, oh, I can't do anything if I can't get the lock, okay? Which in effect means we don't get parallelism, right? Even because only one thread can have a lock, okay? But because we've done this, Ruby can guarantee that its internal state always is consistent and it is not corrupted and it's not broken and that protects our programs because Ruby as a runtime needs to do that, right? But what Ruby does not do and what the GIL does not do is provide us guarantees about our code. So here's your obligatory word definition. What is a guarantee? Guarantee is a formal promise or assurance to begin writing that certain conditions will be fulfilled. It's a promise. It's usually written as these things will happen, right? And the verb of that is to actually provide that promise. So guarantees. What is guaranteed and what is not guaranteed? So Ruby's what we call a shared memory language, right? What that means is, I'm just doing a little bit of Ruby 101 here, every variable is a reference. It is basically a pointer to an area of memory where an object occurs. If I have a variable called a and I sign to it a string, that string is somewhere in memory and a is a reference points to that. If I say variable b equals variable a, I'm saying variable b points to the same area of memory. There are two variables, but they both point to that shared memory, okay? So that means that two variables may reference the same point of memory and two threads that have access to variables that store that reference can access the same shared memory simultaneously, right? So I'm gonna show some code examples to basically that are contrived that will sort of demonstrate this for people who may not know how this works because this is gonna become very important later on, but I just wanna give some examples. So basically here I've got, I'm gonna spawn a thread, two threads. I'm gonna create this string called jerry, it's a mutable string, right? SGR points to it, that is a string in memory somewhere. And I'm gonna spawn these two threads and when these two threads work, they're gonna randomly see for periods of time, they're both going to mutate that string in place, right? I'm gonna call upcase bang in one and downcase bang and that's gonna mutate those strings in place, right? And then at the end, I'm gonna use a join so that I wait for those two threads to finish and we're gonna see what happens. So I ran this a couple of times and luckily I was able to get very, very different results both times, right? The first time, right, because it's a shared memory system, so the first time it ended up coming out at, first out the order of operations is different, right? And in both cases, I got something different at the end. So here's the two questions we wanna explore. Was this code thread safe? And was this code logically correct? Think about that for a second. Is this code thread safe and is it logically correct? I'll give you a hint, it's not logically correct, right? So let's talk about correctness versus safeness. In a fully parallel shared memory system, it is possible for two threads to access the same memory simultaneously, that is an unsafe operation. We can't have that happen, that causes corruption, okay? In a concurrent shared memory system, meaning one where we only have one processor, it's still possible for a context switch to occur while a thread is a process performing a complex memory altering operations, right? Not necessarily a single write, but in this case where I did a read and then I did a write later, let us talk about sort of a complex series of things, there's no way I can prevent any sort of context switching happening in there, right? So the ordering of these operations and their timing becomes very, very important, okay? Another contrived example. Basically the same thing, but in this case what I'm doing is I'm actually duplicating the string, right? So in the first case I'm calling dup, so I now have a copy of the string, I'm changing the copy of the string, right? And then I'm writing back to the original string the data that I've changed. So in the first case I'm replacing the lowercase r's with uppercase r's and then I'm using a gsubbang to write it back in. In the second case I'm changing the uppercase j or lowercase j and I'm running it back in, right? So again, is this thread safe? Is it logically correct? Well we run this and what we see is we get some very, very interesting results, right? This code was not logically correct, right? This is what we call a read update error, right? What it means is I read something and then perform some operations based upon that but while I was doing that the thing that I read was updated which means that my data is still in my computations or calculating a poor result. So then I try and write it back, I've now written back an incorrect result because it didn't take into account the update. This is a very common concurrency problem. It's called read update error. It is a not logically correct but it's a thread safe. Okay, keep in mind those are not the same things. So the answer is yes in this particular case it was thread safe but only by accident, okay? So Ruby's an interpreted language. Did anybody sit through Aaron Patterson's presentation yesterday where he talked about everything that was going on inside and, right, okay, good. He's gonna reference that because he did a really fantastic job of bringing home the point that our Ruby code which looks like stuff that's actually happening really is not actually happening. What happens is Ruby is a compiled interpreted language. So internally Ruby takes our code and it turns that into byte code within the interpreter and Ruby is free to optimize and reorder that code. Okay, Aaron talked about some of the optimization that will happen. So the byte code instructions are not necessarily directly mapped to our code. They produce the intended result but they may be reordered or they may be optimized. Now keep in mind Ruby itself is a program that is written in C. It runs on our system. It is compiled by a C compiler. That compi... So everything that happens in Ruby is written in C code. That C code then gets compiled as well and those compilers are free to optimize and reorder those things when they create the machine code that runs, right? I'm running in math, you may be running Linux, there's Windows, the actual binary that gets created from those is gonna be different in those cases so there's no guarantee that the actual order machine code operations end up being the same, right? And all this stuff happens under our stuff. So we see a single line of code that says variable str equals jerry. It's like, well that's just a simple like atomic operation that's creating this thing, right? It's really not because there's all this stuff going on and there's all potential reordering. There's a lot of potential optimization and we can't look at that and say that is an atomic operation because there could be all kinds of context which is within our code that's happening, right? So what's going on is the GIL exists to protect Ruby's internal states and all that stuff is going on throughout that entire stack underneath our code. Ruby maintains its internal consistency. Yeah, that's very important for us, okay? So what that means is Ruby itself is thread safe but there are no guarantees that our code is thread safe. The GIL does not exist to make our code thread safe that exists to make Ruby thread safe, okay? So what happens is the GIL prevents any kind of interlinked access to memory that's used by the runtime, right? And the internal consistency. So Ruby itself will never become corrupt but Ruby does not provide any guarantees for our code being thread safe. How many people have heard of memory model before? Memory model? Okay, good, so hope that people, good. So memory model, what is a memory model? So a memory model describes the interactions of threads with the shared use of the data, okay? A memory model is a written description that says when certain things happen to data variables, things in memory, the runtime is gonna make certain promises and guarantees about how that works, okay? Defines things like visibility, volatility, and emissivity, synchronization barriers, all this all deep gory stuff. Now I'm not gonna go into these details. Peter Chalupa who is visiting us all the way from the Czech Republic who is a major contributor to Concurrent Ruby, he's speaking this afternoon and he's gonna talk about memory models and a lot of other things related to that and getting the internal details of the synchronization. So if this part of this really is interesting to you, I highly recommend going to his presentation. It's gonna be fascinating, but the point of this is some languages have a documented memory model. Java, its initial memory model was not considered sufficient for concurrent programs, had to create a new one. Java's memory model was not put in place to occur one until 2004. 2004, right, we had multi-processor systems in 2004. It took Java till then to come up with a memory model that's considered sufficient for concurrency and parallelism. C and C++ did not get a formal memory model until 2011. Think about that. Those languages have been around for a long time and they did not get a formal memory model until 2011. So this next slide is not meant to be a slam or anything negative. It's just a statement of fact that Ruby currently does not have a documented memory model, okay? Because when Ruby was created, concurrency was not a top concern and so a memory model was not documented. To this day, we do not have a documented memory model, okay? However, because the GIL does what it does, it presents an implied memory model, okay? Because when we look at what the GIL does, we can make some predictions about how that's going to work with respect to all of these things in memory, which is why that code we were looking at before was accidentally thread safe. But remember, this is not a documented memory model. There are no guarantees and at any given push to master, that behavior could change, right? Moreover, the Ruby specification does not cover this. That's why JRuby and Rubinius can pass all of the Ruby spec tests and not have a GIL because that doesn't cover a memory model. Okay, so we're with me so far. So let's get back to this because this was where we started at and this is where I said, gee, look at that, apparently there's something wrong with that statement that Ruby can't do concurrency because we got a 10x performance increase by writing that code concurrently. So how did that happen, right? So there's this thing where sometimes your program can do a lot of stuff without actually doing anything at all, right? We have any node programmers in here? Anybody know, familiar with node? Okay, I knew that hand was gonna go up. Let's talk about IO input output. All right, so modern computer support both blocking and asynchronous IO. Blocking IO means I'm gonna make an IO call some sort of read write app to the network out to the file, whatever, and it's gonna block my current thread. We also have this idea of asynchronous IO which is I'm gonna do that but it's not gonna block the current thread. It's gonna go off and it's doing it asynchronously and the current thread can keep doing stuff and it can come back to it, right? Node has made this idea of asynchronous IO a very, very popular thing. This is what node does, right? So here's an important thing. This is where this all, this is what it comes down to and this is how we're gonna explain that first example is IO and Ruby programs is blocking IO within Ruby is asynchronous, okay? So again, IO and Ruby programs is blocking. If I make a IO call from a Ruby thread, it will block my thread. However, when Ruby internally makes the IO call, Ruby unlocks the gil, okay? Ruby unlocks the gil as it does also for backticks and system calls, right? What that means is that if I have a Ruby thread that is waiting on IO, it does not block other Ruby threads from doing important stuff, okay? This is what this all comes down to. So, and this is what happened in that original example. In Node.js, we have one main thread and all of the IO is asynchronous and so I basically can just fire up all of these callbacks and they get deep into callback hell and that works because the runtime itself is designed around this event loop. Ruby won't have an event loop, we have a main thread. So when we do IO on the main thread, it's going to block. But if we spawn multiple threads into our IO, those threads will not lock the gil and they can't all be waiting simultaneously for that IO and we can actually have threads doing something useful. So remember, the gil exists to maintain internal consistency of the Ruby runtime, right, let's get back to it. IO operations are slow, right? This is why we have asynchronous IO, we don't want to have to block everything from happening. When a Ruby thread is blocking, waiting for IO, it doesn't have to change the internal state of Ruby, right? It's not doing anything, it's asleep. Ruby knows it's doing nothing, the operating system knows it's doing nothing, it's just sitting there, so it can't make any changes to Ruby internally, it's blocked. So there's really no value in us at that point locking the gil and not letting another thread do something useful while we're blocked, okay? So what this means is that Ruby programs like the one I started this presentation with that do a significant amount of IO generally benefit from concurrency and I showed you an example where we got a 10 times performance increase from that, right, so what kind of program, what do we mean by IO, right? Well, I mean, look, we're talking about whenever we read or write files, such as log files, we're talking about when we interact with databases, we're talking about when we listen for inbound network connections, like I don't know, let's say if we happen to like want to build a web application framework out of Ruby, right? Or we want to connect to external HTV APIs because like I don't know maybe we want to to do some API stuff within our web application or maybe we just want to send emails to like, I don't know when people sign up for applications, right? Raise your hand if these are the kinds of things you do in your code all the time, I should see every hand in this room go up, right? So what we're seeing here is that the Ruby, the way Ruby works internally with respect to IO actually works very well for the kinds of things we do all the time, right? The kinds of things we do. So if you're doing all of these things which everybody is always doing, then your program may benefit from concurrency like we saw in the first example where we got a 10X performance increase by doing that concurrently. So here's my question. Where's the love? Where's the love, right? Why is it, why all the hate, right? Why is it I started out this presentation with all those tweets beating up on Ruby for sucking at concurrency when we just saw that actually Ruby can do fairly well in concurrency for the stuff that we do, right? So how many people here were yesterday at the game show that happened right after lunch, right, that happened, right? So, okay, perfect. So people who were there were very, very smart, very talented, very accomplished people. I have a lot of respect for them. All four people on the panel, I have a lot of respect for all of them. And they did something yesterday that I thought was very fantastic. One of the words that came up was concurrency. Do you remember that, right? Concurrency came up. Two of the three people who responded to that question started out by saying the same thing. I see some people smiling and you know what their answer was. What was their answer? I don't know much about concurrency, okay? It took a lot of courage and a lot of humility for those people who were sitting up there, people who were very, very accomplished in our community who were streaming live all over the world to say, you know what, I just don't know much about this topic, right? I love that. I thought that was great. Because really being humble and saying, I don't know something is way better than running off to Twitter, running off the mouth about things you don't know anything about, which clearly is what Twitter was invented for. That. So, part of the reason why we don't have, for the lack of love for removing concurrency, we just don't have a lack of knowledge, right? But it's not a problem. It's not a bad thing, right? Because the thing about it, most of us don't write code that requires concurrency, right? We don't. So, you know, if we're using frameworks like Rails, it handles the concurrency for us. Like Rails is thread safe. It can run on single threaded web servers. It can run on multi threaded web servers, right? We can run, you know, if you're a 12 factor kind of person, you can run that in multiple processes and it handles that stuff for us, right? Also, a lot of the domains where concurrency is necessary, where parallelism, high performance parallelism is necessary, are domains that also need highly performant languages, right? No, look, we all love Ruby, but we love Ruby because it makes us productive, not because it's the fastest language, right? There's a lot of stuff going on, and all the stuff that makes us productive makes Ruby not so fast, and that's okay. There's nothing wrong with that. It does good by us. But the people who really need that high performance parallelism are probably writing in languages that are much faster anyway, because that's what they need. So they're not coming into Ruby for those things, right? And so basically learning about concurrency is something that most of us, as Ruby, just don't have to do, right? So we just don't have to do it, so we don't know about it. And that's part of the reason why there is just so little love for Ruby with respect to concurrency, because we just don't know. We don't have to know, right? But that's not it. There's a couple of other reasons, two of the reasons. Another one is that, when it comes to concurrency, Ruby's not perfect, okay? So I made a case for how Ruby is actually not as bad at concurrency as we might have otherwise thought. That's not the same thing as being perfect, right? Ruby, perfectly honest, right? Ruby is great at concurrent IO, as we saw. Yeah, but not so much for processor and tentative operations, right? Remember, the GIL prevents full parallelism, okay? So some programs, if written concurrently, will simply not gain a benefit. This is very similar to the first example we saw, but instead of going out and hitting that web API and doing all of that IO, what I'm doing here is I'm basically written this very slow and very naive summation operation, right? So I'm gonna say we're gonna do, we're gonna do, I've said accounts, which is gonna be the number of times we're gonna do this. I'm creating an array of one million randomly generated numbers, okay? What I'm gonna do is then the sum operation is literally just going to walk over the entire thing and it is going to add up all those numbers. It's a very naive brute force summation operation, right? It's gonna work, it's not very efficient, but for the purposes of this, it'll demonstrate what I need to show. So the first time I'm gonna do it concurrently, I'm just gonna do it 10 times one at a time. The other one, I'm gonna do the same thing I did before, I'm gonna fire off 10 futures, I'm gonna have each one of those happen on a future and it's gonna happen asynchronously. So what do we expect to see when we run this in MRI? Well, this time we're gonna see that both operations take about the same amount of time. The GIL prevents full parallelism because only one unit of Ruby code can run at a time and so in this case, we have no benefit, but it didn't hurt us either. Didn't go any slower, did not hurt us either, right? Remember concurrency is about design and about breaking down my application into those independent parts, okay? Now let's say I run this on JRuby, what happens here, I run it on JRuby and while it took about half the time to run on JRuby by running concurrently, then it did serially. And if I run it on Rubinius, guess what? It takes about half the time to run on Rubinius concurrently then it's serially. What does this imply to you, right? I have two cores in my machine and those two run times got full parallelism which meant it took about half the time. But on MRI, which has the GIL, we didn't get that advantage, okay? So again, I'm not gonna be honest, Ruby is very, very good at concurrency when we're doing a lot of IO, not so much when it's processor intensive, okay? So the third reason, the last reason why I think Ruby gets a lot of hate from about concurrency is sort of really the lack of tools, right? So if you look in the Ruby standard library, we've got some basic concurrency tools, thread, fiber, mutex condition variable, right? It's not a lot, if you look at Java, well we've got Java util concurrent, it's got those things, but it's also got futures and executors and it's got exchangers and it's got all, you know, and schedule tasks and timer tasks and all of these really, really cool things I can do with my applications, right? Go, we've got GoRoutine's channels, tickers, timers, mutex, atomic variables, right? Closure, futures, promises, delays, refs, atoms, agents, core async library, right? Erlang, we have GenService, GenEvents, GenEvents, SEMs, we have spawn processes, we have messages and all that stuff. And in Scala, we've got executives, futures, promises, we've got ACA library for actors, all of these languages provide tools that allow us to build the concurrent systems very easily, Ruby doesn't have that, right? But think about Ruby as a language. Why do we love Ruby? It makes us productive. If we look in the standard library, there's so many great things that we can do in the standard library with very little code. How many people saw Asia Hammer release keynote yesterday, right? Where she used tuple space and in a couple lines of code built this hugely powerful distributed system across Google's cloud platform by taking advantage of stuff that's in the standard library. So Ruby's got this really awesome standard library for everything about concurrency, right? So really what it comes down to is, Ruby concurrency needs two things. It needs better tools and a better publicist, right? And like I said before, this presentation is not intended to be a sales pitch for concurrent Ruby, but there was actually a sort of subversive reason why I used future in those, right? Because I want to make this point about having better tools. Notice what I did there. I used that future construct and all of those programs ran it against all three Ruby runtimes and I compared the benchmark results. Isn't that the way it's supposed to work? Should I not be able to build concurrent systems without having to know all of the scoring, inner working of the GIL, knowing when I can do this and when I can't, knowing how it run. It's like, look, when I've given presentations before I've asked this trick question, I've said, how many people can tell me in a Ruby array how many elements get pre-allocated when I create an array? Well, the answer is two parts. The first part is it depends on the runtime. The second part is I don't care. I don't care how many elements. What I expect is I want a really awesome thing called array that does all the cool stuff that Ruby does. I want it to run, I want it to have the exact same behavior on all runtimes and I want to know that each core team is optimizing the bejesus out of that thing for that runtime. But when we deal with concurrency in Ruby, we don't really have that because we have to have these conversations about the GIL and what it does. But if you have good tools like the one I showed you before, and there's others, right? I'm kind of partial to concurrent Ruby, but there are plenty of other gems and libraries out there that provide these tools, then I can stop worrying about this stuff and do what Ruby is good at, which is allow me to build really cool applications. And that's the thing. So we need better tools and a better publicist. Myself and others are working and I know Matt is working trying to build better tools, but now that you know, now that you've seen this, this is the call to action. I would like each and every one of you to help become Ruby's better publicist. Please get this message out that Ruby is not as bad at this as many people think and in fact, Ruby is pretty good at the things that we do all the time. Let's get that message out. So, I'm just about out of time, so let me summarize real quickly. Okay, conclusion, concurrency is not parallelism. Concurrency is not parallelism. Concurrency is not parallelism. If I had a freaking nickel for every time somebody I got Twitter got this wrong. All right, but that's the same thing. Secondly, the GIL is intended to protect Ruby's internal state when the operating system context switches, right? It does not provide thread safe guarantees to our code. However, it does provide an implicit memory model. Do not write your concurrent code with the assumption that that memory model is valid because it could change at any moment, right? The GIL does actually prevent true parallelism in MRI Ruby because, you know, the GIL. But Ruby is actually pretty good at multiplexing our threads when we're doing a lot of concurrent IO, right? And since that's what we do a lot and that's what really is our bread and butter in Ruby, that actually is a pretty good thing. It's not really bad. It's not perfect, right? But at the same time, it helps us do the things that we do better and we should really understand that better and respect that better and take advantage of that more. So, and finally, my parting message is basically this, keep calm and don't sweat the GIL. All right, so with that, again, I just wanna remember, so again, I work for test double and I created this thing called concurrent Ruby, so I do have stickers, right? For both, which I'm happy to give away. Also, you can find me on the Twitter at JerryDantonio, GitHub, JD Antonio, of course, concurrentruby.com takes you to that gem. I do work at test double, as Justin said the other day, you know, we are available for hire and we love working with cool people building cool stuff. JavaScript, Ruby, Erlang, Clojure, Go, testing, concurrency, whatever. If you like what I've talked about, if you like what Justin talked about and you wanna talk to us more, we'd love to talk to you, reach out to me, reach out to Justin, reach out to us at test double over Twitter and we would love to do work with you. That being said, again, thank you very much for being here. My name is Jerry. Take care. Thank you.