为什么Python2.7中的dict定义比Python3.x更快?

为什么Python2.7中的dict定义比Python3.x更快?,python,python-2.7,python-3.x,dictionary,python-internals,Python,Python 2.7,Python 3.x,Dictionary,Python Internals,我遇到了一种(并非非常罕见)情况,在这种情况下,我必须使用map()或列表理解表达式。然后我想知道哪一个更快 StackOverflow answer为我提供了解决方案,但随后我开始自己测试它。结果基本上是一样的,但我在切换到Python 3时发现了一个我很好奇的意外行为,即: λ iulian-pc ~ → python --version Python 2.7.6 λ iulian-pc ~ → python3 --version Python 3.4.3 λ iulian-pc ~ →

我遇到了一种(并非非常罕见)情况,在这种情况下,我必须使用
map()
或列表理解表达式。然后我想知道哪一个更快

StackOverflow answer为我提供了解决方案,但随后我开始自己测试它。结果基本上是一样的,但我在切换到Python 3时发现了一个我很好奇的意外行为,即:

λ iulian-pc ~ → python --version
Python 2.7.6
λ iulian-pc ~ → python3 --version
Python 3.4.3

λ iulian-pc ~ → python -mtimeit '{}'                                                     
10000000 loops, best of 3: 0.0306 usec per loop
λ iulian-pc ~ → python3 -mtimeit '{}'                
10000000 loops, best of 3: 0.105 usec per loop

λ iulian-pc ~ → python -mtimeit 'dict()'
10000000 loops, best of 3: 0.103 usec per loop
λ iulian-pc ~ → python3 -mtimeit 'dict()'
10000000 loops, best of 3: 0.165 usec per loop
我曾假设Python3比Python2快,但在几篇文章(,)中发现情况并非如此。然后我想也许Python 3.5会在这样一个简单的任务中表现得更好,就像他们在
自述文件中所说的那样:

语言基本相同,但有很多细节,尤其是如何表达 像字典和字符串这样的内置对象已经改变了 相当明显,许多不推荐的特性最终都被使用了 删除

但不,它的表现更糟:

λ iulian-pc ~ → python3 --version
Python 3.5.0

λ iulian-pc ~ → python3 -mtimeit '{}'       
10000000 loops, best of 3: 0.144 usec per loop
λ iulian-pc ~ → python3 -mtimeit 'dict()'
1000000 loops, best of 3: 0.217 usec per loop
我曾尝试深入研究dict的Python 3.5源代码,但我对C语言的了解还不足以自己找到答案(或者,我甚至没有在正确的位置进行搜索)

所以,我的问题是:
是什么使得较新版本的Python在一个相对简单的任务(如
dict
定义)上比较旧版本的Python慢,根据常识,反之亦然?我知道,这些差异非常小,在大多数情况下可以忽略。这只是一个让我好奇的观察结果,为什么时间增加了,但至少没有保持不变?

因为没有人关心

您所引用的差异大约为数十或数百纳秒。C编译器优化寄存器使用的方式稍有不同,就很容易导致这样的更改(其他C级优化差异也可能如此)。反过来,这可能是由许多因素引起的,例如Python的C实现(CPython)中局部变量的数量和用法的变化,甚至只是切换C编译器


事实上,没有人在积极地优化这些微小的差异,所以没有人能够给你一个具体的答案。CPython的设计并非绝对意义上的快速。它被设计成可伸缩的。例如,您可以将数百或数千个条目放入字典,它将继续运行良好。但创建字典的绝对速度并不是Python实现者最关心的问题,至少在差异如此之小的情况下是如此。

正如@Kevin所说:

CPython的设计并非绝对意义上的快速。它是 设计为可扩展的

请尝试以下方法:

$ python -mtimeit "dict([(2,3)]*10000000)"
10 loops, best of 3: 512 msec per loop
$
$ python3 -mtimeit "dict([(2,3)]*10000000)"
10 loops, best of 3: 502 msec per loop
再说一遍:

$ python -mtimeit "dict([(2,3)]*100000000)"
10 loops, best of 3: 5.19 sec per loop
$
$ python3 -mtimeit "dict([(2,3)]*100000000)"
10 loops, best of 3: 5.07 sec per loop
这很清楚地表明,在如此微不足道的差异上,你不能将Python3作为输给Python2的基准。从外观上看,Python3应该扩展得更好。

