基本数据类型(字符串和整数)是如何在Python和Perl中实现的

基本数据类型(字符串和整数)是如何在Python和Perl中实现的,python,perl,Python,Perl,我最近一直在想,我对基本类型(如字符串和整数)执行的各种操作在性能方面是如何工作的,我想如果我知道这些基本类型是如何实现的,我会对这一点有更好的了解(例如,我听说字符串和整数在Python中是不可变的。这是否意味着任何修改字符串中一个字符的操作都是O(n),因为必须创建一个全新的字符串?添加数字如何?) 我对Python和Perl中的这一点都很好奇,并且觉得问两次基本相同的问题很愚蠢,所以我只是把它包装成一个问题 如果你能在答案中加入一些操作成本的例子,那会更加有用。在python中,some_

我最近一直在想,我对基本类型(如字符串和整数)执行的各种操作在性能方面是如何工作的,我想如果我知道这些基本类型是如何实现的,我会对这一点有更好的了解(例如,我听说字符串和整数在Python中是不可变的。这是否意味着任何修改字符串中一个字符的操作都是O(n),因为必须创建一个全新的字符串?添加数字如何?)

我对Python和Perl中的这一点都很好奇,并且觉得问两次基本相同的问题很愚蠢,所以我只是把它包装成一个问题


如果你能在答案中加入一些操作成本的例子,那会更加有用。

在python中,
some_string[5]='a'
将是一个错误,但最接近的等效操作,
some_string=some_string[5::][a'+some_string[6:][/code>实际上是O(n)。但这不仅仅适用于不可变对象。串联列表也是如此:
[1,2,3]+[4,5,6]
生成一个新列表,并且是O(n)

添加数字会创建一个新的值,但通常结果值在内存中总是相同的大小,因此它是O(1)。当然,这只适用于较小的整数。一旦达到某个阈值(在我的机器上为20位),整数会突然占用可变的空间。我不知道这会如何影响渐进性能

然而,我发现即使在
log10(n)==1000附近,它似乎也没有显著的效果:

>>> times = [timeit.timeit(stmt=stmt.format(10 ** i, 10 ** i), number=100) for i in range(1000)]
>>> sum(times) * 1.0 / len(times)
3.0851364135742186e-06
>>> times[-1]
3.0994415283203125e-06
对于字符串,渐近性能影响更为明显:

>>> stmt = 's[:5] + "a" + s[6:]'
>>> setup = 's = "b" * {0}'
>>> times = [timeit.timeit(stmt=stmt, setup=setup.format(i), number=10) for i in range(100000)]
>>> sum(times) * 1.0 / len(times)
6.2434492111206052e-05
>>> times[-1]
0.0001220703125
最后一次操作的执行时间远低于平均值。而且趋势相当稳定:

>>> for t in times[0:100000:10000]:
...     print t
... 
5.00679016113e-06
1.31130218506e-05
2.90870666504e-05
3.88622283936e-05
5.10215759277e-05
6.19888305664e-05
7.41481781006e-05
8.48770141602e-05
9.60826873779e-05
0.000108957290649
尽管如此,像这样在小字符串上的操作还是相当便宜的


要展开其他问题,列表和字符串的索引访问都是O(1)

>>> stmt = 'x = s[{0}] + s[{1}] + s[{2}]'
>>> setup = 's = "a" * {0}'
>>> times = [timeit.timeit(stmt=stmt.format(i / 2, i / 3, i / 4), setup=setup.format(i + 1), number=10) for i in range(1000000)]
>>> sum(times) * 1.0 / len(times)
3.6441037654876707e-06
>>> times[-1]
3.0994415283203125e-06
与列表类似:

>>> stmt = 'x = s[{0}] + s[{1}] + s[{2}]'
>>> setup = 's = ["a"] * {0}'
>>> times = [timeit.timeit(stmt=stmt.format(i / 2, i / 3, i / 4), setup=setup.format(i + 1), number=10) for i in range(100000)]
>>> sum(times) * 1.0 / len(times)
2.8617620468139648e-06
>>> times[-1]
1.9073486328125e-06
切片复制字符串和列表,因此是O(n)与
n==len(slice)
。没有“好”的方法来替换字符串中的一个字母,尽管我想强调的是,“坏”的方法在大多数情况下已经足够好了。如果你想要“好”通过这种方式,可以使用不同的数据类型;操作列表并在需要字符串时连接它;或者使用StringIO对象。提供了一些关于连接不同内置Python数据类型的有用信息

最后,由于您对内部结构非常感兴趣,我在中找到了
PyStringObject
struct
声明(来自版本2.7;3+可能看起来有所不同)。这与您所期望的差不多——一个带有一些额外铃铛和哨子的c字符串:

typedef struct {
    PyObject_VAR_HEAD
PyObject\u VAR\u HEAD
是一个c预处理器宏,根据解释的规则扩展为如下内容。)

继续

    long ob_shash;
    int ob_sstate;
    char ob_sval[1];

    /* Invariants:
     *     ob_sval contains space for 'ob_size+1' elements.
     *     ob_sval[ob_size] == 0.
     *     ob_shash is the hash of the string or -1 if not computed yet.
     *     ob_sstate != 0 iff the string object is in stringobject.c's
     *       'interned' dictionary; in this case the two references
     *       from 'interned' to this object are *not counted* in ob_refcnt.
     */
} PyStringObject;
列表有一个--c数组,带有额外的铃铛和哨子,但不是以null结尾的,通常有额外的预分配存储空间


