预排序可以改善itertools.combinations的表现吗?

时间:2015-05-25 09:10:45

标签: python python-3.x benchmarking

我目前正在研究大数据,这项工作的一个重要步骤是获得数千个字符串的所有已排序组合。

在python 3.4中,我尝试了三种方法来执行此操作。

首先,将数据伪造成字符串列表,有或没有GO术语:

# -*- coding: utf-8 -*-
import random as rd

# With GO term
# data = ["GO:" + str(i) for i in range(0, 10000)]
# Xor, without GO term
data = [str(i) for i in range(0, 10000)]

rd.shuffle(data)
print("shuffle done")

GO术语只是添加到所有数据字符串开头的常量字符串。有和没有GO术语的结果将随之而来。

现在,执行基准测试:

from itertools import combinations, product

def test1(): # use condition
    g = ((i,j) if (i < j) else (j,i) for i, j in combinations(data, 2))
    print(len([(i, j) for i, j in g]))


def test2(): # use filter
    g = ((i, j) for i,j in product(data, data) if (i < j))
    print(len([(i, j) for i, j in g]))


def test3(): # sort before combine
    g = ((i,j) for i, j in combinations(sorted(data), 2))
    print(len([(i, j) for i, j in g]))


import timeit

print(timeit.timeit(stmt=test1, number=3))
print(timeit.timeit(stmt=test2, number=3))
print(timeit.timeit(stmt=test3, number=3))

使用GO术语输出:

49995000
49995000
49995000
23.490827083587646
49995000
49995000
49995000
31.04393219947815
49995000
49995000
49995000
16.878661155700684

没有GO术语的输出:

49995000
49995000
49995000
22.99237084388733
49995000
49995000
49995000
29.025460958480835
49995000
49995000
49995000
16.596422910690308

每次通话打印的49995000证明我们所有方法都会产生相同数量的数据。 现在的问题是:

  1. 与第二种方法相比,为什么第一种方法如此之快?使用过滤器是我用于过滤数据的主要方式。到目前为止,我认为过滤器形式已经过大量优化。
  2. 为什么第三种方法似乎更好?排序是O(N * log(N)),它似乎在大型列表上看起来很昂贵吗?
  3. 为什么GO术语对第一种和第二种方法的影响要大于第三种方法?
  4. 我的第一个猜测是排序+组合产生的数据比较少得多,而另外两种方法对每对字符串进行比较,但是,由于排序听起来像是一个繁重的操作,我不完全确定

2 个答案:

答案 0 :(得分:1)

  1. 真的不是一个大惊喜,看到combinations仅创建i<j成立的组合,而product创建所有组合,包括i<j不为真的组合if (i < j) else (j,i)中的test1是多余的。在test1中省略此检查会大大缩短执行时间,如下所示。
  2. i<j签入test1

    shuffle done
    49995000
    49995000
    49995000
    31.66194307899991
    49995000
    49995000
    49995000
    37.66488860800018
    49995000
    49995000
    49995000
    22.706632076000005
    

    没有i<j签入test1

    shuffle done
    49995000
    49995000
    49995000
    25.07709688900013
    49995000
    49995000
    49995000
    39.405620851000094
    49995000
    49995000
    49995000
    23.54182383899979
    

答案 1 :(得分:1)

我非常确定您所观察到的性能差异的重要贡献在于检查if (i < j) 49995000次与排序10000个元素的列表,而不是假定的排序与未排序的可迭代。

combinations应该在两种情况下都做同样的工作,因为它们产生相同数量的元素,并且不对元素进行排序并按字典顺序返回它们。

要正确测试排序是否有所不同:

  1. 使用相同的数据集执行相同的条件检查,但已排序且未排序:

    sorted_data = sorted(data)
    
    def test1():
        g = ((i,j) if (i < j) else (j,i) for i, j in combinations(sorted_data, 2))
        return len([(i, j) for i, j in g])
    
    def test2():
        g = ((i,j) if (i < j) else (j,i) for i, j in combinations(data, 2))
        return len([(i, j) for i, j in g])
    
    %timeit test1()
    1 loops, best of 3: 23.5 s per loop
    
    %timeit test2()
    1 loops, best of 3: 24.6 s per loop
    
  2. 在没有条件的情况下执行测试:

    def test3():
        g = ((i,j) for i, j in combinations(sorted_data, 2))
        return len([(i, j) for i, j in g])
    
    def test4():
        g = ((i,j) for i, j in combinations(data, 2))
        return len([(i, j) for i, j in g])
    
    %timeit test3()
    1 loops, best of 3: 20.7 s per loop
    
    %timeit test4()
    1 loops, best of 3: 21.3 s per loop
    
  3.   

    为什么第一种方法与第二种方法相比如此之快?使用过滤器是我用于过滤数据的主要方式。到目前为止,我认为过滤器形式已经过大量优化。

    使用组合产生的元素少于检查条件的元素。 10000C2 = 49995000用于产品的组合与10000**2 = 100000000

      

    为什么GO术语对第一种和第二种方法的影响大于第三种方法?

    第一种和第二种方法受到用于比较49995000和100000000次的附加字符的影响。第三个仅受到对10000个项目进行排序所需的比较的影响。

    经过一些摆弄后,似乎排序可能会产生一些差异,但不会像使用条件那样大。不知道是什么原因引起的。

    from itertools import combinations
    import random as rd
    
    data = ["{0:04d}".format(i) for i in range(0, 10000)] # Normalize str length
    rd.shuffle(data)
    
    sorted_data = sorted(data)
    reversed_sorted_data = sorted_data[::-1]
    
    def test1():
        g = list((i,j) if (i < j) else (j,i) for i, j in combinations(data, 2))
        print('unsorted with conditional: ', len(g))
    
    %timeit test1()
    # unsorted with conditional:  49995000
    # unsorted with conditional:  49995000
    # unsorted with conditional:  49995000
    # unsorted with conditional:  49995000
    # 1 loops, best of 3: 20.7 s per loop
    
    def test2():
        g = list((i,j) if (i < j) else (j,i) for i, j in combinations(sorted_data, 2))
        print('sorted with conditional: ', len(g))
    
    %timeit test2()
    # sorted with conditional:  49995000
    # sorted with conditional:  49995000
    # sorted with conditional:  49995000
    # sorted with conditional:  49995000
    # 1 loops, best of 3: 19.6 s per loop
    
    def test3():
        g = list((i,j) for i, j in combinations(data, 2))
        print('unsorted without conditional: ', len(g))
        
    %timeit test3()
    # unsorted without conditional:  49995000
    # unsorted without conditional:  49995000
    # unsorted without conditional:  49995000
    # unsorted without conditional:  49995000
    # 1 loops, best of 3: 15.7 s per loop
    
    def test4():
        g = list((i,j) for i, j in combinations(sorted_data, 2))
        print('sorted without conditional: ', len(g))
    
    %timeit test4()
    # sorted without conditional:  49995000
    # sorted without conditional:  49995000
    # sorted without conditional:  49995000
    # sorted without conditional:  49995000
    # 1 loops, best of 3: 15.3 s per loop
    
    def test5():
        g = list((i,j) for i, j in combinations(reversed_sorted_data, 2))
        print('reverse sorted without conditional: ', len(g))
        
    %timeit test5()
    # reverse sorted without conditional:  49995000
    # reverse sorted without conditional:  49995000
    # reverse sorted without conditional:  49995000
    # reverse sorted without conditional:  49995000
    # 1 loops, best of 3: 15 s per loop