让我们
{}

>>> from dis import dis
>>> dis(lambda: {})
  1           0 BUILD_MAP                0
              3 RETURN_VALUE

这是多一点的代码

编辑: Python3.4构建映射id的实现与2.7完全相同(感谢@user2357112)。我挖得更深,看起来Python 3分钟的dict大小是8

PyDict_MINSIZE_COMBINED是任何新的非拆分dict的起始大小。8允许dict具有不超过5个活动条目;实验表明,这对于大多数dict(主要由为传递关键字参数而创建的通常较小的dict组成)来说已经足够了。将其设为8,而不是4,可以减少大多数词典的大小调整次数,而不会占用大量额外内存

在这两种情况下,
minused
的值均为1


Python 2.7创建一个空的dict,Python 3.4创建一个7元素的dict。

谢谢您的回答,Kevin。我知道这些差异很小,但是你知道为什么Python3.x中的时间增加了而没有保持不变吗?@iulian:没有人试图让它保持不变,所以它增加了也就不足为奇了。如果它下跌,也同样不足为奇。在2.7和3.5之间有很多内容发生了变化。这两个版本之间的差异非常大,几乎可以肯定,这不是由于编译器分配的局部变量或寄存器的数量造成的。Dict对于python的内部结构来说是非常基本的,它包含了大量的特例优化。差异可能是因为实现中的更改。@pvg:我提到了实现中的更改:“这反过来可能是由许多事情引起的,例如Python的C实现中局部变量的数量和用法的更改……”这是一个实现更改。@Kevin我认为这在技术上是正确的(最好的一种是正确的!)但是我认为你的回答的要点误解了时差和实现变化的规模。这并没有发生,因为他们使用了更多的局部变量。两个循环都没有运行,因为堆栈上没有参数。3.x代码与2.x代码没有实质性的区别,除了一些额外的未使用的变量nches和一个
PUSH(map);DISPATCH();
@Kevin是的,但3.x代码有分支,必须分析何时执行和何时不执行。两个额外的高度可预测的分支似乎不会导致观察到的效果。对于
{}
case,它需要3倍的时间!所有分支和已涉及的分配中的另外两个分支不会这样做。对于
dict()
case,这些代码甚至都不会运行,但绝对时间的增加与
{}差不多
案例。要找到经济放缓的实际根源,你需要深入挖掘。我的假设是,这一过程中涉及的额外分配与差异有很大关系——例如,现在有一个
PyDictKeysObject
,它通过
PyMem_MALLOC
单独分配,而不是通过自由列表分配,但要分配给r如果要确认这一点,我们必须合作
TARGET(BUILD_MAP)
{
    x = _PyDict_NewPresized((Py_ssize_t)oparg);
    PUSH(x);
    if (x != NULL) DISPATCH();
    break;
}
TARGET(BUILD_MAP) {
    int i;
    PyObject *map = _PyDict_NewPresized((Py_ssize_t)oparg);
    if (map == NULL)
        goto error;
    for (i = oparg; i > 0; i--) {
        int err;
        PyObject *key = PEEK(2*i);
        PyObject *value = PEEK(2*i - 1);
        err = PyDict_SetItem(map, key, value);
        if (err != 0) {
            Py_DECREF(map);
            goto error;
        }
    }

    while (oparg--) {
        Py_DECREF(POP());
        Py_DECREF(POP());
    }
    PUSH(map);
    DISPATCH();
}
PyObject *
_PyDict_NewPresized(Py_ssize_t minused)
{
    Py_ssize_t newsize;
    PyDictKeysObject *new_keys;
    for (newsize = PyDict_MINSIZE_COMBINED;
         newsize <= minused && newsize > 0;
         newsize <<= 1)
        ;
    new_keys = new_keys_object(newsize);
    if (new_keys == NULL)
        return NULL;
    return new_dict(new_keys, NULL);
}
PyObject *
_PyDict_NewPresized(Py_ssize_t minused)
{
    PyObject *op = PyDict_New();

    if (minused>5 && op != NULL && dictresize((PyDictObject *)op, minused) == -1) {
        Py_DECREF(op);
        return NULL;
    }
    return op;
}