1 00:00:06,580 --> 00:00:08,190 - We just started learning here now 2 00:00:08,190 --> 00:00:10,609 how that escape analysis teaches us 3 00:00:10,609 --> 00:00:12,460 when something allocates or not. 4 00:00:12,460 --> 00:00:16,170 How something is shared will determine whether it allocates. 5 00:00:16,170 --> 00:00:18,760 Now there's another part of allocation in Go, 6 00:00:18,760 --> 00:00:22,260 and that is that if the compiler doesn't know the size 7 00:00:22,260 --> 00:00:24,420 of a value at compile time, 8 00:00:24,420 --> 00:00:27,110 it must immediately construct it on the heap, 9 00:00:27,110 --> 00:00:29,440 because those frames that I've been drawing 10 00:00:29,440 --> 00:00:32,700 for every function are sized at compile time. 11 00:00:32,700 --> 00:00:34,430 Frames are not dynamic, 12 00:00:34,430 --> 00:00:36,560 so if the compiler doesn't know the size of something 13 00:00:36,560 --> 00:00:39,337 at compile time, it cannot place it on the stack. 14 00:00:39,337 --> 00:00:41,820 Now the compiler knows the size of a lot of things 15 00:00:41,820 --> 00:00:43,350 at compile time, it knows your struct types, 16 00:00:43,350 --> 00:00:46,110 we already learned how to read that, your built in types, 17 00:00:46,110 --> 00:00:48,490 other types that we're going to be using. 18 00:00:48,490 --> 00:00:51,540 But sometimes you might have things like collections 19 00:00:51,540 --> 00:00:54,490 that are based on, their size is based on a variable, 20 00:00:54,490 --> 00:00:58,460 which gives the compilers no idea what the size of that is, 21 00:00:58,460 --> 00:01:02,320 and we're going to have automatic allocations immediately. 22 00:01:02,320 --> 00:01:04,700 That's the other place where you might have an allocation 23 00:01:04,700 --> 00:01:06,997 in Go, compiler doesn't know the size of something, 24 00:01:06,997 --> 00:01:09,220 automatic allocation. 25 00:01:09,220 --> 00:01:11,890 But we talked about these stacks in Go 26 00:01:11,890 --> 00:01:13,970 being very, very, very small. 27 00:01:13,970 --> 00:01:16,470 The operating system stack's about 1MB, 28 00:01:16,470 --> 00:01:19,950 and your Go stack is 2K. 29 00:01:19,950 --> 00:01:22,670 What happens when you've got a Go routine 30 00:01:22,670 --> 00:01:25,050 that's making lots of function calls 31 00:01:25,050 --> 00:01:28,950 and eventually it runs out of stack space? 32 00:01:28,950 --> 00:01:29,890 What's going to happen? 33 00:01:29,890 --> 00:01:32,260 We can't just terminate the Go routine 34 00:01:32,260 --> 00:01:35,320 like we do with a thread, and we won't. 35 00:01:35,320 --> 00:01:37,680 Remember, Go is about integrity first, 36 00:01:37,680 --> 00:01:40,350 it's about minimizing resources second. 37 00:01:40,350 --> 00:01:42,200 We always want to try to reduce 38 00:01:42,200 --> 00:01:44,810 and minimize the amount of resources that we need. 39 00:01:44,810 --> 00:01:46,810 We're all on cloud computing today, 40 00:01:46,810 --> 00:01:48,710 shared resources are important. 41 00:01:48,710 --> 00:01:51,110 That 2K stack is a big part of that. 42 00:01:51,110 --> 00:01:53,310 But when we run out of stack space, 43 00:01:53,310 --> 00:01:56,690 what we need now is to get a new stack. 44 00:01:56,690 --> 00:01:58,560 This is something for me and it's very unique 45 00:01:58,560 --> 00:02:01,410 from a programming language, I'd never seen this before. 46 00:02:01,410 --> 00:02:04,030 Basically, imagine that we had our stack, 47 00:02:04,030 --> 00:02:05,610 we had some value there, 48 00:02:05,610 --> 00:02:08,300 and imagine we were even sharing this value 49 00:02:08,300 --> 00:02:10,470 as we move down the call stack. 50 00:02:10,470 --> 00:02:13,100 Eventually, we run out of stack space. 51 00:02:13,100 --> 00:02:17,170 What Go does is it has what it called contiguous stacks. 52 00:02:17,170 --> 00:02:20,510 What it's going to do is allocate a larger stack, 53 00:02:20,510 --> 00:02:23,900 25% larger than the original one, 54 00:02:23,900 --> 00:02:28,900 and then, what it's got to do is copy all these frames 55 00:02:29,270 --> 00:02:34,050 back over, in this case, these pointers are relative, 56 00:02:34,050 --> 00:02:36,030 so they're very fast to fix. 57 00:02:36,030 --> 00:02:39,490 But basically, that Go routine, during the function call, 58 00:02:39,490 --> 00:02:41,950 we're going to take a little bit of this latency hit 59 00:02:41,950 --> 00:02:45,610 on creating the larger stack, copying those frames over, 60 00:02:45,610 --> 00:02:48,070 and readjusting any of these pointers. 61 00:02:48,070 --> 00:02:50,530 We do this in Go, we take this hit, 62 00:02:50,530 --> 00:02:52,960 one for integrity and two to minimize resources. 63 00:02:52,960 --> 00:02:54,450 I told you nothing is free. 64 00:02:54,450 --> 00:02:57,470 Integrity and minimizing resources come with a cost. 65 00:02:57,470 --> 00:02:59,030 But this isn't something that's going to happen 66 00:02:59,030 --> 00:03:00,200 all of the time. 67 00:03:00,200 --> 00:03:03,820 2K is usually more than enough for our stacks, 68 00:03:03,820 --> 00:03:05,240 because you usually don't go more 69 00:03:05,240 --> 00:03:08,520 than even like 10 function calls deep, you don't. 70 00:03:08,520 --> 00:03:10,440 There's other optimizations the compiler can do 71 00:03:10,440 --> 00:03:12,386 to keep these frames very, very small. 72 00:03:12,386 --> 00:03:14,920 This is a very good trade-off and balance. 73 00:03:14,920 --> 00:03:16,610 But this can happen. 74 00:03:16,610 --> 00:03:18,820 What this is telling us is, 75 00:03:18,820 --> 00:03:20,940 which is, again, really unique for me, 76 00:03:20,940 --> 00:03:24,130 is that values on your stack 77 00:03:24,130 --> 00:03:26,920 can potentially be moving around. 78 00:03:26,920 --> 00:03:28,620 This is a whole new world. 79 00:03:28,620 --> 00:03:31,000 I mean, I'm going to show you an example of this 80 00:03:31,000 --> 00:03:32,850 right here with a little piece of Go. 81 00:03:33,890 --> 00:03:36,540 I've got a constant of size 10, 82 00:03:36,540 --> 00:03:38,980 and here's my main, right, here's main, 83 00:03:38,980 --> 00:03:41,800 and we're constructing a string value, there it is, 84 00:03:41,800 --> 00:03:44,740 it says HELLO, and then I'm going to share this string value 85 00:03:44,740 --> 00:03:47,960 down the call stack, you see that on line 14. 86 00:03:47,960 --> 00:03:51,430 But what I've also done is allocated an array. 87 00:03:51,430 --> 00:03:54,200 Arrays, we know the size of an array at compile time, 88 00:03:54,200 --> 00:03:56,330 it's part of, built into the array. 89 00:03:56,330 --> 00:03:58,620 This is a 40 byte array on the playground, 90 00:03:58,620 --> 00:04:01,510 an integer is 4 bytes, I have 10 of them, 40 bytes. 91 00:04:01,510 --> 00:04:04,340 This array is going to be placed on the stack frame. 92 00:04:04,340 --> 00:04:06,960 Still going to keep the stack frame small. 93 00:04:06,960 --> 00:04:09,560 But stackCopy's a recursive function. 94 00:04:09,560 --> 00:04:13,140 It calls itself over and over and over again, 95 00:04:13,140 --> 00:04:16,260 constantly sharing this string down the call stack. 96 00:04:16,260 --> 00:04:20,880 Now, if I run this for 10 function calls or 10 iterations, 97 00:04:20,880 --> 00:04:24,360 you see that the address on every iteration of the string 98 00:04:24,360 --> 00:04:28,440 inside of that frame for main hasn't changed. 99 00:04:28,440 --> 00:04:29,470 Beautiful. 100 00:04:29,470 --> 00:04:32,270 Our 2K stack is large enough to run this program 101 00:04:32,270 --> 00:04:33,820 without any issues. 102 00:04:33,820 --> 00:04:36,940 But what if I change our 40 byte array 103 00:04:36,940 --> 00:04:41,190 to be a 4,000 byte array? 104 00:04:41,190 --> 00:04:42,023 Whoa. 105 00:04:42,023 --> 00:04:44,450 Remember, we only started with a 2K stack. 106 00:04:44,450 --> 00:04:46,730 That means on the first call to stackCopy, 107 00:04:46,730 --> 00:04:48,570 we're going to get some growth. 108 00:04:48,570 --> 00:04:52,230 That also means that that 25% is only going to go so far, 109 00:04:52,230 --> 00:04:55,380 and we're going to see more growth as we run this program. 110 00:04:55,380 --> 00:04:58,033 Watch what happens now when I run this program. 111 00:04:59,470 --> 00:05:01,890 What you'll see is, honestly, 112 00:05:01,890 --> 00:05:05,360 this is already a new stack location. 113 00:05:05,360 --> 00:05:07,220 But look on index two. 114 00:05:07,220 --> 00:05:11,830 On index two, we also have a new address, 55fa0. 115 00:05:11,830 --> 00:05:13,800 We have another new stack. 116 00:05:13,800 --> 00:05:17,570 In fact, if you look on index six, we've done it again. 117 00:05:17,570 --> 00:05:18,403 Why? 118 00:05:18,403 --> 00:05:21,360 Because this 4K array is being kept on the stack frame 119 00:05:21,360 --> 00:05:23,040 because it doesn't need to escape. 120 00:05:23,040 --> 00:05:26,170 Remember, escape analysis says we want to use the stack 121 00:05:26,170 --> 00:05:29,290 and using the heap is an exception when there's integrity. 122 00:05:29,290 --> 00:05:32,440 Here's a classic case of there is no escape. 123 00:05:32,440 --> 00:05:37,150 But you can see that the stack has grown several times. 124 00:05:37,150 --> 00:05:38,510 Cool, we're having the ability 125 00:05:38,510 --> 00:05:40,650 to start with very small stacks, 126 00:05:40,650 --> 00:05:42,567 which means we can have lots of Go routines, 127 00:05:42,567 --> 00:05:44,780 and the stack grows when it needs to grow. 128 00:05:44,780 --> 00:05:47,430 But think about the side effect that we have here. 129 00:05:47,430 --> 00:05:51,340 Since a value can move in memory that's on the stack, 130 00:05:51,340 --> 00:05:53,610 this actually creates an interesting constraint 131 00:05:53,610 --> 00:05:54,870 for us in Go. 132 00:05:54,870 --> 00:05:59,570 What this means is, is no stack can have a pointer 133 00:06:00,720 --> 00:06:02,330 to another stack. 134 00:06:02,330 --> 00:06:06,750 Imagine, we had all of these stacks all over the place, 135 00:06:06,750 --> 00:06:08,843 hundreds of thousands of Go routines 136 00:06:08,843 --> 00:06:12,220 with pointers to each other's stacks. 137 00:06:12,220 --> 00:06:16,020 That would be total chaos if one stack had to grow. 138 00:06:16,020 --> 00:06:16,970 Think about it. 139 00:06:16,970 --> 00:06:19,230 If this stack had to grow, 140 00:06:19,230 --> 00:06:21,250 we would have to track every pointer 141 00:06:21,250 --> 00:06:24,630 that points to this stack and update it as well. 142 00:06:24,630 --> 00:06:26,460 You want to talk about stop the world latency, 143 00:06:26,460 --> 00:06:28,200 that would be insane. 144 00:06:28,200 --> 00:06:30,460 One of the constraints or engineering choices here 145 00:06:30,460 --> 00:06:33,300 in Go is, since our stacks can move, 146 00:06:33,300 --> 00:06:35,920 it means that the only pointers to a stack 147 00:06:35,920 --> 00:06:37,890 would be local pointers. 148 00:06:37,890 --> 00:06:40,690 Only that stack memory is for the Go routine, 149 00:06:40,690 --> 00:06:42,250 then the Go routine only. 150 00:06:42,250 --> 00:06:45,600 Stack memory cannot be shared across Go routines. 151 00:06:45,600 --> 00:06:48,460 Again, this is where the escape analysis come into the heap. 152 00:06:48,460 --> 00:06:52,550 The heap basically now is used for any value 153 00:06:52,550 --> 00:06:55,359 that's going to be shared across Go routine boundaries, 154 00:06:55,359 --> 00:06:58,000 and any value that casting on the frame 155 00:06:58,000 --> 00:06:59,910 because there's an integrity issue, 156 00:06:59,910 --> 00:07:02,910 or any value where we don't know the size at compile time. 157 00:07:02,910 --> 00:07:06,870 These are our three constraints around allocations in Go. 158 00:07:06,870 --> 00:07:08,530 Again, it's because we want to really be able 159 00:07:08,530 --> 00:07:10,800 to have hundreds of thousands of Go routines, 160 00:07:10,800 --> 00:07:11,650 which means we need to be able 161 00:07:11,650 --> 00:07:13,410 to have hundreds of thousands of stacks, 162 00:07:13,410 --> 00:07:15,060 we couldn't do that if they were MG. 163 00:07:15,060 --> 00:07:17,100 They have to be as small as possible. 164 00:07:17,100 --> 00:07:18,730 We take the small hit, 165 00:07:18,730 --> 00:07:23,730 always for integrity and for uses, small memory usage, 166 00:07:24,160 --> 00:07:26,240 because that is our higher priority in Go. 167 00:07:26,240 --> 00:07:27,330 What we care about is not 168 00:07:27,330 --> 00:07:29,510 that our code is the fastest it can be, 169 00:07:29,510 --> 00:07:31,653 we care about is it fast enough.