从140秒到2秒的优化

本文记录了一项从140秒优化到2秒的任务,通过对2亿个数字进行非重复计数。作者从bitarray出发,逐步引入多进程并最终采用NumPy和C扩展,实现了显著的性能提升。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

from http://www.tech-q.cn/viewthread.php?tid=2123

 

[PYTHON] 从140秒到2秒的优化

从2亿个0~2,000,000,000之间的数字样本中找出不重复的记录总数,首先想到的是bloom filter,转念一想既然全都是数字,bloom filter有点太重,bitarray也许更有效,于是第一个版本出来,部分代码如下:
  1. ba = bitarray(212**4)
  2. cnt = 0
  3. for i in data:
  4.     if (not ba[i]):
  5.         cnt += 1
  6.         ba[i] = True
  7. print cnt
复制代码
大概需要140s左右,觉得if (not ba):这个比较费,改了第二版:
  1. for i in data:
  2.     ba[i] = True
  3. print ba.count()
复制代码
速度有所提升,到了120s左右,开始打起多核运算的主意了,山寨了一个map-reduce,首先通过maper把数据按照除4得余分成4份:
  1. def maper(data):
  2.     map_data = (array('I'),array('I'),array('I'),array('I'))
  3.     for i in data:
  4.         m = i % 4
  5.         map_data[m].append(i)
  6.     return map_data
  7. 然后起了一个4个进程的woker pool分别计算,最后把结果汇总:
  8. def worker(data):
  9.     counter = bitarray(256**4)
  10.     for i in data:counter[i] = True
  11.     return counter.count()
  12. p = Pool(4)
  13. result = p.map(worker, data)
复制代码
速度提高明显,到了50s左右,这个做法的问题是两次遍历:map的时候一次、reduce的时候又一次,于是开始想办法解决,把文件直接分开运算,不再map,把最后的结果做一下位或再计数:
  1. p = Pool(4)
  2. result = p.map(worker, data)
  3. print (result[0] | result[1] | result[2] | result[3]).count()
复制代码
到了26s左右,可能Python在进程间交换大数据量效率不是太好,再优化的空间有限,想起之前用Python的科学运算库做过数据挖掘,能不能用那个库试试,于是有了NumPy的版本:
  1. import numpy as np
  2. print len(np.unique(np.fromfile('/path/to/data.dat', np.uint32)))
复制代码
全部程序就这两行,速度到了12s,让人崩溃,NumPy的底层大多是C的实现,对代码做了一个profile,发现NumPy用了sort,有点浪费,如果我用C实现一部分功能的话效果应该会不错,注意到代码中有for i in data,data中有2亿条,就循环调用了2亿次,尝试把这个调用都封装在C里面,使用C级别的循环,于是用C扩展了一下bitarray包:
  1. static PyObject *
  2. bitarray_fromarray(bitarrayobject *self, PyObject *pyo)
  3. {
  4.     unsigned int *l;
  5.     idx_t n1;
  6.     Py_ssize_t nbytes, nitems, i;
  7.     if (PyObject_AsReadBuffer(pyo, (const void **)&l, &nbytes) != 0)
  8.         return Py_False;
  9.     nitems = nbytes/sizeof(unsigned int);
  10.     for (i=0; i<nitems; i++) {
  11.         *(self->ob_item + l[i] / 8) |= ((char) 1) << (l[i])%8;
  12.     }
  13.     n1 = count(self);
  14.     return PyLong_FromLongLong(n1);
  15. }
复制代码
直接读取文件buffer到bitarray,python程序就变成了:
  1. from bitarray import bitarray
  2. counter = bitarray(212 ** 4)
  3. fp = open('/path/to/data.datbk', 'rb')
  4. un = counter.fromarray(fp.read())
  5. print un
复制代码
一共5行代码,速度到了2s内,收工。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值