【问题标题】:how to avoid substrings如何避免子字符串
【发布时间】:2010-12-20 00:21:35
【问题描述】:

我目前处理这样的字符串部分:

for (i, j) in huge_list_of_indices:
    process(huge_text_block[i:j])

我想避免生成这些临时子字符串的开销。有任何想法吗?也许是一个以某种方式使用索引偏移的包装器?这是我目前的瓶颈。

请注意,process() 是另一个 Python 模块,它需要一个字符串作为输入。

编辑:

有些人怀疑存在问题。以下是一些示例结果:

import time
import string
text = string.letters * 1000

def timeit(fn):
    t1 = time.time()
    for i in range(len(text)):
        fn(i)
    t2 = time.time()
    print '%s took %0.3f ms' % (fn.func_name, (t2-t1) * 1000)

def test_1(i):
    return text[i:]

def test_2(i):
    return text[:]

def test_3(i):
    return text

timeit(test_1)
timeit(test_2)
timeit(test_3)

输出:

test_1 took 972.046 ms
test_2 took 47.620 ms
test_3 took 43.457 ms

【问题讨论】:

  • 我认为你在正确的轨道上使用了使用索引偏移的包装器。你试过什么了?实际上,你怎么知道 Python 没有已经为你做这件事?
  • 如何在不临时创建小字符串的情况下传递一系列小字符串?
  • @lunixbochs - 如果对象是用 C 语言实现的(如字符串)并公开缓冲区/内存视图 API,那么您基本上会按照 Richard 想象的方式进行操作......(使用指向对象数据的指针 - 尽管您没有'不要在 Python 中那样称呼它们 [请参阅我的答案以获取示例])。
  • 顺便说一下,你的比较是不相关的。使用for i in range(len(text))test_1() 返回的字符串 text[i:] 越来越短,而字符串 text[:]test_2() 返回的 > 具有恒定长度。这样做,比较会测量由于返回对象的切片和长度而导致的执行时间差异。
  • 即使你的断言是真的@eyquem(返回字符串需要时间随字符串长度而变化),那么 OP 的断言仍然是正确的——使用可以避免的切片是有成本的。

标签: python regex string


【解决方案1】:

我想你要找的是buffers

缓冲区的特点是它们“切片”支持缓冲区接口的对象不复制其内容,但本质上是在切片对象内容上打开一个“窗口”。 here 提供了更多技术解释。摘录:

用 C 语言实现的 Python 对象可以导出一组称为“缓冲区接口”的函数。对象可以使用这些函数以原始的、面向字节的格式公开其数据。对象的客户端可以使用缓冲区接口直接访问对象数据,无需先复制。

在您的情况下,代码应该或多或少像这样:

>>> s = 'Hugely_long_string_not_to_be_copied'
>>> ij = [(0, 3), (6, 9), (12, 18)]
>>> for i, j in ij:
...     print buffer(s, i, j-i)  # Should become process(...)
Hug
_lo
string

HTH!

【讨论】:

  • 这看起来很有希望 - 将进行调查
  • 优秀 - 缓冲区可以按我需要的方式视为字符串
【解决方案2】:

使用索引偏移量到 mmap 对象的包装器可以工作,是的。

但是在你这样做之前,你确定生成这些子字符串是个问题吗?在发现时间和内存实际去向之前,不要进行优化。我不认为这是一个严重的问题。

【讨论】:

  • 我同意应该检查子字符串的生成是否存在问题,但它们当然可以。如果切片非常大,切片很多,并且与切片相比处理速度相对较快,那么这可能是个大问题。我亲眼目睹了这是一个问题的实例。
  • process() 主要是一组正则表达式,所以很轻。在这种情况下如何使用 mmap?
  • @Richard :当您说正则表达式部分比切片部分轻时,我非常怀疑。但这只是一个猜测。建议:使用cProfile模块进行比较。
  • @heltonbiker : 无需怀疑 - 已添加示例测试输出
  • @Richard:没有人怀疑切片比不切片花费更多时间。问题是切片是否真的会在您的过程中浪费大量时间。因此,您必须将您的程序作为一个整体进行概要分析,这样您才能看到需要时间。
【解决方案3】:

如果您使用的是 Python3,则可以使用协议缓冲区和内存视图。假设文本存储在文件系统中的某个位置:

f = open(FILENAME, 'rb')
data = bytearray(os.path.getsize(FILENAME))
f.readinto(data)

mv = memoryview(data)

for (i, j) in huge_list_of_indices:
    process(mv[i:j])

查看this 文章。可能有用。

【讨论】:

  • 看起来很有希望,但目前停留在 2.6 上
  • @Richard - memoryviews 是“R-only”缓冲区的“RW 姐妹”。缓冲区(请参阅我的回答)已向后移植到 2.6,内存视图到 2.7... 仅供您了解,以防您在某个时候发现需要 RW 功能!
  • 在 python 2.7 中,file.readinto 的文档说“readinto() -> 未记录。不要使用它;它可能会消失。”。在 python 3.2 中,文档完全是空的……你知道当前计划在未来支持什么吗?
【解决方案4】:

也许使用索引偏移的包装器确实是您正在寻找的。这是一个完成这项工作的示例。根据您的需要,您可能需要对切片添加更多检查(针对溢出和负索引)。

#!/usr/bin/env python

from collections import Sequence
from timeit import Timer

def process(s):
    return s[0], len(s)

class FakeString(Sequence):
    def __init__(self, string):
        self._string = string
        self.fake_start = 0
        self.fake_stop = len(string)

    def setFakeIndices(self, i, j):
        self.fake_start = i
        self.fake_stop = j

    def __len__(self):
        return self.fake_stop - self.fake_start

    def __getitem__(self, ii):
        if isinstance(ii, slice):
            if ii.start is None:
                start = self.fake_start
            else:
                start = ii.start + self.fake_start
            if ii.stop is None:
                stop = self.fake_stop
            else:
                stop = ii.stop + self.fake_start
            ii = slice(start,
                       stop,
                       ii.step)
        else:
            ii = ii + self.fake_start
        return self._string[ii]

def initial_method():
    r = []
    for n in xrange(1000):
        r.append(process(huge_string[1:9999999]))
    return r

def alternative_method():
    r = []
    for n in xrange(1000):
        fake_string.setFakeIndices(1, 9999999)
        r.append(process(fake_string))
    return r


if __name__ == '__main__':
    huge_string = 'ABCDEFGHIJ' * 100000
    fake_string = FakeString(huge_string)

    fake_string.setFakeIndices(5,15)
    assert fake_string[:] == huge_string[5:15]

    t = Timer(initial_method)
    print "initial_method(): %fs" % t.timeit(number=1)

给出:

initial_method(): 1.248001s  
alternative_method(): 0.003416s

【讨论】:

  • 这主要是将成本从字符串创建转移到字符串访问,因为这些包装器会很慢。
  • 你是对的,但取决于 process() 中所做的事情,它可能值得移动...... @Richard 告诉我们字符串创建是瓶颈,所以我认为 process() 方法很轻关于字符串访问(如我的示例中)。
【解决方案5】:

OP 给出的示例将给出切片和不切片之间几乎最大的性能差异。

如果处理确实需要花费大量时间,则问题可能几乎不存在。

事实是 OP 需要让我们知道进程的作用。最可能的情况是它做了一些重要的事情,因此他应该分析他的代码。

改编自 op 的例子:

#slice_time.py

import time
import string
text = string.letters * 1000
import random
indices = range(len(text))
random.shuffle(indices)
import re


def greater_processing(a_string):
    results = re.findall('m', a_string)

def medium_processing(a_string):
    return re.search('m.*?m', a_string)                                                                              

def lesser_processing(a_string):
    return re.match('m', a_string)

def least_processing(a_string):
    return a_string

def timeit(fn, processor):
    t1 = time.time()
    for i in indices:
        fn(i, i + 1000, processor)
    t2 = time.time()
    print '%s took %0.3f ms %s' % (fn.func_name, (t2-t1) * 1000, processor.__name__)

def test_part_slice(i, j, processor):
    return processor(text[i:j])

def test_copy(i, j, processor):
    return processor(text[:])

def test_text(i, j, processor):
    return processor(text)

def test_buffer(i, j, processor):
    return processor(buffer(text, i, j - i))

if __name__ == '__main__':
    processors = [least_processing, lesser_processing, medium_processing, greater_processing]
    tests = [test_part_slice, test_copy, test_text, test_buffer]
    for processor in processors:
        for test in tests:
            timeit(test, processor)

然后运行...

In [494]: run slice_time.py
test_part_slice took 68.264 ms least_processing
test_copy took 42.988 ms least_processing
test_text took 33.075 ms least_processing
test_buffer took 76.770 ms least_processing
test_part_slice took 270.038 ms lesser_processing
test_copy took 197.681 ms lesser_processing
test_text took 196.716 ms lesser_processing
test_buffer took 262.288 ms lesser_processing
test_part_slice took 416.072 ms medium_processing
test_copy took 352.254 ms medium_processing
test_text took 337.971 ms medium_processing
test_buffer took 438.683 ms medium_processing
test_part_slice took 502.069 ms greater_processing
test_copy took 8149.231 ms greater_processing
test_text took 8292.333 ms greater_processing
test_buffer took 563.009 ms greater_processing

注意事项:

是的,我用 [i:] 切片尝试了 OP 的原始 test_1,它的速度要慢得多,使他的测试更加愚蠢。

有趣的是,缓冲区几乎总是比切片稍慢。这次有一个做得更好的地方!真正的测试虽然在下面,但缓冲区似乎对较大的子字符串做得更好,而切片对较小的子字符串做得更好。

而且,是的,我在这个测试中确实有一些随机性,所以测试一下,看看不同的结果 :)。更改 1000 的大小也可能很有趣。

