 Rhyw gwaith, fel chi'n gweithio ar gyfer y ddechrau Newyddion yma, ac rydw i ymddangos i'r Cadw Cymru. Rwy'n gweithio ar gyfer Y Orochol Lwyddiad. Dw i'n gofio'n ddifodol o ddodol i fynd i'w gwirio. Ddodol o'r ddodol i fynd i'w gyfrifiad. Ardod o'r ddodol i'r gyfrifiad gyda'r gyfrifiad. Y Orochol wedyn yw'r gwirio ar gyfer y dyfodol. Mae yna rhai o'r gwirio ar gyfer dyfodol. ei wneud tynnau ei cael ei fod yn y buddur o'u cyfarfod sydd gyda'r cyfrant o'r wneud yma'r cyfranau endohedd. Fel y cyfranau rwy'n bade i'w gwneud yr hynny yma, ac nid yw rhaid i'n ymweld yn rhoi rhoi rhaid o'r cyfranau sy'n aeddwn yn gwneud y cyfransau cyfranau a'r cyfransau achos ei gwybod. Ac mae'r cyfransau cyfransau allan yn ei cofio'r rhno o botl yn bwysig o'r dwyloedd cyhoedd i'w awdraff. ac mae'n meddwl i'r cyfnodd ydych yn ei ddweud ar gyfer cael cyfnodd. Dyna gweithio o'r newydd ein system hefyd yw'r Jei Rybyd yn ymwneud. Efallai'r rhubi yn ymgwneud o'r newid o'r Llyfrfridau Llyfridol, bym o'r labn o'r projeg yng Nghymru, gyda gyda genedlaeth genedlaeth JVM, ac yn ymddi'r ymddiadau. Mae'r gwerth o'r JVM yn ymddiadau, yn ymddiadau i'r prydysgol, a'r community JVM yn ymddiadau i'r prydysgol, ac yn ymddiadau i'r prydysgol yn JVM. Yn ymddiadau genedlaeth JVM, ymddiadau i'r newydd gympailer, yn ymddiadau i'r JVM, yn ymddiadau i'r grwp, ac yn ymddiadau i'rolisgol gympailer gympailer yng Nghymru, ac mae'n targedo'r gympailer yng nghymru. Mae'n ddych chi'n ei wneud fakeidol gyda'r rangos o'r rŵmdenau yn hipon mwywg, yn enwedig i rhai ardal gyfadau, a'u dod o'r rhaid i'r rhaid i'r ryth deis o'r rhaid. Ein ei ddweud o gwir o gympailer gympailers fath o gympailer oedd mewn pethau, o ddweud o herfodol i endurance. Ein ei ddweud o gweith o gympailer oedd yn digwydd, Iyn o enw i Lleiddau newydd a Lleiddau Paiddau, ar un o Lleiddau Paiddau sy'n gweld nhw'n ardal yn gynnig i'r un sgol bai gyda Lleiddau Paiddau. Bydd yna ein pethau lleol oedd bai gennu un sgol, Tom Salt, beth sawl yn cynnig i'r rhaid o Lleiddau Paiddau ychydigau y unigengor ym Mhwysg Bwrthynol. Ond yr unig yw gweithio gyda'r POSI Ivaluatio, o'r JVM oeddech chi oedd yn gweithio mewn teimlo? Ar y cyhoedd ychydig yn gweithio ym Mhwysg Bwrthynol rywbeth rhywbeth arwyr ar y galaxydd, ac oedd ychydig yÙr yn gwneud yn gweithio'n gwybod. Ychydig yn gweithio ym Mhwysg Ymhwyr. Ych chi'n gallu gyhoedd ar gyfer gweithies rhywbeth ym Mhwysg Bwrthynol oherwydd i gweithio'r language. They're good features of it because it makes it easier to program. Asked what we care about more, new people' are improve yr hwb i ngillaeth amio. Why they're community features. There's probably things to seeinaly we talked about many times before. When we started implementing truffle, we'll go the blog post by Charles Clutter on why you need to implement it rwbi and why you can start making performance to quite quickly. Listen to a lot of the things here. They also listen to a lot evolved people's blog posts on what is part and about Sergey Moise luncheon. So I fixed it on a big problem, big lump, promotion. We have it at an integer roedd yn rhoi fael, mae'r bwysig ei ddadwyd fydd yn gweithio. Mae'n ddweud ar ôl e, iawn mae'r bwysig yw'r mountainorfeidliad. Mae'r bwysig yn cael ei ddweud gofynion ar y brif. Mae'r bwysig mae'r bwysig yn cael ei gwylau cael eu chyflogu a'i gwneud. Mae'r gofynion yng nghymru wedi'i rhesweddol â'i gofynion awddentol pof, oherwydd y gallwn gallu rai amgarjor, oedd y gallwn gallwn meffer i ddweud y methaf oherwydd That means you always need to continue to check that the method hasn't been redefined. So if you matter if any of the bit of work you're doing, you also have to do some check to check that something hasn't been redefined or you haven't overflowed. And quickly, the checks become more work than you're actually doing in terms of real work on the processor. Binding, so, kernel binding and proc binding. These are frequent listed as a couple of the things that are the most hard to implement in Ruby. Personal binding allow you to get an object's receptor so that all of your local variables are wrapped up so you can manipulate it like a Ruby object, and the proc binding allows you to do that for a proc, or a block or a lamb, or something like that, which you've already got. They make it very tricky to optimize Ruby because they mean that you can't… An implementation of Ruby can't store its local variables on the stack like Seawood, because you're always able to get them and modify them as a Ruby object at any time. Object space is listed as one of things which is tricky to optimize Ruby for, o'i bod ni'n fwy o bobl yn ychydig i'r byw, ond ni'n gwneud o bobl sy'n bwynt o bobl o'r byd, ond o'ch dda'u gynhyrchu i'w byddai gynhyrchu i'w byddai gynhyrchu i'w byddai gyrdd. Yn jay Ruby, y gwaith o bobl o bobl o'r byd i ddechrau'r byddau dda, ond mae'r cost yn ymgwneud. Yn dechreu panethau, mae'n dechrau'r byddau ddechrau o bobl o'r byddau ddechrau o'u byd i ddechrau'r byd i ddechrau'r byddai gyrdd yma, to tastes. Again these checks become more work than you're actually doing and threat rays allows you to send a exception for one thread to another. Again you need to keep doing checks therefore have I got something I should be raising in this thread. So those checks are the problem that we need to get rid of and deoptimization elegantly solves all these problems Where we the optimization elegantly solves all these problems. When we talk about jet compilers, we talk about optimizing code. We go from a slow interpreter and we want to jump into fast jitted code. That's how J微by works. This is how Rabinias works. It's how TOPAS works, all these implementations. They go from a slow interpreter, ids fast jitted code. The optimization is going the other way. It's going from your fast jitted code into your slow interpreted code. My idea is, if we can de-optimize at any point, we can make the compiled code forget about the checks. We can make the compiled code not allocate objects, as long as we can rely on the interpreter to have the objects there for real. It's going the other way to JITs which is why we talk about it as being the antidote to JIT compilation. Before we talk about stuff at a more technical level, let's provide a bit of an illustration. I'll use Alice in Wonderland as an analogy here. So, in Alice in Wonderland, Alice goes down a rabbit hole. She's chasing after a white rabbit. The white rabbit disappears into a room when she enters the room or she can see these tiny little doors. And she wants to follow the rabbit to see where it's going. And she bends down and she can open these doors up and she can see a beautiful garden on the other side. However, she's far too large to fit through. Alice's tall person, tiny little rabbit, and the door is designed for the rabbit. But thankfully, on a table, she finds a bottle of medicine which is labelled Drink Me. And she very courageously drinks this bottle without knowing what it will do. And she shuts up like a telescope and she suddenly lies in small. Now she can go through the door into the beautiful garden. But there's a problem. She realises the door was locked and she's left the key for the door on the table. So now she's not able to get through. And because she's shrunk herself, she can't get hold of the key anymore to get through the door. Thankfully, she also finds a tiny little bit of cake and that's labelled Eat Me. And she eats the cake and she blows up again and now she's able to get the key. But her problems continue from there, but that's where we'll stop the analogy. So relating that to de-optimisation, Ruby is Alice. She'd like to go through the little door into the garden of Utopia of High Performance. But she's simply far too big. She does too much work. She's got all these checks. She always needs to have these objects available to really compare them and use them as real Ruby objects. The Just-In-Time Compiler is the bottle labelled Drink Me. Because if she can drink that, she can shrink down as small as possible and then she can hopefully get through the door. But when you do that in a conventional implementation of Ruby, you leave something behind. You leave behind the set trace funk. You leave behind object space. And in reality, most implementations can't get that small anyway because they've got no good way of shrinking beyond those checks. So de-optimisation is the solution to that and that it reverses the effects of jit. So de-optimisation is like the cake which allows you to restore back to your original size. And the idea is if you can drink the bottle of medicine at any point and you can eat the cake whenever you like, then we can make our compiled code much simpler because we always go back to our full implementation of Ruby whenever we need to. So what does de-optimisation do for Ruby? Let's talk about fix num to big num promotion. As we said here, the problem is checks. We want to get rid of as many checks as possible. We want to make our code smaller. We want to shrink the code. And the medicine of jit is about shrinking it. So let's consider an example to start with. A plus B plus C. We'll assume that we know these are fix nums to start with. Perhaps because we worked it out through some type specialisation or perhaps it's some future implementation in Ruby where they're annotated as fix nums. This is the code we need, a pseudo code, to add together these two numbers. First of all, we add together A and B as fix nums. But they may have overflowed. So we need to check, did that overflow? If it does, we need to redo the calculations as a big num. And then we continue with the next set of calculations, so plus C as a big num. If it didn't overflow, then we continue as a fix num, but we need to check for overflow on plus C as well. Now that's a huge amount of code. It's got branches in it. Processors don't like branches. A branch will often destroy any kind of pipeline you've got in a processor, even if you can predict them fairly well. And remember, if you have a language like go or a language like C, the code you'll get when you add together two numbers is pretty much one machine instruction, add. Here we've got literally, potentially hundreds of machine instructions. And this is why fix num plus big num is really slow. So the solution to this, using dynamic deoptimisation, is to say we'll add the numbers together, we'll check if they overflowed, and then if they will, we'll just forget it. Everything will stop. We're not going to bother trying to handle that case. And what we do instead is we deoptimize. So that's our compiled code. If we get an overflow, we'll jump back into the interpreter, which implements all this logic, but in our JIT code, we simply implement this logic. Do the A plus B as a fix num if it overflowed, deoptimize. We add C onto it if it overflowed, deoptimize. This means you then don't have any branches, or you've got one very simple branch on each case. You don't have a code in it, and you've shrunk your code. Literally, you've reduced the amount of code you have, and less code is pretty much always faster code. You've got a finite amount of space for instructions and a processor, and a valuable resource. This allows us to reduce that. So that's fantastic if your code doesn't overflow, if your numbers don't overflow. If they do, isn't that really expensive? Deoptimisation sounds like a really complicated thing, and it is, and it certainly isn't free. It takes quite a long time to deoptimize on the order of many, many nanoseconds. So what happens if you frequently overflow? If you've got some code path where the numbers always overflow. Well, after we've deoptimized back to the interpreter, the next time we can compile, we can compile a slightly different code. So if A plus B frequently overflows, or has overflowed ever, we can now recompile with that branch in that handles that case. The other one which didn't overflow will keep that as a deoptimize. So we can gradually make your code more and more open to using the different dynamic programming aspects of Ruby as it goes, but we only add them as we need them. We call this a type of specialisation, and over time your program specializes with the code it needs, and each time we can deoptimize, go back to the interpreter, then when we compile again, we can include a bit more information that we need, but we're getting bigger very gradually, rather than having it all at the first place. Monkey patching methods. So whenever we have something like myObject.myMethod.xy, we need to have a check. And the simplest implementations of Ruby, which have no cache units like that, don't need a check because they simply do the look up every time. So the simplest case is look up my method in myObject, and then call it with x, y. But any non-trivial implementation of Ruby these days will do, has the object changed, then look up the method and call it. If it hasn't, use a cached version of that method, and then call it with x, y. But what we need to do therefore is each time we need to check has the class been changed at all? Has anyone monkey patched the methods on it? And again, this then becomes a check. So the tiny bit of code we're really interested in doing the call has again become swamped by all this bookkeeping and stuff like that. So we can improve that by, like we did with the overflow checks, we can say if the class has changed, we'll stop everything and we'll de-optimize. We'll jump back into the interpreter and we'll handle it there. If it hasn't changed, we'll continue and we'll use the cached method. Now we can actually do one better here than the overflow checks. We can remove this check even further. What we can do is we can say use the cached method and call it with x, y. Without any checks whatsoever. So we've removed all the dynamic programming, meta programming, monkey patching features of Ruby here to simply have use the cached method and call it. So how do we support de-optimization here? One of the things we can do in de-optimization if we go back to the analogy with the cake, we can force someone to eat cake from afar. So if Alice has shrunk herself and then you realize that she's shrunk herself too far and you want to stop her, you can stop her what she's doing, you can force her to feed cake and she'll grow again. So what we do here is if somebody else, monkey patches a method, they force everyone else to eat cake and de-optimize. So if your code is still running, then the method you're using cannot have been redefined because if they had been redefined, someone would have forced you to eat cake and you would have gone back to that interpreter code which does those full checks. But your compiled code you're using in the fast path can be nice and fast like that. Binding. So the problem with binding we said was that it allows you to get access to your local variables at any point. So if you consider some simple rubal code such as these local variables where you have A is 14, B is an array of 8.2 and 3.4. The way almost all ruby implementations represent this will be that I'll have a stack. So this is the program stack with your local variables on it, A and B. A points to a fixed num. We may be able to do a little bit of optimizations with something like tagged integers to simplify that but in the conceptual case it's pointing at an object 14. B points to an array which points to other numbers as well. Now this is really inefficient because to allocate these objects we have to call malloc. If you're calling malloc it's thousands of times slower than actually just using registers into that. It's really, really slow. So what we'd like to do in a really performative implementation of ruby is to put those values directly on the stack. But the problem is if they're on the stack how can you get a binding of them? If we have anything allocated on a stack of this and we call dot binding or we've allocated a proc and it's gone somewhere else and been stored somewhere else and we call dot binding on that, how do we get access to these values? Well in de-optimization we can recreate the interpreter state so we can recreate all those objects. What we do is we take the call stack with the local variable values on it and we pull them off the stack and put them back into the real objects. Now that sounds like real magic be able to access your own stack take values off it which have been optimized away but remember we're writing the compiler here so the only person who puts anything on the stack is us. So we know where everything on the stack is so we can put it off if we want. This is part of de-optimization. In our compiled case we use the call stack and in our interpreter case we use the full objects. When we de-optimize as well as jumping into the interpreter we take all the values off the stack and put them really into their objects. This means we can support binding effortlessly because we can use local variables, sealant style local variables on the stack for all our Ruby local variables but at any point we can take them off the stack and use them again. And anyone who's using that local variables on the stack will be de-optimized as well and they'll start using the method the object which represents these local variables as a Ruby object. There's a few other techniques which we can talk about using de-optimization which we can relate to the other example. So something like object space the problem normally is that you can't store your Ruby local variables on the stack because you need to get access to them as objects like we do in binding we solve it in exactly the same way. We de-optimize everyone we tell everyone that whatever local variables you've stored on the stack I want you to put them into real objects. Tell everyone to do that and then you can enumerate all these objects. And then there's nothing stopping you using the stack for local variables because you can simply put everything back into objects later on. SetTraceFunk we can implement very much like monkey patching. So if we have a call to setTraceFunk on every single line we simply make that a method which does nothing and we inline it. And then instead of checking whether someone's installed a new setTraceFunk all the time we de-optimize them if we install a setTraceFunk. So to install a setTraceFunk you tell everyone to stop you make them all eat cake they all become large again and they all do the explicit checks for a setTraceFunk. So this means these features like object space and setTraceFunk don't have any overhead. So you've been told throughout your Ruby careers that monkey patching has an overhead. Checking for overflow has an overhead. SetTraceFunk has an overhead. Object space has an overhead. And they simply don't. Checking for overflow has one little tiny overhead which is the jump on overflow. But apart from that these features literally have no overheads whatsoever. In our implementation of JbPlus Truffle we have them enabled all the time and if you did switch them off it makes no difference to the machine code generated at all. So how do we support de-optimization? Well there's only three things we need to be able to do. We need to recreate the interpreter stack frame. We need to jump from the jitter code back into the interpreter and we need to allow us to force threads to do this. Now the first two are pretty simple. Recreating the interpreter stack frame as we said each thread knows what's on the stack because it put it there. The compiler knows what variables it put into which stack locations. So we can simply tell it go through your listing of what you put where and put it back onto the stack. Jumping from the jitter code interpreter again is pretty simple. It's not much more than a go to. Goes from this compiled code to the interpreter code. The stack's already there and you can keep executing. But I'll explain how we force other threads to do this in a bit more detail. But this is pretty interesting and it's pretty unique in modern VM implementations. So say you've got this code. Loop do, A is 14, B is 2, A plus B. Now that's tight in a loop so we'd like to compile that so it has no checks on the method add there. We'd like it to keep running without any of these checks in. What some of implementations of Ruby do at the moment is they check a flag on every time they loop should I de-optimize at this point. So if you wanted to redefine add or if you wanted to raise an exception in this thread they have to keep checking this flag again and again. And checking that flag takes this from something which is a tight operation which just uses local variables or probably just uses registers to something which accesses shared memory and that's got implications for things like concurrency for your caches, everything like that. Suddenly it's gone to a shared memory operation where it was really simple before. So we could have been more implemented like this. We could say de-optimize if someone set this flag. What we actually do in the JVM is we use something called a safe point. This is a fantastic trick. What we do on each loop is we read a location in memory. That's all we do, we simply read it. When we want it to de-optimize what we do is we change the permissions on that page of memory to cause a seg fault. So what we effectively do is we cause your thread to crash and then from that point we can reconstruct the state and keep going. So effectively when you use a safe point when you redefine a method, when you use threadplace, raise what we do is we cause your thread to crash by causing something very much like a null pointer exception. And that's really simple to do. It's simply one instruction, read. These safe points as we call them are already in your code because they're necessary for GCs. You may have heard about garbage collectors doing a stop the world collection but to stop all the threads you need to tell them to stop somehow. So we have these safe points which allow us to do that by changing these page permissions. So we've already got these safe points in. So this is how this can be zero overhead because we already have these safe points to support GC. We can reuse them to do method redefinitions, set trace func, object space, stuff like that. So all modern implementations of Ruby part of Marvia MRI use de-optimisation to some extent more or less. But JRublus Truffle uses de-optimisation more pervasively and more aggressively than any other implementation in Ruby. And actually more aggressively than a lot of other implementations of other languages such as the V8, such as spider monkey and systems like that. So we're presenting the implementation of Ruby here. How do you know I don't just have a toy implementation which doesn't really implement Ruby? And that's a really legitimate concern. I know in the past we've had some questions raised about other implementations of Ruby, how much of Ruby they actually implement when they start talking about performance. So I'm just going to take a couple of minutes to convince you that we have a real implementation of Ruby here. First of all, we support 86% of the Ruby spec language specs. That's provided by the Ruby spec project. We're very grateful for them providing that code. So the general language features, we do support the vast majority of them and we're closing the gap on getting to 100% pretty fast. We don't support the same level of specs on the core library, but that's something that's just a case of implementing more libraries. It's not a case of new language features. If there's a language feature which someone thinks will impact our performance and we haven't implemented yet, we'd love to know about it and we'll stop everything and we'll implement that language feature right away. So we've used things such as Charles Nutt's list of difficult things to implement before you start talking about performance. We've gone through them systematically. If anyone else has anything else that they think should implement before we start talking about performance, we will do. We implement all these difficult parts of Ruby. Method invalidation, dynamic programming such as send, method missing, stuff like that, binding, threads. We have initial support for C extensions, frame local variables, object space, regular expressions in codings, eval, concurrency debugging, closures, promotion, all those things. And actually, some of those we implement better than the other implementations of Ruby. So as I said, our object space is always on. Our set trace funk is always on. We have a debugger which is always on. You can always stop our programs, introspect them using a shell and then continue running at the same performance. Our support of threads is a little bit limited in that it only provides concurrency at the moment. We have a global interpreter lock, but we actually recently took on a PhD student to work full-time at looking at that, so we hope to have some progress on that soon. Our support for C extensions is also pretty minimal at the moment. We're starting to look at making that more featureful, but we run them much, much faster than any other implementation of Ruby. We actually interpret C code when you have a C extension. We interpret it using the same system we use to interpret Ruby. The big question you're going to ask is, no, we don't run Rails yet. And we're not likely to run Rails for another year or so, probably. So no one here is going to be able to use JV Plus Truffle to do anything at the moment. And we're not proposing that anyone should try and do it. It's a research project, that's all it is. The rest of the JV community are working on other implementation techniques in Ruby which are relevant to what we need today. This is a research project and we don't run Rails. However, we are working towards it. Do I think that we'll actually provide any performance increase to Rails? Well, let's think about what Rails does and why it makes it slow. It does lots of things like dynamic programming. It uses method missing, it uses send, respond to and stuff like that. And it also uses lots of little objects all the time. Well, actually JV Plus Truffle is awesome at that sort of problem. If you have lots of objects, we can allocate them on the stack instead of on the heap, because we can always go back to the heap if we need them. JV Plus Truffle can inline through method missing, through send, through respond to, because it can always de-optimize and go back to the full interpretive it needs to. So, actually, I think there's lots of promise for Rails. Rails isn't something that might trip us up. I think it's something that Rails will be surprisingly effective. So, let's talk about performance. As I said, the blog post backs this stuff up. You can recreate our experiments if you'd like to. Please do. So, first of all, you can pair it against some classic benchmarks. These are the benchmarks that language implementers use, computer scientists use. So, they're very well understood and the reason we use them is because everyone in our community understands them, but they're not representative of real Ruby programs, but we'll start with them. So, on something that's highly numerical and highly computation-intensive, such as Fanko Redux or Mandobrot, Mandobrot is a fractal generator, we can be up to 35 times faster than MRI. And JV and Rubinius are only about twice as fast on that sort of highly computationally intensive stuff. I also show on this graph Topaz, which is the Pi-Pi implementation of Ruby, which was started a couple of years ago now. It's gone quiet. I don't think there's any recent development on that, but we can compare against Topaz as well. Having Topaz is really good, actually, because it shows that our numbers aren't absolutely out of nowhere. Topaz is achieving something vaguely similar. On some benchmarks, we don't do any better than anyone else, really, such as binary trees and Pi digits. These are very much memory-bound benchmarks and we can't allocate memory faster than anyone else magically. So, we're limited on those. Those are synthetic benchmarks, as I said. They're not really representative of any Ruby code. So, let's talk about some real code. We've been using two Ruby gems over the last few months, chunky.png and psd.rb. Chunky.png is an implementation of the PNG file format in pure Ruby. psd.rb is an implementation of the Photoshop file format in pure Ruby. This is production code written to make money for business. It's a real Ruby code that people are using in production today. We're very grateful to the authors of these gems for writing this code in the first place. On these real-world gems, we took the kernels of them and, on average, we are 10 times faster than MRI and JRuby and Rabbinius are no faster than MRI on these. That's because these are highly computationally intensive work. This is about decoding images. It's about applying filters to images. If you call malloc in one of these benchmarks to allocate something like a temporary array, you've already lost. So, you need to have de-optimisation to be able to remove those allocations. You need to have de-optimisation to have highly efficient implementations of the basic operators like add and multiply. If you don't have those, you're never going to be any faster than MRI. Again, we're also a lot faster here than topaz. That's because our ability to de-optimise is well above what topaz can do at the moment. I'll pick out a few benchmarks and show where this 10x figure comes from. If we look at some example benchmarks, on some of them we can be up to two orders of magnitude faster. So, chunky operations compose takes one image and compose it on top of another. If there's no filter there, it's effectively doing memory copy. If you've got de-optimisation, you can get rid of all the temporary intermediate arrays and hashes and method calls and the monkey patching that goes into that. You can simply write pretty much the same thing you'd get if you wrote mem copy in C. Clamp is a really good example, but it clamps a value between two extremes, so sort of min, max. And they implement this in this library by creating an array with the values in, sorting it and taking the middle value. And with de-optimisation, we can actually allocate that temporary array on the stack and then we can trace the values simply through as registers all the way through to where they're used. So this is sort of what Rails does, isn't it? Allocates little, tiny arrays, little, tiny strings, keeps using them. If we can allocate those away, we can do much better than other implementations can. So I think that de-optimisation is the single most important optimisation there is in Ruby. If you apply de-optimisation, you can get rid of everything that makes Ruby slow. You can get rid of the overflow checks. You can get rid of the monkey patching. You can get rid of the temporary data structures which clog everything up. Any implementation which wants to look at creating a high-performance version of Ruby and maths is talking about making a jit for MRI. If you want it to be high-performance, then you need to look at using de-optimisation. And de-optimisation is the key optimisation to make. It's not simple to implement, though. We're only able to use it because we've got the massive power of the JVM behind us. Implementing it for a simple implementation I understand is pretty much not as attractable. JVM plus Truffle builds on a lot of other projects. So we're very grateful to JVM for taking us into their repository and we use their parser, the implementation, regular expressions, implementation strings, everything like that. Of course, we depend on Ruby itself for the definition of that language and we're starting to use the core library implementation for Rubinius. So JVM plus Truffle is taking what's best from the other implementations and is bringing it together with a really high-performance jit core. The other thing Truffle depends on is the team which make the Grail compiler at Oracle and JKU University in Lin's. So there's not more people than work on it than just me here. That's my Twitter handle. As I say, if you want more information about this, I've written a really detailed blog post. So if I've covered over anything today, if I've given you a simple summary of what we're doing, all the hard details are there and you can rerun those benchmarks for yourself, you can look at the performance yourself. And as I said, it is just a research project you won't be able to buy at any time soon. Thanks very much. There's no more good questions.