不用说……这大部分只适用于cPython-,而且可能看起来完全不同!

Perl字符串肯定不是不可变的。每个字符串都有一个缓冲区、缓冲区中字符串的初始偏移量、缓冲区的长度和使用的缓冲区量。此外,对于utf8字符串,字符长度是当需要计算时缓存。在某一点上,也有一些额外的字符偏移量到字节偏移量信息的缓存,但我不确定这是否仍然存在

如果需要增加缓冲区,它会重新分配。许多平台上的Perl都知道系统malloc的粒度,因此它可以为11字节的字符串分配一个(比如)14字节的缓冲区,知道这实际上不会占用任何额外的内存


初始偏移量允许O(1)从字符串的开头删除数据。

某些字符串[5]='a'
在Python中是O(1)-
类型错误对于较长的字符串不会变得更昂贵;)废话。正确的。对不起,我的蟒蛇有点生锈了。我想我的意思是:有没有一种方法可以在不复制整个字符串的情况下修改字符串中的一个字母(即比O(n)快)?如果你有Python3,并且你想使用
字节
,那么是的。
Python2.6+中的bytearray
提供了可更改的字节字符串,它们共享普通字符串的所有属性,除了散列性(以及字典键等可用性)如果不复制unicode字符串,就无法修改unicode字符串中的单个字母。这可能是两个独立的问题。对于列表,我可以在O(1)时间内用lst[5]='a'修改一个项目,因为我不需要创建新列表,也不需要对正在修改的项目周围的项目执行任何操作(注意:列表存储为动态数组,所以我只是替换数组中的一个指针)。如果我有一个很长的字符串,而创建一个新字符串的成本很高,是否没有有效的方法来替换一个字母?如果我加入然后拆分呢?还有,切片操作呢,比如str[:5]?它的效率有多高?它对字符串是如何工作的?如果我知道字符串是如何存储的,我想我会更乐意回答这样的问题。@Eli,我添加了一些更详细的内容--希望这能回答您的问题!谢谢!您的新答案非常详细,正是我想要的(无论如何,对于Python).1!因此它们的实现方式基本上与Perl数组相同(两侧都有增长的空间,摊销O(1)向两侧追加字符,等等)?不,我认为字符串缓冲区的分配更为保守。
    long ob_shash;
    int ob_sstate;
    char ob_sval[1];

    /* Invariants:
     *     ob_sval contains space for 'ob_size+1' elements.
     *     ob_sval[ob_size] == 0.
     *     ob_shash is the hash of the string or -1 if not computed yet.
     *     ob_sstate != 0 iff the string object is in stringobject.c's
     *       'interned' dictionary; in this case the two references
     *       from 'interned' to this object are *not counted* in ob_refcnt.
     */
} PyStringObject;