所以,也许其他人相信你,但我不相信,所以我想了解一下处理的作用以及你是如何得出结论的: "切片是个问题。"

我在我的示例中分析了介质处理并将 string.letters 乘数提高到 100000 并将切片的长度提高到 10000。下面还有一个长度为 100 的切片。我使用 cProfile 来处理这些(开销比 profile 少得多!)。

test_part_slice took 77338.285 ms medium_processing
         31200019 function calls in 77.338 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   77.338   77.338 <string>:1(<module>)
        2    0.000    0.000    0.000    0.000 iostream.py:63(write)
  5200000    8.208    0.000   43.823    0.000 re.py:139(search)
  5200000    9.205    0.000   12.897    0.000 re.py:228(_compile)
  5200000    5.651    0.000   49.475    0.000 slice_time.py:15(medium_processing)
        1    7.901    7.901   77.338   77.338 slice_time.py:24(timeit)
  5200000   19.963    0.000   69.438    0.000 slice_time.py:31(test_part_slice)
        2    0.000    0.000    0.000    0.000 utf_8.py:15(decode)
        2    0.000    0.000    0.000    0.000 {_codecs.utf_8_decode}
        2    0.000    0.000    0.000    0.000 {isinstance}
        2    0.000    0.000    0.000    0.000 {method 'decode' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  5200000    3.692    0.000    3.692    0.000 {method 'get' of 'dict' objects}
  5200000   22.718    0.000   22.718    0.000 {method 'search' of '_sre.SRE_Pattern' objects}
        2    0.000    0.000    0.000    0.000 {method 'write' of '_io.StringIO' objects}
        4    0.000    0.000    0.000    0.000 {time.time}


test_buffer took 58067.440 ms medium_processing
         31200103 function calls in 58.068 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   58.068   58.068 <string>:1(<module>)
        3    0.000    0.000    0.000    0.000 __init__.py:185(dumps)
        3    0.000    0.000    0.000    0.000 encoder.py:102(__init__)
        3    0.000    0.000    0.000    0.000 encoder.py:180(encode)
        3    0.000    0.000    0.000    0.000 encoder.py:206(iterencode)
        1    0.000    0.000    0.001    0.001 iostream.py:37(flush)
        2    0.000    0.000    0.001    0.000 iostream.py:63(write)
        1    0.000    0.000    0.000    0.000 iostream.py:86(_new_buffer)
        3    0.000    0.000    0.000    0.000 jsonapi.py:57(_squash_unicode)
        3    0.000    0.000    0.000    0.000 jsonapi.py:69(dumps)
        2    0.000    0.000    0.000    0.000 jsonutil.py:78(date_default)
        1    0.000    0.000    0.000    0.000 os.py:743(urandom)
  5200000    6.814    0.000   39.110    0.000 re.py:139(search)
  5200000    7.853    0.000   10.878    0.000 re.py:228(_compile)
        1    0.000    0.000    0.000    0.000 session.py:149(msg_header)
        1    0.000    0.000    0.000    0.000 session.py:153(extract_header)
        1    0.000    0.000    0.000    0.000 session.py:315(msg_id)
        1    0.000    0.000    0.000    0.000 session.py:350(msg_header)
        1    0.000    0.000    0.000    0.000 session.py:353(msg)
        1    0.000    0.000    0.000    0.000 session.py:370(sign)
        1    0.000    0.000    0.000    0.000 session.py:385(serialize)
        1    0.000    0.000    0.001    0.001 session.py:437(send)
        3    0.000    0.000    0.000    0.000 session.py:75(<lambda>)
  5200000    4.732    0.000   43.842    0.000 slice_time.py:15(medium_processing)
        1    5.423    5.423   58.068   58.068 slice_time.py:24(timeit)
  5200000    8.802    0.000   52.645    0.000 slice_time.py:40(test_buffer)
        7    0.000    0.000    0.000    0.000 traitlets.py:268(__get__)
        2    0.000    0.000    0.000    0.000 utf_8.py:15(decode)
        1    0.000    0.000    0.000    0.000 uuid.py:101(__init__)
        1    0.000    0.000    0.000    0.000 uuid.py:197(__str__)
        1    0.000    0.000    0.000    0.000 uuid.py:531(uuid4)
        2    0.000    0.000    0.000    0.000 {_codecs.utf_8_decode}
        1    0.000    0.000    0.000    0.000 {built-in method now}
       18    0.000    0.000    0.000    0.000 {isinstance}
        4    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {locals}
        1    0.000    0.000    0.000    0.000 {map}
        2    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'close' of '_io.StringIO' objects}
        1    0.000    0.000    0.000    0.000 {method 'count' of 'list' objects}
        2    0.000    0.000    0.000    0.000 {method 'decode' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {method 'extend' of 'list' objects}
  5200001    3.025    0.000    3.025    0.000 {method 'get' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {method 'getvalue' of '_io.StringIO' objects}
        3    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}
  5200000   21.418    0.000   21.418    0.000 {method 'search' of '_sre.SRE_Pattern' objects}
        1    0.000    0.000    0.000    0.000 {method 'send_multipart' of 'zmq.core.socket.Socket' objects}
        2    0.000    0.000    0.000    0.000 {method 'strftime' of 'datetime.date' objects}
        1    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}
        2    0.000    0.000    0.000    0.000 {method 'write' of '_io.StringIO' objects}
        1    0.000    0.000    0.000    0.000 {posix.close}
        1    0.000    0.000    0.000    0.000 {posix.open}
        1    0.000    0.000    0.000    0.000 {posix.read}
        4    0.000    0.000    0.000    0.000 {time.time}

