问题描述:有1T字节的数据,都是int32整型。现只有1G字节内存可以用于计算。求所有数据中出现次数最多的数。你可以假设这样的数只有一个。
思路
拆分
内存受限的计算,只有1G字节内存。为便于分析,先假定只用0.5G字节的内存做计算。1T字节的数据显然不能一次性装入内存,所以需要先做数据拆分。后续计算结果不求精确,只是展示思路。
。将原数据集分成2000份,然后逐个处理。随便拆分成2000份就可以了吗?下面讲讲拆分策略。让每一份数据的取值范围与其它份没有交集,可以简化计算过程。将数据的取值范围也分成2000份,准确地说,单位是段,整数数轴上的2000段,不同段之间没有交集。int32的取值范围是,一共4G个数,,也就是每段有2百万个数。利用桶排序的思想,一段就是一个桶子。各个桶子的取值范围依次是 ,...以此类推直到。假设1T数据放在一个大文件里,打开文件从中取数据。每个int32类型占4个字节,如果用0.5G字节内存加载数据,可以装个int32。每次从大文件中加载1,2500,0000个数据到内存中。根据数据所在的段对应到一个桶子,每个桶子形成一个小文件。每个小文件开头都要记录数据个数,所在段的开始值和结束值,以便于后续计算。所有数都处理完成后就得到了2000个小文件。
计算
拆分完成后,就要逐个把小文件加载到内存中处理了。因为1T字节的数据在int32取值范围上的分布未必是均匀的,所以可能某些小文件中数据较多。小文件中数据个数超过1,2500,0000个,就不能一次性载入内存,只能分批加载了。每一批至多载入1,2500,0000个数。因为一个小文件中的数的取值范围在整数数轴上是2百万个相邻的点,所以可以利用计数排序的思想。因为数据总量是1T字节,某个数出现的次数可能大于uint32最大值,所以要用uint64记录。利用一个2百万长度的uint64类型的数组做数据计数。,数组占用内存16M字节,内存依然绰绰有余(0.5G内存用于装数据,剩下的内存给计算使用)。因为文件开头记录了段的开始值,开始值加数组索引就是实际的数据值了。遍历小文件中的数,每个数都可以映射到数组的一个索引(数据值减去2百万),那个索引上的元素值+1(也就是数据个数+1),直到当前文件中的数都处理完。遍历数组找到当前文件的出现次数最多的数以及次数,与之前算出来的出现最多的数的次数(或者是初始次数0)比较,如果刚算出来的出现次数更多那就更新记录。继续处理下一个文件,直到所有小文件都处理完,就得到了需要的结果。
补充
上面分析过程假定只用0.5G内存,实际上我们可以使用更多的内存来加载数据,只要给进程和计算预留足够的内存即可。
注意看清题目是1T个数还是1T字节的数据量,要根据题目要求做分析。
优化
-
优化拆分
可以用双线程,一个线程C(消费者)负责计算,一个线程P(生产者)负责读文件。P维护一个队列,队列中每个item都是一份至多为0.2G字节的数据。P读大文件,每次读0.2G字节,然后放入队列尾部。队列最大长度限制为3,即最多保持字节的数据在队列中。C每次从队列头拿走一个item去处理,P再加载生成一个item,装满队列。那么内存中最多保持字节的数据。 -
优化计算
如果CPU有多核,在控制好内存占用的前提下,可以使用多线程。一个管理者线程M(生产者),多个工作线程W(消费者)。M不断地将未处理的文件分配给空闲的W,W处理完之后将结果通知给M。这样做,多个文件就可以并行处理了。