跟读练习: Wrote My Own Memory Allocator (and ran DOOM with It) - 通过视频学习英语口语

正在创建课程...
1
One day I was outside, doing normal human activities like touching grass and listening to birds.
2
When the blazing sun gave me a strange idea.
3
What if I wrote my own malloc?
4
So as soon as I got home I started building.
5
And in this video I'll show you how I wrote my own memory allocator in PUC from scratch
6
and how I used to run a real application.
7
Before we start programming it's important to understand what malloc actually does.
8
The standard malloc function is used to allocate memory dynamically.
9
For example, you can use it to create a dynamic array, precisely known at runtime.
10
But because this is C, there is always some problem with memory hiding somewhere.
11
As you probably know, C doesn't have a built-in garbage collector, so managing memory is the programmer's responsibility.
12
For the allocation, C has a special free function.
13
It takes a pointer to a previously allocated memory block and marks that block as free.
14
So the system can reuse it later.
15
And that means I also need to create my own free function.
16
First I had to choose the names.
17
I remembered the name of my channel and decided to call them podalloc and pod3.
18
But before we continue, let's take a look at how memory allocation actually works.
19
Every process has its own memory space.
20
This space is divided into several zones.
21
At the top we have the stack, which is used for function calls and local variables.
22
Below it we have the heap, which is used exactly for dynamic user data.
23
And this is the part that malloc works with.
24
Below that there are other memory regions used for global variables, program code and other things that are not really important for us right now.
25
In this layout the stack grows downward, while the heap grows upward.
26
And it's better to keep these two zones from overlapping, unless you really want to meet a segmentation fold.
27
Another question is how we can actually use this heap for our needs.
28
We need some way to request memory from the operating system.
29
There are a few ways to do this, but for me the best is MMAP.
30
It works kinda similar to malloc.
31
You tell it how much memory you need and it's returned a pointer to the beginning of that memory region.
32
But the important difference is that malloc reserves memory inside an already existing area.
33
While MMAP asks the iOS for a new memory region of their requested size.
34
Now we can use this to write a simple version of the allocator.
35
To free allocated memory we need to know its size.
36
For that I added a mem block structure with a size field and a data pointer, which points to the beginning of the memory available to the user.
37
In POT-A-LOG we simply call a map to request some memory from the operating system.
38
Then at the beginning of the memory region we place our mem block structure.
39
It works like a header for the allocated block.
40
Later in POT-3 we use this header with monMap monMap does the opposite of mmap.
41
It releases the memory back to the OS.
42
For this to work it needs a pointer to the beginning of the memory region and its size, exactly what we keep in the header.
43
After this we can finally test how it actually works.
44
As you can see I allocated some memory, wrote some text into it and printed it.
45
And works fine!
46
But of course it can't be that easy.
47
Right now this is basically just a wrapper around M-map and Moonmap.
48
The main problem with this approach is time.
49
I wrote a simple test that allocates and frees memory, and then compares it with the malloc from C.
50
As you can see, the difference is really big.
51
My allocator is 435 times slower.
52
It's even slower than me understanding hints.
53
Using this in a real program would seriously slow it down.
54
The reason is M-Map and MoonMap.
55
Every allocation and every free requires a system call.
56
And they are pretty slow.
57
Sometimes they are so slow that you forget why you even call them.
58
To make our allocator faster, we need to reduce the number of M-Map calls as much as possible.
59
A common approach is to call a map once to allocate a large chunk of memory.
60
Usually at least one virtual memory page in your system.
61
If you're wondering what page I'm talking about, here's a quick reminder of what virtual memory is.
62
It's a memory abstraction provided by the operating system.
63
Every process gets its own virtual address space, which is divided into small, fixed-sized parts called pages.
64
The program works with virtual addresses, and the system translates these addresses to real physical memory in RAM.
65
Because of this, variables in different programs can even have the same addresses.
66
But that doesn't mean they are stored in the same place in RAM.
67
Each address just belongs to its own process virtual memory space.
68
So, with a new approach we allocate one big chunk of memory
69
and then filled step by step whenever someone calls for tolog.
70
When this page fills up, we allocate another one.
71
As you can see, in this example we have 8 memory allocations, but only 2 mmap calls.
72
This is one of those cases where the number doesn't matter, but matters how you use it.
73
To implement this I changed the mem block structure a little bit.
74
Now each block works like part of doubly linked list.
75
And I also added a free flag to show whether the block is available for allocation or not.
76
Also because we now use pages, I added a page structure.
77
It works like another list, but instead of linking memory blocks, it links different pages together.
78
This will come in handy later.
79
After this I change the allocator function.
80
Now it searches for a free block with enough space.
81
If it can't find one, we create another memory page.
82
You may notice that if the user asks for only 100 bytes, we allocate an entire page for it.
83
We use a much bigger block.
84
We are just wasting memory.
85
To deal with this, we split the memory block.
86
One part is given to the user and the other part remains free for future allocations.
87
With this new approach I also changed port 3.
88
If we split large memory blocks during allocation, we need to merge them back during their allocation.
89
Then the merged block can be reduced later.
90
And now we only return memory to the system when the whole page becomes free.
91
So this approach reduces main map calls too.
92
Well, let's test this with some allocations.
93
And as you can see it somehow become worse.
94
Now it is 1823 times slower, which doesn't really look like a better solution.
95
Without previous changes, it looks more like a waste of time.
96
But if we look at the number of mMAP calls, it becomes much smaller, so that means the problem is not the system calls.
97
After a bit of investigation, I found that most of the time is spent searching for a free block.
98
More precisely, it takes 99.3% of the total time.
99
On one hand, that's good, because system calls are no longer the main problem.
100
On other hand, now we need some fancy data structure to make it faster.
101
The current version searches through a list of all memory blocks.
102
It has to check each block until it finds a free one.
103
So this takes about 0 and time.
104
Looking at this layout, we can see that free blocks are scattered randomly around the page.
105
At the end of the page, there is also some unused space that has not been allocated yet.
106
What if we move free blocks into a separate list?
107
That's where we can quickly get a free block without searching through all allocated blocks.
108
But there is another problem.
109
We need to find a block with the right size.
110
For example, if we need a block of 100 bytes or more, we may have to skip smaller blocks before finding one that actually fits.
111
This can be fixed too.
112
Instead of using one free list, we create 8 of them.
113
Each list stores memory blocks of a certain size category.
114
When a block is freed, it's added to the appropriate list.
115
The benefit is that this is much faster than the previous approach.
116
Usually programs allocate small chunks of memory, so most allocation will fit into one of those eight categories.
117
Another question is how to do this in code.
118
First I added three list pointers to our structures.
119
Then I added functions for inserting blocks into three lists and removing blocks from them.
120
Now when a new page is created, its first free block is automatically added to the appropriate free list.
121
I also changed how splitting works.
122
Now when we split a block, we remove the original block from the free list, give one part to the user and add the remaining part back to the correct free list.
123
And in pod3 I now use the same insert and remove function to keep all free lists updated.
124
At this point it can already works, but there is always some hidden problem in a low level program.
125
And this time the problem is not even the program itself, it's the machine where the program runs.
126
Right now I can allocate 3 bytes and then 4 bytes.
127
And as you can see the second address is not aligned.
128
On some systems this can cause an error, because some processes don't like unaligned addresses.
129
Also, aligned memory is usually faster to read and write.
130
So, to fix this we need to round up the requested amount of memory.
131
We can do it with simple math, but that would be too slow and I need somehow to justify all the time I spent learning bit operators.
132
This version works much faster.
133
We start by adding this to move the number into the next alignment range.
134
For example, if the number is 12 and the alignment is 8, it becomes 19.
135
When we take that, which is 7, in binary it looks like this, exactly the lowest 3 bits.
136
After that we use the tilde operator to flip the bits and create a mask.
137
Finally we use the end operator to raise the lowest 3 bits.
138
As a result 19 becomes 16, which is perfectly aligned to 8 bytes.
139
Pretty easy, as you can see.
140
So after crawling through the depths of C, we can finally run it.
141
And this version actually works better than all previous ones.
142
Of course it's still not as fast as the original Mylock, but further improvements would be much harder and would probably give us a smaller performance gain.
143
Testing only with benchmarks is boring, so let's see how it works in a real program.
144
For the target I chose Doom.
145
I replaced its original allocation function with my own, added some logs to track memory usage and wrote a small python script to visualize it.
146
After playing for some time, everything worked fine for me, no crashes or anything like that.
147
For me, this was a really interesting project, And I hope you enjoy it too.
148
I recommend you trying to write your own allocator as well.
149
It really helps you understand how memory works and improves your understanding of low-level programming.
150
If you enjoyed this video and want to see more, please subscribe, leave a like and write a comment with your ideas or questions.
151
Thanks for watching!

