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

答案为我提供了解决方案,但后来我开始自己测试.基本上,结果是一样的,但我在切换到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快,但在几篇文章(12)中发现情况并非如此.然后我想也许Python 3.5会在这样一个简单的任务中表现得更好,正如他们在README中所说:

语言基本相同,但有很多细节,尤其是如何表达

但没有,它的表现更糟:

λ 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

我try 深入研究Python 3.5源代码已经dict年了,但我对C语言的知识不足以自己找到答案(或者,我甚至可能没有在正确的地方搜索).

所以,我的问题是:

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

推荐答案

让我们disassemble {}:

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

Python 2.7 implementation of BUILD_MAP

TARGET(BUILD_MAP)
{
    x = _PyDict_NewPresized((Py_ssize_t)oparg);
    PUSH(x);
    if (x != NULL) DISPATCH();
    break;
}

Python 3.5 implementation of BUILD_MAP

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();
}

这是更多的代码.

EDIT:

Python 3.4实现的BUILD_MAP id与2.7完全相同(谢谢@user2357112).我挖得更深,看起来Python 3分钟的dict大小是8PyDict_MINSIZE_COMBINED const

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

看看_PyDict_NewPresized in Python 3.4

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);
}

2.7年后

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;
}

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

Python2.7创建一个空的dict,Python3.4创建一个7元素的dict.

Python-3.x相关问答推荐

Numpy将3D数组的每个切片相乘以进行转置并对其求和

将strid()映射到Pandas DataFrame中的字符串不会更改NaN条目,但仍然声称它们不同?

Python多处理池:缺少一个进程

将f-字符串放置在f-字符串内

动态范围内来自另外两列的列求和

为什么空列表也能起作用?

在REPLACE INTO中引用变量会抛出sqlite3.OperationalError

如何将日期时间索引写入日期类型的表?

如何将 OLS 趋势线添加到使用 updatemenus 显示数据子集的 plotly 散点图图形对象?

Pandas matplotlib:条形图占总数的百分比

Python Regex 查找给定字符串是否遵循交替元音、辅音或辅音、元音的连续模式

从 Python2 到 Python3 的这种解包行为的变化是什么?

创建一个可旋转的 3D 地球

TensorFlow:dataset.train.next_batch 是如何定义的?

pandas 中 df.reindex() 和 df.set_index() 方法的区别

Python中的依赖倒置

混合全局/参数和名为top的函数的奇怪python行为

将 numpy.float64 列表快速转换为 Python 中的浮点数

Python 3 中的连接列表

finally 总是在 try 块返回之前运行,那么为什么 finally 块中的更新不会影响 try 块返回的变量的值呢?