更小的切片(长度为 100)。

test_part_slice took 54916.153 ms medium_processing
         31200019 function calls in 54.916 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   54.916   54.916 <string>:1(<module>)
        2    0.000    0.000    0.000    0.000 iostream.py:63(write)
  5200000    6.788    0.000   38.312    0.000 re.py:139(search)
  5200000    8.014    0.000   11.257    0.000 re.py:228(_compile)
  5200000    4.722    0.000   43.034    0.000 slice_time.py:15(medium_processing)
        1    5.594    5.594   54.916   54.916 slice_time.py:24(timeit)
  5200000    6.288    0.000   49.322    0.000 slice_time.py:31(test_part_slice)
        2    0.000    0.000    0.000    0.000 utf_8.py:15(decode)
        2    0.000    0.000    0.000    0.000 {_codecs.utf_8_decode}
        2    0.000    0.000    0.000    0.000 {isinstance}
        2    0.000    0.000    0.000    0.000 {method 'decode' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  5200000    3.242    0.000    3.242    0.000 {method 'get' of 'dict' objects}
  5200000   20.268    0.000   20.268    0.000 {method 'search' of '_sre.SRE_Pattern' objects}
        2    0.000    0.000    0.000    0.000 {method 'write' of '_io.StringIO' objects}
        4    0.000    0.000    0.000    0.000 {time.time}


test_buffer took 62019.684 ms medium_processing
         31200103 function calls in 62.020 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000   62.020   62.020 <string>:1(<module>)
        3    0.000    0.000    0.000    0.000 __init__.py:185(dumps)
        3    0.000    0.000    0.000    0.000 encoder.py:102(__init__)
        3    0.000    0.000    0.000    0.000 encoder.py:180(encode)
        3    0.000    0.000    0.000    0.000 encoder.py:206(iterencode)
        1    0.000    0.000    0.001    0.001 iostream.py:37(flush)
        2    0.000    0.000    0.001    0.000 iostream.py:63(write)
        1    0.000    0.000    0.000    0.000 iostream.py:86(_new_buffer)
        3    0.000    0.000    0.000    0.000 jsonapi.py:57(_squash_unicode)
        3    0.000    0.000    0.000    0.000 jsonapi.py:69(dumps)
        2    0.000    0.000    0.000    0.000 jsonutil.py:78(date_default)
        1    0.000    0.000    0.000    0.000 os.py:743(urandom)
  5200000    7.426    0.000   41.152    0.000 re.py:139(search)
  5200000    8.470    0.000   11.628    0.000 re.py:228(_compile)
        1    0.000    0.000    0.000    0.000 session.py:149(msg_header)
        1    0.000    0.000    0.000    0.000 session.py:153(extract_header)
        1    0.000    0.000    0.000    0.000 session.py:315(msg_id)
        1    0.000    0.000    0.000    0.000 session.py:350(msg_header)
        1    0.000    0.000    0.000    0.000 session.py:353(msg)
        1    0.000    0.000    0.000    0.000 session.py:370(sign)
        1    0.000    0.000    0.000    0.000 session.py:385(serialize)
        1    0.000    0.000    0.001    0.001 session.py:437(send)
        3    0.000    0.000    0.000    0.000 session.py:75(<lambda>)
  5200000    5.399    0.000   46.551    0.000 slice_time.py:15(medium_processing)
        1    5.958    5.958   62.020   62.020 slice_time.py:24(timeit)
  5200000    9.510    0.000   56.061    0.000 slice_time.py:40(test_buffer)
        7    0.000    0.000    0.000    0.000 traitlets.py:268(__get__)
        2    0.000    0.000    0.000    0.000 utf_8.py:15(decode)
        1    0.000    0.000    0.000    0.000 uuid.py:101(__init__)
        1    0.000    0.000    0.000    0.000 uuid.py:197(__str__)
        1    0.000    0.000    0.000    0.000 uuid.py:531(uuid4)
        2    0.000    0.000    0.000    0.000 {_codecs.utf_8_decode}
        1    0.000    0.000    0.000    0.000 {built-in method now}
       18    0.000    0.000    0.000    0.000 {isinstance}
        4    0.000    0.000    0.000    0.000 {len}
        1    0.000    0.000    0.000    0.000 {locals}
        1    0.000    0.000    0.000    0.000 {map}
        2    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'close' of '_io.StringIO' objects}
        1    0.000    0.000    0.000    0.000 {method 'count' of 'list' objects}
        2    0.000    0.000    0.000    0.000 {method 'decode' of 'str' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {method 'extend' of 'list' objects}
  5200001    3.158    0.000    3.158    0.000 {method 'get' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {method 'getvalue' of '_io.StringIO' objects}
        3    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}
  5200000   22.097    0.000   22.097    0.000 {method 'search' of '_sre.SRE_Pattern' objects}
        1    0.000    0.000    0.000    0.000 {method 'send_multipart' of 'zmq.core.socket.Socket' objects}
        2    0.000    0.000    0.000    0.000 {method 'strftime' of 'datetime.date' objects}
        1    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}
        2    0.000    0.000    0.000    0.000 {method 'write' of '_io.StringIO' objects}
        1    0.000    0.000    0.000    0.000 {posix.close}
        1    0.000    0.000    0.000    0.000 {posix.open}
        1    0.000    0.000    0.000    0.000 {posix.read}
        4    0.000    0.000    0.000    0.000 {time.time}

【讨论】:

    【解决方案6】:

    进程(huge_text_block[i:j])

    我想避免生成这些临时子字符串的开销。
    (...)
    注意 process() 是另一个 python 模块 需要一个字符串作为输入

    完全矛盾。
    你怎么能想象找到一种方法来不创建函数需要的东西?!

    【讨论】:

    • 如示例所示,我已经有一个字符串并且正在使用子字符串。我希望有更有效的方法。
    • @Richard 执行时产生错误(因为 process() 未知)的两行代码不构成示例 - 也许,我不明白你的问题。您能否解释一下 "I want to Avoid generate temporary substrings" 的意思。您在评论中说 "process() 主要只是一组正则表达式" 。据我了解,正则表达式只吃字符串。你想催眠他们,让他们相信他们吃了一根不存在的绳子吗?
    • 我也没有在我的示例中定义“huge_list_of_indices” - 这是理解问题的另一个障碍吗?
    • 没有。指令 for (i, j) in huge_list_of_indices 非常清楚地了解 huge_list_of_indices 是由什么组成的。
    猜你喜欢
    • 2021-03-09
    • 1970-01-01
    • 1970-01-01
    • 2015-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多