So as you know, there's no way to make and free memory in go
because it's a garbage collected language and in go 1.5
major changes were made to the garbage collector so that it limits, the
amount of time spent on garbage collection. So the garbage collection should be
something you don't have to worry about. Of course, there's not a hundred percent true
because if you do, create a, large amount of garbage, you will use a large amount of
memory. And you may then find yourself trying to discover, where is that coming from my
program? Because
Memory is limited and especially for long-running go programs. You'll find
yourself needing to limit the amount of memory and perhaps reuse it.
So there are a number of things you want to be able to, do you want to be able to figure out what's being
allocated and where it's going and later. You're going to want to figure
out how to reuse memory so that you put less pressure on the
garbage collector for smaller programs. You can just assume the garbage collection
does what you need, but for long-running ones, it can be quite important. So I've
made a kind of silly test
Program. He had that makes a load of garbage and we're going to look at
what happens. So it's a ridiculous program. What it does, is
it creates a string? That's a reasonable length. And then
it goes into an infinite Loop. And on that string, it keeps calling this
length function. It completely ignores the returned from it.
And within the length function, is actually going to return the length of the string, but it's going to do
it in a silly way, but it will create garbage. So, first of all, it's going to
turn it into a bite, slice.
Race that string is going to get the length of the by slice as an integer
being stored in n, and it's going to create another bite, slice of that
length, and return that value.
Now, it looks like there's nothing here that really should be
worried about. This shouldn't create much garbage but if we run this program we're going to discover that
there's quite a lot of garbage collection going on. I've made using
the time package the inside the loop a little thing that every second.
So if since it began a second has gone by
Is going to use the runtime package, to get memory information. If we
just pop in to go doc and we have a look at runtime memstats.
So let us take a quick look at what memstats has in it,
which we can use use. Go doc to do that and it's a structure with a
lot of information about what's happening internally in, go such as,
you know, the amount of memory that has been allocated over all the what I'm totally interested
in this one, the size of the Heap. So the size of allocated bytes is not yet
freed. And also, you can have a look
down here and you can see there's no mgc which is the number of garbage
collections that have happened. So I'm going to print out those two values. I want to point out how much memory is allocated for
the Heap right now, and for how many garbage collections have
happened bear in mind. There are two places that you really need to worry about in terms of memory.
There's the stack, which is what's temporary to a function and then there's the
Heap, which is the global amount of memory that go is using for
objects which will have to be garbage collected. Obviously the stack disappears when a function
gets exited anything that is
Not on the stack, will in a function will have been said to have escaped so we'll have
left the function and will actually be on the Heap. So it's the Heap size we have to worry about
so I should be able now to run this program.
We're going to let it run and you can see the number of garbage collection is going up. So it's doing mayor very very
many garbage Collections and you can see the memory bouncing around, so is up
around five Mega, then it gets garbage collected down. So clearly
there is garbage being generated in this program as things go around and we can find out a bit
more by asking the go run tool like this. We can say
GC flags and things to the garbage collection and we do - em
and then it's going to print out a bunch of information. It allows
Run the program as well. But I'm going to stop it from running and it's going to tell us what it's thinking
about what it can do. And you'll notice that it
says, that this thing, this make here,
On this line here escape to the Heap. Is it that actually ended up being on the
Heap. So there's probably a lot of where the garbage is because we're making these these useless slices.
It doesn't mention this guy here so it seems unlikely that one
will have done but so the various things here that are escaping out to the Heap. So
this sort of thing can be a little bit deceptive because there's there's information leaving this function going on to the Heap
and doesn't this, just an integer and obviously was on the stack and it just disappeared, didn't it didn't escape
and you can see other things in here. So,
Chaz, this VAR here is noticing that, that doesn't need to go onto the
stack, is just a structure that's been allocated on. The stack, is not on the Heap, and
so it doesn't have to be garbage collected. So you can see even a fairly small program. There's quite a bit of
garbage collection that's going on and when you've got a lot of garbage collection,
the garbage collection pauses can become quite long. Now what they didn't go
1.5 is they altered the garbage collector to reduce the pause
time to give it a sort of guaranteed upper bound but you may still
want to reduce the amount of garbage.
Image just so that you reduce the amount of allocated memory because you maybe sharing memory on your machine
with somebody else.
So the previous program was creating a lot of garbage which was being god with collected. You could see the
memory going up and down on the Heap and a very large number of garbage collections actually having to do
work. I'm going to change that program slightly. So that rather than
creating garbage, it starts allocating large amounts of memory and we want to want to
find out where and why. So what I've done is I've
now created a slice and then that slice, I'm going to
keep the length of the strings are all going to be the same. In fact, just going to repeatedly go
around
And add the lengths to that particular slice using this function. Save up here.
It's just doing an append to a slice, so it's going to get bigger and bigger, and bigger, and bigger and bigger. Obviously,
use a large amount of memory, it Loops forever. It still does the same
thing printing out, the Heap size, the number of garbage collections, but it's also going to do
something, using the Pea Prof package. Now sitting all that for a
moment. Let's just build that particular program and
run it. I'm going to see a slightly different Behavior. Now the Heap size are now getting
enormous, the
Um, of garbage collection is actually fairly small because there isn't much garbage here. There's just a lot
of memory being allocated. As you can see, we're getting up into hundreds of megabytes of
memory and in order to find out where that memory was obviously, in our case, it's
fairly obvious, we can use the P Prof package. Now the people of
package has a couple of things one, it can do CPU profiling if you're looking
at the CPU problem or memory profiling. And what I've done
here is I've caused it on the first time around. So after a second of
running to write out,
File called profile using the right heat profiles function,
so it will just write that out. I've been a bit naughty because I've ignored areas here but this is just a
little test and so it will have written out a file called
profile here. There it is and I can then run the P
Prof program. So if I do go to all people off and then
Foo, that's the name of the program. I'm actually debugging and the
profile. I can come in here and it will tell me where the memory is being able to cater.
So the large amount of memory is being allocated to.
Main Minister fairly simple program but I can also get it to tell me exactly
where. And so, if I go if I type list and I go back
up again, it's going to show me where the memory was being allocated.
In fact, it's here. There's 224 megabytes of
integers been allocated, on that specific line. So when you're debugging
something and you want to find out where memory comes from, this is very very useful. For example, this is the sort of
place where you would find a make or perhaps a complex structure being
allocated and you'll be able to figure
How much memory is being used and where it is within your program, for a very
complicated program, there's another option, which is called Web, which will,
if you've got graphviz installed, which includes dot will actually create an
SVG file showing you the functions, you're calling throughout your
program and show you where it memory is allocated. In, this example, is actually
pretty simple because everything's happening in that main, but I can still run it and give you
a sense of what it looks like.
So if we zoom in, it's going to show me that main
here within main. There's two hundred twenty four megabytes allocated and I can look
around in here and see precisely where that got that got allocated by
which parts of the program. So in fact is split into a couple of different allocations.
So this particular functionality for a complex program, you also get a
big directed graph. Here can be very useful in highlighting. Okay, this is
where the memory is really going. Also, the
Pop function will give you the top location so you can obviously work down here and
see whereabouts in the program. The memory is being allocated.
So the previous program I showed you, my generation garbage, was rather simple is better to use
something. That's a slightly more realistic situation. So right here, I've
built a little program which is similar to many things that happen in real
programs which is they need to create and then forget about buffers. There might
be things that have been read from a disk or from the network, are the places
where you need to create often bite, slices of information. So,
to kind of simulate that situation, I've written a little program that starts
Tengo routines and these go routines Loop forever. And what they
do is they make some random size buffer using this make buffer
function, it makes a buffer that's between 5 and 10 Meg
of bites. And then it stores it in a pool of
buffers here. So some of these are buffers that are currently in use, and of course, the ones that are
no longer in the pool when they get pushed out, our things are going to be on the
Heap and they could be garbage collected and then it's going to sleep for some random
amount of time and do this.
It's all over again. So the idea here is it simulates. A whole lot of buffers that are coming into and out of existence over time
and to make sure all these don't overlap, I'm giving each
go routine, a block of 20 slots in the pool and I do that, because it's not
safe to do concurrent access on the same slots. In a slight, it is
safe to do concurrent access in separate parts of the slice long as you're not changing it.
And then I'm going to use runtime again with memstats for every second
to print out the size of the Heap and see what's happening here. So, fairly
simple. But the idea of this is
Simulate something that many programs really do. So let's just build that that looks great
and we can run it and we're going to see is we're going to see the size of the Heap over time.
So obviously we are allocating quite a lot of memory here. There's 10 go routines, running continuously, creating
buffers, dropping them and there are up to 200 of these buffers actually in the pool,
which are in memory, all the time, and in some sense in use by the program,
and you can clearly see that as it started up. The pool size grew,
there are many items in the pool and then
over time, you'll seeing the Heap go up and down, as garbage collection occurs,
but the Heap is fairly large, and you probably don't want to have memory grow
enormously like this, if you can avoid it. So how can you deal with that problem?
Well, there's a couple of ways, the most common ways to do some sort of
recycling of these buffers. If you think about these buffers are just blocks of memory,
you could make them available to some other go routine.
I gave an example in other part of this course of the Leaky buffer where you could use a
buffered channel to store available, but
byte buffers and then get them and put them back up to some, some number that are available.
There's actually a new piece of functionality in go which
is called the sync.pool which allows us to solve this problem without doing any
real special work and allows us to keep around a pool of things.
In this case it would be buffers. It could be any sort of structure or type that's we want to
save and it
I was go to scale up and scale down as needed, so if there's a need to free them, it will
free them automatically, and you don't have to worry about what's going on and that can save significant
amounts of memory. If you're using creating and destroying objects
buffers things like that, very frequently. So I've modified the program now to use the
new sync.pool, which is an automatic way of storing and retrieving
items that you might want to reuse later while still giving
go the chance to throw them away. If it needs to make space just to show you how I've
done that. I've added
Added in the sink package here. And then I've created this thing I call spare, which is where I'm
going to keep my spare buffers and what the sync.pool
needs. It needs a new function. So if it needs to create a new something,
whatever it is, you're storing in it, it has to have some function that can do that, and it has to be a
function that Returns the empty interface. So I modified make buffer to
say it returns an empty interface, it's still returning a bite, slice, one of
these things but as what is returning and store that in spare new
and then there are two functions.
As in the pool here which are get and put. So what's
happening here, is that when we need a buffer and one of these go routines needs a
buffer, it can call get which it does here. There's a type of solution because
it doesn't know the type. So it has to say yes this is actually a byte buffer and
it will either get one from its internal collection of
buffers that been put back in the past or it'll call the new function to create a
new one if it has two. And then we're going to store that thing in my original
pool, which was the
Live list if you like. So if there's a slot for that, then put that
put that back. Put the thing that was their thing. I'm about to evict back into the sync.pool
for reuse later and store the new thing in it.
So this is very, very simple and you're not having to do any work to manage. That's why there's
automatically being managed by go. Let's just build it.
I'm going to see a slightly different behavior in terms of the Heap here.
So what happens is the if you remember in the previous one, the heat was kind of going up and down in
size because it was it was a tremendous amount of garbage being generated in order
to do this, what's going to happen now is that he was going to grow up to the size of my
pool of 200 buffers and it's going to stay very, very
stable. You can see it's kind of slow down in terms of How It's Growing here.
As buffers are put into the sync.pool and taken out as needed. And if occasionally,
it might need to create a new one, it will you'll notice another thing here, which is
stop this guy running, which
That these go routines are sharing access to the sync.pool. It's completely
safe to do that. So you don't have to do anything special around locking you can run as many
go routines, you like and they can all be putting objects and taking them off again into a
pool. Since it takes an empty interface, you can put anything you like in here.
So any sorts of structures buffers? Like I have that you might want to reuse,
you can do that. The only thing you might want to do between instances is reset
them. So it is 0 them if there was sensitive data in them, or if its structure and maybe it needs
to have certain
Certain elements zeroed. But apart from that, this is really the best way in which to control
memory use. If you're in a situation where you create a lot of garbage of
similar items and keep discarding them, especially if they're very large.