Python 为什么A.issuperset(B)比所有的(A中的B代表B中的B)都慢得多?

Python 为什么A.issuperset(B)比所有的(A中的B代表B中的B)都慢得多?,python,performance,set,python-internals,Python,Performance,Set,Python Internals,考虑测试一个集合a是否是iterableB的超集,一次使用集合的方法,一次使用我自己的表达式,使用超集的定义: >>> A = set(range(1000)) >>> B = range(-1000, 0) >>> A.issuperset(B) False >>> all(b in A for b in B) False 现在我们来计时: >>> from timeit import timeit &

考虑测试一个集合
a
是否是iterable
B
的超集,一次使用集合的方法,一次使用我自己的表达式,使用超集的定义:

>>> A = set(range(1000))
>>> B = range(-1000, 0)
>>> A.issuperset(B)
False
>>> all(b in A for b in B)
False
现在我们来计时:

>>> from timeit import timeit
>>> timeit(lambda: A.issuperset(B))
52.666367300000005
>>> timeit(lambda: all(b in A for b in B))
0.9698789999999917
集合自身的方法要慢得多。为什么?大概它可以/应该做同样的事情,但是在C速度下,所以应该更快


我使用的是CPython 3.8.1。

区别似乎来自这样一个事实:在您的测试中,您实际上并没有在两个集合之间运行
issuperset
,而是在一个集合和一个范围之间运行。大部分时间用于将范围转换为集合。考虑以下时间:

A = set(range(1000))
B_set = set(range(-1000, 0))
B_range = range(-1000, 0)
B_list = list(range(-1000, 0))

%%timeit 
A.issuperset(B_set)
654 ns ± 6.09 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%%timeit 
A.issuperset(B_range)
29.9 µs ± 259 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%%timeit 
A.issuperset(B_list)
15.4 µs ± 233 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)


# Creating a set from a range. 
%%timeit
B_set = set(B_range)
29.2 µs ± 209 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

%%timeit 
all(b in A for b in B_set)
816 ns ± 16.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%%timeit 
all(b in A for b in B_range)
474 ns ± 4.74 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
set.issubset
set.issubset
的组坚持首先构建参数集:

static PyObject *
set_issubset(PySetObject *so, PyObject *other)
{
    setentry *entry;
    Py_ssize_t pos = 0;
    int rv;

    if (!PyAnySet_Check(other)) {
        PyObject *tmp, *result;
        tmp = make_new_set(&PySet_Type, other);
        if (tmp == NULL)
            return NULL;
        result = set_issubset(so, tmp);
        Py_DECREF(tmp);
        return result;
    }
    if (PySet_GET_SIZE(so) > PySet_GET_SIZE(other))
        Py_RETURN_FALSE;

    while (set_next(so, &pos, &entry)) {
        rv = set_contains_entry((PySetObject *)other, entry->key, entry->hash);
        if (rv < 0)
            return NULL;
        if (!rv)
            Py_RETURN_FALSE;
    }
    Py_RETURN_TRUE;
}

PyDoc_STRVAR(issubset_doc, "Report whether another set contains this set.");

static PyObject *
set_issuperset(PySetObject *so, PyObject *other)
{
    PyObject *tmp, *result;

    if (!PyAnySet_Check(other)) {
        tmp = make_new_set(&PySet_Type, other);
        if (tmp == NULL)
            return NULL;
        result = set_issuperset(so, tmp);
        Py_DECREF(tmp);
        return result;
    }
    return set_issubset((PySetObject *)other, (PyObject *)so);
}

PyDoc_STRVAR(issuperset_doc, "Report whether this set contains another set.");
静态PyObject*
set_issubset(PySetObject*so,PyObject*other)
{
setentry*条目;
Py_ssize_t pos=0;
int-rv;
如果(!PyAnySet_Check(其他)){
PyObject*tmp,*结果;
tmp=创建新集合(&PySet类型,其他);
if(tmp==NULL)
返回NULL;
结果=设置(so,tmp);
Py_DECREF(tmp);
返回结果;
}
if(PySet_GET_SIZE(so)>PySet_GET_SIZE(其他))
Py_RETURN_FALSE;
while(设置_next(so、pos和entry)){
rv=set_包含_条目((PySetObject*)其他,条目->键,条目->散列);
如果(rv<0)
返回NULL;
如果(!rv)
Py_RETURN_FALSE;
}
Py_返回真;
}
PyDoc_STRVAR(issubset_doc,“报告另一个集合是否包含此集合”);
静态PyObject*
set_issuperset(PySetObject*so,PyObject*other)
{
PyObject*tmp,*结果;
如果(!PyAnySet_Check(其他)){
tmp=创建新集合(&PySet类型,其他);
if(tmp==NULL)
返回NULL;
结果=设置发布时间(so,tmp);
Py_DECREF(tmp);
返回结果;
}
返回集合_issubset((PySetObject*)other,(PyObject*)so);
}
PyDoc_STRVAR(issuperset_doc,“报告此集合是否包含其他集合”);
issupSet
对参数进行设置,然后调用
other.issubset(self)
。(
issubset
也坚持将一个集合作为其参数,但它得到了一个集合,因此在本例中不需要转换。)他们可以相当容易地向
issubset
添加一个代码路径来处理未经集合转换的非集合参数,但他们没有这样做


我怀疑这样做的原因可能是在调用
{1}.issupertet([2,[3]])
时抛出错误,其中参数包含不可损坏的元素。然而,也可能没有人费心优化它。搜索
issupertt
会发现0个关于优化
issupertt
的问题,甚至没有解决问题。令人惊讶的是,
issubset
有一个更难的优化,但是尽管它会导致类似的异常行为变化,但问题的回复中没有任何一个提到这一点。

issupertet
短路,请参见此处的实现:关于可能的原因:可能,尽管“测试其他元素中的每个元素是否都在集合中”,并且其他元素可以是“任意可编辑的”“,我看不到任何关于散列元素的信息。这种
issubset
优化似乎不仅更加困难,而且空间效率也更低,因为它可以跟踪iterable中已经遇到的集合元素。对于
issueperset
,这是不必要的。我认为我们可以排除散列性的原因,因为相关的
{1}.isdisjoint([1,[2]])
没有抱怨,只返回
False