【问题标题】:Performance of small sets in PythonPython中小集合的性能
【发布时间】:2012-06-16 22:34:23
【问题描述】:

我正在寻找最有效的方法来表示 Python 中给定范围(例如 0-10)内的一小组整数。在这种情况下,效率意味着快速构建(从未排序的列表中)、快速查询(每组几个查询)和合理快速构建排序版本(可能每十组左右一次)。先验的候选人正在使用 Python 的内置集合类型(快速查询),使用排序数组(可能更快地构造?),或使用位数组(如果我在 C 中,一切都快......但我怀疑 Python 会高效(?))。有什么建议可以选择吗?

谢谢。

【问题讨论】:

  • 任何只有 10 个元素的东西都会足够快。
  • 如果我想构建数百万个它们怎么办? :-)
  • 一亿个项目,如果它们是整数,则为 400mb,一些开销,你有大约 500mb 的内存。信封背面计算表明内置集合应该没问题
  • 我的建议是保持简单,直到您发现您的解决方案不够快。不要过早优化。

标签: python list set bitarray


【解决方案1】:

我会使用位图并将“集合”的成员存储在 int... 在这种情况下,这实际上可能比内置的 set 类型更快——尽管我没有测试了。它肯定需要更少的存储空间。

更新

我现在没有时间进行类似完整的实现并将其与 Python 的内置类进行基准测试,但我认为这是一个说明我的建议的工作示例。正如我认为你会同意的那样,代码看起来相当快并且内存效率很高。

鉴于 Python 几乎透明的“无限”长整数功能,所编写的内容将自动处理比您需要的范围大得多的整数值,尽管这样做可能会减慢速度。 ;)

class BitSet(object):
    def __init__(self, *bitlist):
        self._bitmap = 0
        for bitnum in bitlist:
            self._bitmap |= (1 << bitnum)

    def add(self, bitnum):
        self._bitmap |= (1 << bitnum)

    def remove(self, bitnum):
        if self._bitmap & (1 << bitnum):
            self._bitmap &= ~(1 << bitnum)
        else:
            raise KeyError

    def discard(self, bitnum):
       self._bitmap &= ~(1 << bitnum)

    def clear(self):
        self._bitmap = 0

    def __contains__(self, bitnum):
        return bool(self._bitmap & (1 << bitnum))

    def __int__(self):
        return self._bitmap

if __name__ == '__main__':

    bs = BitSet()

    print '28 in bs:', 28 in bs
    print 'bs.add(28)'
    bs.add(28)
    print '28 in bs:', 28 in bs

    print
    print '5 in bs:', 5 in bs
    print 'bs.add(5)'
    bs.add(5)
    print '5 in bs:', 5 in bs

    print
    print 'bs.remove(28)'
    bs.remove(28)
    print '28 in bs:', 28 in bs

【讨论】:

    【解决方案2】:

    在这种情况下,您可能只使用 True/False 值列表。 set 使用的哈希表将做同样的事情,但它会包含哈希、桶分配和冲突检测的开销。

    myset = [False] * 11
    for i in values:
        myset[i] = True
    mysorted = [i for i in range(11) if myset[i]]
    

    与往常一样,您需要自己安排时间,以了解它在您的情况下是如何运作的。

    【讨论】:

      【解决方案3】:

      我的建议是坚持使用内置的set()。编写性能优于内置 C 代码的 Python 代码将非常困难。如果依赖内置的 C 代码,构建速度和查找速度将是最快的。

      对于排序列表,最好的办法是使用内置排序功能:

      x = set(seq) # build set from some sequence
      lst = sorted(x)  # get sorted list from set
      

      一般来说,在 Python 中,编写的代码越少,速度就越快。您越能依赖 Python 的内置 C 基础,速度就越快。在许多情况下,解释型 Python 比 C 代码慢 20 到 100 倍,而且很难做到聪明到让您领先一步,而不是按预期使用内置功能。

      如果保证您的集合始终是 [0, 10] 范围内的整数,并且您希望确保内存占用尽可能小,那么整数内的位标志将是可行的方法.

      pow2 = [2**i for i in range(32)]
      
      x = 0  # set with no values
      def add_to_int_set(x, n):
          return x | pow2[n]
      
      def in_int_set(x, n):
          return x & pow2[n]
      
      def list_from_int_set(x):
          return [i for i in range(32) if x & pow2[i]]
      

      我敢打赌,这实际上比使用内置 set() 函数要慢,但您知道每个集合只是一个 int 对象:4 个字节,加上 Python 对象的开销。

      如果您确实需要数十亿个,您可以使用 NumPy array 而不是 Python 列表来节省空间; NumPy array 将只存储裸整数。事实上,NumPy 有一个 16 位整数类型,所以如果你的集合真的只在 [0, 10] 的范围内,你可以使用 NumPy array 将存储大小减少到每个字节。

      http://www.scipy.org/FAQ#head-16a621f03792969969e44df8a9eb360918ce9613

      【讨论】:

      • 请注意,set 未排序,尽管它可能看起来是针对某些输入的。
      • A set 不需要排序。如果你想对set 中的元素进行排序list,我已经展示了如何使用sorted() 做到这一点。
      • 在您添加有关生成排序列表的部分之前,我发表了评论。此外,如果他们看到一些典型的例子,有些人可能会误以为set已经排序。
      【解决方案4】:

      即使对于小型集合,“包含”检查使用集合的速度也会更快。

      >>> Timer("3 in values", 'values = [range(10)]').timeit(number = 10**7)
      0.5200109481811523
      >>> Timer("3 in values", 'values = set(range(10))').timeit(number = 10**7)
      0.2755239009857178
      

      另一方面,正如您所指出的,构建一个集合需要更长的时间。

      >>> Timer("set(range(10))").timeit(number = 10**7)
      5.87517786026001
      >>> Timer("list(range(10))").timeit(number = 10**7)
      4.129410028457642
      

      排序的时候也有一些区别:

      >>> Timer("sorted(values)", 'values = set(range(10, 0, -1))').timeit(number = 10**7)
      5.277467966079712
      >>> Timer("sorted(values)", 'values = list(range(10, 0, -1))').timeit(number = 10**7)
      4.3836448192596436
      >>> Timer("values.sort()", 'values = list(range(10, 0, -1))').timeit(number = 10**7)
      2.073429822921753
      

      就地排序明显更快,并且仅适用于列表。

      因此,如果您只对每个集合执行少量查询,则列表的性能会更高。在进行大量查询时,我会使用集合。
      无论哪种情况,小集合之间的差异都很小。

      不建议在 Python 中构建自己的集合类型以获得更好的性能。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-01-25
        • 1970-01-01
        • 2012-07-15
        • 2017-04-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多