看YouTube学英语:从内存分配器到 Doom 的编程故事

这是一个程序员突发奇想的真实场景:在户外感受自然时,被阳光激发灵感,决定自己编写内存分配器,还成功用它运行了经典游戏 Doom。视频里不仅有技术细节,还有调试过程中的笑与泪,是个兼具趣味性和知识性的英语学习素材。通过这样的内容,你既能了解编程知识,又能练习英语听力和口语,可谓一举两得。

实用表达与搭配

  • blazing sun:烈日。比如视频开头提到“the blazing sun gave me a strange idea”,生动描绘了阳光强烈的场景。
  • dynamic array:动态数组。这是编程中的常用术语,在描述 malloc 功能时频繁出现。
  • segmentation fault:段错误。程序员常遇到的错误类型,视频中用“unless you really want to meet a segmentation fault”形象说明内存重叠的后果。
  • system call:系统调用。是操作系统提供的服务接口,视频里解释了 mmap 和 munmap 属于系统调用。
  • doubly linked list:双向链表。数据结构中的概念,用于优化内存分配器的性能。

你的 shadow speak 挑战

现在就打开视频,找到“After this, I changed the allocator function... we create another memory page”这段(约视频中间部分),开始 shadowing 练习。先听一遍原音,注意说话人的语调、停顿和重音,尤其是“search for a free block”“fill up”等短语的发音。然后跟着录音逐句模仿,尽量做到语速、节奏一致。重复练习 3-5 次后,用自己的话复述这段内容,检查是否能流畅表达关键信息。这个任务能有效提高英语发音和口语流畅度,快来试试吧!

通过看YouTube学英语,结合 shadow speak 练习,你会发现英语口语进步其实没那么难。下次遇到喜欢的视频,不妨都用这种方法试试,坚持下去必有收获。

视频中的语法

说话人最常用的结构,并附上视频中的原话:

结构视频中的用法
被动语态 be + 过去分词 — 强调发生了什么,而不是谁做的is divided · are stored · is given
定语从句 who / which + 从句 — 补充说明人或事物stack, which is · heap, which is · space, which is

什么是跟读法?

跟读法 (Shadowing) 是一种有科学依据的语言学习技巧,最初开发用于专业口译员的培训,并由多语言者Alexander Arguelles博士普及。这个方法简单而强大:您在听英语母语原声的同时立即大声重复——就像是一个延迟1-2秒紧跟说话者的影子。与被动听力或语法练习不同,跟读法强迫您的大脑和口腔肌肉同时处理并模仿真实的讲话模式。研究表明它能显着提高发音准确性,语调,节奏,连读,听力理解和口语流利度——使其成为雅思口语备考和真实英语交流最有效的方法之一。

影子跟读法: 阅读完整分步指南 →