Python的切片有多快
为了节省空间,并且避免在不同数据源之间保持数据一致性带来的麻烦,我在考虑只存储一些子字符串的起始和结束索引,而不是直接存储这些子字符串。问题是,如果我这样做,可能会一直在创建切片(slice)。这样做值得避免吗?切片操作的速度够快,我是不是不用担心?还有,创建和销毁新对象的开销又如何呢?
好吧,我吸取了教训。除非真的有问题需要解决,否则不要过早优化。(当然,这并不是说要写毫无意义的糟糕代码,但这不是重点……)另外,在来Stack Overflow之前,记得先测试和分析一下。=D 谢谢大家!
5 个回答
我也没有做过任何测量,不过听起来你已经在用C语言的思路来解决Python中的问题。你可能想看看Python自带的mmap
库:
内存映射文件对象既像字符串,又像文件对象。不过,与普通字符串对象不同的是,这些对象是可变的。你可以在大多数需要字符串的地方使用mmap对象;比如,你可以用re模块在内存映射文件中搜索内容。因为它们是可变的,你可以通过obj[index] = 'a'来改变一个字符,或者通过给切片赋值来改变一段子字符串:obj[i1:i2] = '...'。你还可以从当前文件位置开始读写数据,并通过seek()在文件中跳转到不同的位置。
我不太确定你的问题是否正好是这个意思。而且需要强调的是,你确实需要进行一些测量。Python的timeit
库使用起来很简单,但还有cProfile
或者hotshot
,不过据我了解,hotshot
有可能会被从标准库中移除。
在一条评论中,提问者提到了数据库中的“冗余”问题,但没有说明具体是哪个数据库。从那条评论中得到的信息来看,似乎Python的字符串切片并不是主要问题,真正的“切片”是在从数据库中取数据时由数据库引擎来完成的。
如果情况确实如此,我建议一般来说不要在数据库中存储重复的信息。应该遵循一种“规范形式”(虽然这个说法可以稍微宽松一些;-),也就是说信息只存储一次,派生的信息可以重新计算(或者由数据库引擎缓存等;-)。而故意存储派生信息的“非规范化”应该是个例外,只有在特定且经过良好测量的检索性能需求下才可以这样做。
如果提到的“数据库”是个误导;-),或者说是像我上面提到的“规范形式”那样用得不太严谨;-),那么还有另一个考虑:因为Python的字符串是不可变的,所以不需要通过复制来进行切片,而是可以让每个切片重用其父字符串的一部分内存空间(就像numpy数组的切片那样)。不过,这个功能目前并不在Python的核心中。我曾经尝试过一个补丁来实现这个功能,但因为要添加对大字符串的引用,这样即使只有一个小子字符串被引用,它也会一直占用内存,这在通用情况下是个大问题。不过,可以为需要保持在内存中的大“父”字符串创建一个特殊的字符串子类(还有一个unicode的子类)。目前,buffer
在某种程度上实现了这一点,但你不能在buffer对象上调用字符串方法(除非先将其显式复制到字符串对象),所以它主要用于输出和一些特殊情况……不过在概念上并没有什么阻碍去添加字符串方法(我怀疑这会被纳入核心,但作为第三方模块维护起来应该相对简单;-)。
这种方法的价值很难通过测量来证明,无论如何,速度与当前的隐式复制方法非常相似;优势主要体现在减少内存占用上,这并不会让某段Python代码运行得更快,但可以让某个程序在内存稍少的机器上运行,或者在多个实例同时在不同进程中运行时更好地进行多任务处理。可以参考rope,这是一个在C++环境中实验过的类似但更丰富的方法(但注意它没有被纳入标准;-)。
快到什么程度算快呢?你现在是怎么做的?你到底存储了什么,取出了什么?答案可能会很大程度上依赖于这些问题。这就引出了……
测量!不要只是在理论上讨论和分析;试着测量一下哪种方式更有效。然后再决定是否值得为了提高性能而重构你的数据库。
补充:我刚刚做了一个测试,比较了字符串切片和在一个以 (start, end)
元组为键的字典中查找的速度。结果显示两者之间差别不大。不过这个测试比较简单,所以要谨慎看待这个结果。