性能优化的一个关键就是使用缓存。从硬件层面的 CPU 的一级缓存,二级缓存,三级缓存 到操作系统内核的各种文件缓存,目录缓存,文件元数据信息缓存以及应用软件各种缓存都是为了 加速访问。
而我今天遇到了一个没有使用缓存会导致内存膨胀的问题。之所以会导致内存膨胀是因为每创建一个协程, 会创建一个对应的 C 的内存结构,而这个 C 结构体的内存的生命周期和协程的生命周期是不一致的。 因此即使协程被 GC 回收了也无法回收 C 结构体的内存。
针对这个问题,我想到两个方案:
- 如果可以给创建的协程设置 GC 函数,由 GC 函数来实现 C 结构体的内存回收也是很好的。
- 另一个就是本次方案,使用协程缓存来解决,这样不会创建新的协程,性能也会更高了。
不过缓存也会导致被缓存数据无法被 GC 回收的问题, 因此使用缓存也要非常的小心。 缓存不宜开得过大,过大浪费内存;更不能开得太小,太小缓存就起不到应有的效果。
下面把 func 和 args 赋值为 nil 就是为了减少缓存带来的影响,让 GC 可以尽快,尽可能的回收内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
local co_create = coroutine.create
local co_yield = coroutine.yield
local co_create = coroutine.create
local co_status = coroutine.status
local co_resume = coroutine.resume
local co_pool = {}
local co_pool_cnt = 0
local co_pool_size = 128
local function create_co(func)
local arg1, arg2, arg3
local co
if co_pool_cnt > 0 then
co = co_pool[co_pool_cnt]
co_pool[co_pool_cnt] = nil
co_pool_cnt = co_pool_cnt - 1
co_resume(co, func)
else
co = co_create(function(...)
local rets = {func(...)}
while true do
if co_pool_cnt < co_pool_size then
co_pool_cnt = co_pool_cnt + 1
co_pool[co_pool_cnt] = co
else
return unpack(rets)
end
func = co_yield(unpack(rets))
local args = { co_yield() }
rets = {func(unpack(args))}
func = nil
args = nil
end
end)
end
return co
end
-- examples
local function f(a)
print(a)
end
local cos = {}
for i = 1, 20 do
local co = create_co(f)
table.insert(cos, co)
end
for i = 1, 20 do
local co = table.remove(cos)
coroutine.resume(co, i)
end