使用Python在2M内存中排序一百万个32位整数

  有人开玩笑地问我 如何使用python在2M内存中排序百万个32位整数.为了应付这个挑战,我学习了下缓冲I/O.

  很 明显,这是个开玩笑问题.假设是 2进制编码,单单是数据就已经占了4M!唯解释就是: 给定个包含百万个32位整数文件,你如何使用最少内存去排序好它们?这可能需要使用某种合并排序方式,把数据分块在内存排序,并保存到临时文件中 去,最后把临时文件合并获得最终结果.

  下面是我解决方案,稍候会解释.

  注意:所有例子都是使用python3.0. 这个例子区别的处就是使用file.buffer访问 2进制流方式去访问流文件.

#!/usr/bin/env python3.0
import sys, .gif' />, tempfile, heapq
assert .gif' />..gif' />('i').itemsize 4
def sfromfile(f):
 while True:
  a = .gif' />..gif' />('i')
  a.from(f.read(4000))
   not a:
    
  for x in a:
    yield x
iters =
while True:
 a = .gif' />..gif' />('i')
 a.from(sys.stdin.buffer.read(40000))
  not a:
   
 f = tempfile.TemporaryFile
 .gif' />..gif' />('i', sorted(a)).tofile(f)
 f.seek(0)
 iters.append(sfromfile(f))
a = .gif' />..gif' />('i')
for x in heapq.merge(*iters):
 a.append(x)
  len(a) >= 1000:
   a.tofile(sys.stdout.buffer)
   del a[:]
a:
 a.tofile(sys.stdout.buffer)
  在 我Goole桌面(个使用了3年,跑着Googlied Linux个人电脑,用Python3.0pystones测试得分是34000),在输入文件包含百万个32位随机整数情况下,这个大概花 了5.4秒.还不太差,如果直接在内存中排序大概需要2秒:

#!/usr/bin/env python3.0
import sys, .gif' />
a = .gif' />..gif' />('i', sys.stdin.buffer.read)
a = list(a)
a.sort
a = .gif' />..gif' />('i', a)
a.tofile(sys.stdout.buffer)
  回到合并排序方案.头 3行非常明显:

#!/usr/bin/env python3.0
import sys, .gif' />, tempfile, heapq
assert .gif' />..gif' />('i').itemsize 4
  第行表示我们使用Python3.0.第 2行引入将使用模块.第 3行检查到64位系统时中断,64位系统中'i'类型值不是表示32位整数;这里我没有考虑代码移植性.

  然后我们定义个辅助,它是个从文件读入整数生成器:

def sfromfile(f):
 while True:
   a = .gif' />..gif' />('i')
   a.from(f.read(4000))
    not a:
     
   for x in a:
     yield x
  这 个地方已经调优了算法性能: 它每次读入1000个整数,然后个个yield.我原来没有用到缓冲 -- 它就每次只从文件读入4个字节,转换成整数,然后yield.但跑起来就慢了4倍!要注意是我们不能使用a.fromfile(f,1000) fromfile思路方法在文件没有足够情况下会出错,并且我想要代码能自动适应文件中整数.(我原先设想大概会写入10,000个整数到个临 时文件中.)

  接着我们进入循环.反复地从输入文件读入10,000个整数,在内存中排序,然后写入临时文件.我们为临时文件增加个迭代器,通过上面sfromfile,使其成为迭代器中个列表,以备在随后合并阶段使用.

iters =
while True:
 a = .gif' />..gif' />('i')
 a.from(sys.stdin.buffer.read(40000))
  not a:
   
 f = tempfile.TemporaryFile
 .gif' />..gif' />('i', sorted(a)).tofile(f)
 f.seek(0)
 iters.append(sfromfile(f))
  要注意是,为了应对包含百万个值输入,将会创建100个包含10,000个值临时文件.



  最后我们合并这些文件(每个都已经排序). heapq模块有个非常实用: heapq.merge(iter1, iter2, ...)它把多个已排序输入值(如本例中)合并排序返回个有序迭代器

a = .gif' />..gif' />('i')
for x in heapq.merge(*iters):
 a.append(x)
  len(a) >= 1000:
   a.tofile(sys.stdout.buffer)
   del a[:]
a:
 a.tofile(sys.stdout.buffer)


  这里也是个必不可少使用缓冲I/O地方:当个值可用时候就写入文件,这样会成倍地减慢算法速度.因此,通过简单地增加输入输出缓冲,可以获得上十倍速度提升!

  这就是主要思路了.

  另外我们还能体验到heapq模块强大它包含了在输出阶段需要合并迭代器功能当然.gif' />模块管理 2进制数据便利也是令人印象深刻

  最后,通过这个例子让你们知道,Python 3.0相对于Python2.5来说差别并不是很大!



Tags:  32位机 32位系统 32位游戏

延伸阅读

最新评论

发表评论