前面给公司出了一个有趣的问题,似乎没采用。所以现在放出来大家看着玩玩。

以下代码在python2中适用。python3请看尾部注释。

import os, time
data = range(10000000)
pid = os.fork()
if pid < 0:
    print 'error:', pid
    os.exit(pid)
if pid > 0:
    os.wait()
    os.exit(0)
sum(data)

在第2行执行的前后,使用ps和free观察内存使用情况,可以看到进程使用了320M内存,系统被占用了320M内存。在3行执行后再观察,有两个进程分别占用320M内存,系统总计被占用了320M内存。 问1,为什么两个进程分别占用320M左右内存,系统总计占用数并没有翻倍?这种现象叫做什么?

在10行执行后再观察,有两个进程分别占用320M内存,系统总计被占用了560M内存。 问2,为什么sum增加了系统内存占用,解释其开销。

问3,推测出python整数对象长度和当前CPU字长。(python自身的内存开销忽略)

答1. 这种现象叫做COW,copy on write。在fork后,两个进程会共享内存表项,一致的部分会仅使用一个页面。

答2. 每个进程的内存占用都没有上升,但总内存占用量上升了,这必然是发生了页面写时复制的结果。页面写时复制必须写入内存,因此推测python使用引用计数 手段控制对象生命周期。当sum时,每个对象都要被读取。在读取前,系统会增加其引用计数。在这个过程中会发生页面写时复制,导致系统内存占用上升。

注:内存复制必须发生在写时刻,说读取导致内存复制的统统不得分。答出引用计数四个字即可得全分。

答3. 在写时复制时,数字对象会增加引用计数,而数组对象不会(准确的说,数组对象本身引用计数会增加,但只有一页会发生复制)。发生复制增加的内存有 240M,因此整数对象长度24字节。未倍增内存有80M。对象有10M个。因此推断指针长度为8字节,当前CPU字长64位。

注:python3里有几个不同。首先,range返回了一个生成器,所以需要改为data = list(range(10000000))。其次,python3中,int长度为28,pointer长度为9。因此下面的数据会需要调整,而且并不很容易解释。