【问题标题】:Python process killedPython进程被杀死
【发布时间】:2016-03-18 18:46:37
【问题描述】:

我正在运行以下代码,并收到来自 python 的“已终止”消息:

import random,string

def rotations(t):
        ''' Return list of rotations of input string t '''
        tt = t * 2
        return [ tt[i:i+len(t)] for i in xrange(0, len(t)) ]
def bwtViaBwm(t):
        return ''.join(map(lambda x: x[-1], bwm(t)))
def bwm(t):
        return sorted(rotations(t))

def build_FM(fname):
        stream=readfile(fname)
        fc=[x[0] for x in bwtViaBwm(stream)]


def readfile(sd):
    s=""
    with open(sd,'r') as myfile:
        s =myfile.read()
    return s.rstrip('\n')

def writefile(sd,N):
        with open(sd, "wb") as sink:
            sink.write(''.join(random.choice(string.ascii_uppercase + string.digits) for _ in xrange(int(N))))
            sink.write('$')
        return
def main():
    fname= sys.argv[1]
    N =sys.argv[2]
    writefile(fname,N)
    build_FM(fname)
    return

if __name__=='__main__':
        main()

它需要一个文件名和一个数字作为输入。该代码创建一个大小为N 的随机流,然后在该流上运行 BWT 转换。当我输入N=500000 时,我收到一条“已终止”消息,这对于内存错误来说似乎是一个很小的数字。我的系统运行 Ubuntu 14.04、8GB RAM 和 python 2.7。

这就是我运行脚本的方式:

python  fm.py new_file.csv 500000

几秒钟后我得到了这个:

killed

【问题讨论】:

  • 你应该包括具体的错误!
  • 我是怎么知道的?控制台只是输出killed
  • 你能打印出你得到的确切的控制台输入/输出吗?请注意,我们不知道您是如何调用程序的,或者控制台报告的具体是什么:很遗憾,如果没有这个,我们将无法重现您的问题。
  • @NathanielFord 我用那个信息改变了问题
  • 我系统的 RAM 在 3 秒内被填满,然后进程被杀死。

标签: python-2.7


【解决方案1】:

问题在于您的 rotations 函数:

def rotations(t):
    ''' Return list of rotations of input string t '''
    tt = t * 2
    return [ tt[i:i+len(t)] for i in xrange(0, len(t)) ]

看看它的作用:

>>> rotations('x')
['x']
>>> rotations('xx')
['xx', 'xx']
>>> rotations('xxxxx')
['xxxxx', 'xxxxx', 'xxxxx', 'xxxxx', 'xxxxx']

这样做的结果将呈指数级增长。因此,500000 字符的文件将产生长度为500000^2 的结果。

从计算上讲,对于这么大的输入,不太可能有一种方法可以完成您尝试的操作:即让字符串的每次旋转都长 500k 个字符。我们知道输入中的每个元素都有一个输出,每个输出都有一个原始输入的长度。因此,最小大小为n*nn^2。除非你知道你只需要有限数量的这些(并且可以及早淘汰它们),否则你总是会遇到这个问题。

如何解决问题

首先我们需要找出问题所在。让我们看看代码在做什么。假设一个简单的起始集:

bacb

rotation() 提供该集合的所有可能旋转:

>>> rotations('bacb')
['bacb', 'acbb', 'cbba', 'bbac']

然后您对该列表进行排序。

>>> sorted(rotations('bacb'))
['acbb', 'bacb', 'bbac', 'cbba']

然后你取每个元素的最后一个元素,得到bdac。这意味着对于输入中的每个元素 n,您正在分配一个排序顺序,例如 n+1 ... n(环绕)将按字母数字排序。

为了解决这个问题,算法将是:

  • 创建一个空列表“final_order”,它将是输入列表的“排序”索引列表。
  • 对于每个元素
    • 获取从该元素加一开始的原始字符串的“旋转”
    • 以排序方式将轮换放入“final_order”列表中:
    • 获取“final_order”列表中第一个元素的“旋转”。
    • 比较两个旋转。
    • 如果新旋转小于旧旋转,则在该点插入到列表中。否则转到下一个轮换。
    • 如果没有其他旋转,则将新的旋转放在那里。

(可能有一种更快的排序方式,但为了便于解释,我将采用这种方式。)

我们首先需要的是get_rotation(input, idx):

def get_rotation(input, idx):
    return input[idx + 1:] + input[:idx + 1]

现在是困难的部分(见 cmets):

def strange_sort(input):
    sorted_indices = list()  # Initialize the list

    for idx in range(len(input)):  # For each element in the list
        new_rotation = get_rotation(input, idx)  # Get the rotation starting at that index
        found_location = False  # Need this to handle the sorting
        for sorted_idx in range(len(sorted_indices)):  # Iterate through all 'found' indices
            old_rotation = get_rotation(input, sorted_indices[sorted_idx])  # Get the rotation starting at the found/old rotation
            if new_rotation < old_rotation:  # Which comes first?
                # If this one, insert the new rotation's starting index before the index of the already sorted rotation
                sorted_indices.insert(sorted_idx, idx)
                found_location = True
                break
        if not found_location:  # If greater than everything, insert at end
            sorted_indices.insert(len(sorted_indices), idx)
    return "".join(map(lambda x: input[x], sorted_indices))  # Join and return result

运行这个我们得到一个短输入的预期结果:

>>> print("Final result={}".format(strange_sort('bacb')))
Final result=bbca

这是带有测试/计时器的完整程序:

import random, string, datetime

def get_rotation(input, idx):
    return input[idx + 1:] + input[:idx + 1]

def strange_sort(input):
    sorted_indices = list()

    for idx in range(len(input)):
        new_rotation = get_rotation(input, idx)
        found_location = False
        for sorted_idx in range(len(sorted_indices)):
            old_rotation = get_rotation(input, sorted_indices[sorted_idx])
            if new_rotation < old_rotation:
                sorted_indices.insert(sorted_idx, idx)
                found_location = True
                break
        if not found_location:
            sorted_indices.insert(len(sorted_indices), idx)
    return "".join(map(lambda x: input[x], sorted_indices))

n1 = 5
n2 = 50
n3 = 500
n4 = 5000
n5 = 50000
n6 = 500000

n = [n1, n2, n3, n4, n5, n6]

def test(lst):
    for l in range(len(lst)):
        input = ''.join(random.choice(string.ascii_uppercase+string.digits) for x in range(lst[l]))
        start = datetime.datetime.now()
        result = strange_sort(input)
        end = datetime.datetime.now()
        runtime = end - start
        print("n{} runtime={} head={} tail={}".format(l, runtime.seconds, result[:5], result[-5:]))

test(n)

尝试是利用不需要存储所有内容,只需为初始排序的每个索引存储最终排序的索引即可。可悲的是,上面的实现显然太慢了,从运行中我们可以看出:

$ python2 strange_sort.py
n0 runtime=0 head=SJP29 tail=SJP29
n1 runtime=0 head=5KXB4 tail=59WAK
n2 runtime=0 head=JWO54 tail=7PH60
n3 runtime=4 head=Y2X2O tail=MFUGK
(Still running)

好的,所以我们知道这种情况很糟糕。我们可以让它更快吗?我们从Python Wiki Entry on Big-O 中看到,需要O(M) 来获取字符串切片。对我们来说,这意味着O(N),因为我们正在拍摄两个切片,它们会增加整个长度。这在计算上是一场灾难,因为我们每次都在这样做。

与其每次都获得完整的旋转,不如让我们迭代和比较。一个旋转的一个索引与另一个旋转的一个索引的单个比较应该是O(2)。在最坏的情况下,我们必须这样做O(N) 次,但不太可能每次都这样。

我们添加了一个额外的 for 循环并将其重新设计为只查看下一个索引:

for offset in range(len(input)):
    if new_rotation[offset] < input[(sorted_indices[sorted_idx] + offset) % len(input)]:
        sorted_indices.insert(sorted_idx, idx)
        found_location = True
        break
if found_location:
    break

我们现在用我们的计时器来执行它:

$ python2 strange_sort.py
n0 runtime=0 head=VA6KY tail=VA6KY
n1 runtime=0 head=YZ39U tail=63V0O
n2 runtime=0 head=JFYKP tail=8EB2S
n3 runtime=0 head=IR4J9 tail=VLR4Z
n4 runtime=28 head=EYKVG tail=7Q3NM
n5 runtime=4372 head=JX4KS tail=6GZ6K

正如我们所见,这次我们只用了 28 秒就到达了n4。不过,这对n6 来说并不是个好兆头。唉,看起来这种计算的复杂性表明我们需要一种比Insertion Sort 更好的排序方法,Insertion Sort 最差(甚至平均)是O(n^2)。在输入500K 时,至少需要进行250B(十亿)计算。 (次数n,计算机每次计算实际执行的指令数)。

我们已经了解到的是,您实际上并不需要将旋转放在一边。要解决这个问题,你必须编写一个快速排序算法,它的输入不是实际值,而是一个可以在给定精度下计算值的函数。

把整个事情放在头上,我想尝试创建一个对象,该对象可以对自身进行足够深入的搜索,以了解它如何与另一个对象进行排序,并使用 Python 的内置排序。

import random, string, datetime
from functools import total_ordering


@total_ordering
class Rotation(object):
    """Describes a rotation of an input based on getting the original and then offsetting it."""

    def __init__(self, original, idx):
        self.original = original
        self.idx = idx

    def getOffset(self, offset):
        return self.original[(self.idx + offset) % len(self.original)]

    def __eq__(self, other):
        print("checking equality")
        if self.idx == other.idx:
            return True
        for offset in range(len(self.original)):
            if self.getOffset(offset) is not other.getOffset(offset):
                print("this={} is not that={}".format(self.getOffset(offset), other.getOffset(
                        offset)))
                return False
        return True

    def __lt__(self, other):
        for offset in range(len(self.original)):
            if self.getOffset(offset) < other.getOffset(offset):
                return True
            elif self.getOffset(offset) > other.getOffset(offset):
                return False
        return False

    def __str__(self):
        return self.getOffset(-1)

    def __repr__(self):
        return "".join(map(lambda x: str(x), [self.getOffset(idx) for idx in range(len(
                self.original))]))


def improved_strange_sort(input):
    original = list(input)
    rotations = [Rotation(original, idx) for idx in range(len(original))]
    result = sorted(rotations)
    # print("original={} rotations={} result={}".format(original, rotations, result))
    return "".join(map(lambda x: str(x), result))


def test(input):
    start = datetime.datetime.now()
    result = improved_strange_sort(input)
    end = datetime.datetime.now()
    runtime = end - start
    print("input={} runtime={} head={} tail={}".format(input[:5], runtime.seconds, result[:5],
                                                       result[-5:]))


def timed_test(lst):
    for l in range(len(lst)):
        print("Test {} with length={}".format(l, lst[l]))
        test(''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(lst[l])))


n1 = 5
n2 = 50
n3 = 500
n4 = 5000
n5 = 50000
n6 = 500000

n = [n1, n2, n3, n4, n5, n6]

test('bacb')
timed_test(n)

这似乎产生了正确的结果:

$ python2 strange_sort.py 
input=bacb runtime=0 head=bbca tail=bbca
Test 0 with length=5
input=FB2EH runtime=0 head=BF2HE tail=BF2HE
Test 1 with length=50
input=JT3ZP runtime=0 head=W8XQE tail=QRUC3
Test 2 with length=500
input=TL8L7 runtime=0 head=R4ZUG tail=M268H
Test 3 with length=5000
input=PYFED runtime=1 head=L5J0T tail=HBSMV
Test 4 with length=50000
input=C6TR8 runtime=254 head=74IIZ tail=U69JG
Test 5 with length=500000
(still running)

【讨论】:

  • 有更好的算法解决方案吗?
  • @curious 尽可能多地提供。如果我的慢排序版本最终完成,我可能会再次更新。不过,我估计这个算法需要 3.5 天。
  • 谢谢!这个最后的额外循环应该放在最终代码中的什么地方?
  • for sorted_idx in range(len(sorted_indices)): 之下。但是请看我的更新。
  • @curious 一定要支持/接受对您有帮助的答案!这将有助于在未来获得更多、质量、帮助!
【解决方案2】:

我做了一些实验,问题出在rotations(t)

第一个问题是您将输入字符串的大小加倍,最初是 500.000 个字符,现在变成了 1.000.000。但这仍然是负担得起的,我们仍然在谈论 1.5 兆字节左右的内存。

但在此之后,您创建了一个包含 500.000 个字符串的列表,每个字符串有 500.000 个字符长,这大约相当于 232 GB 的内存,这些内存只是为了进行下一个计算步骤而浮动。

这显然是不可能的,因为我们都没有这么多内存,所以你的程序会被杀死。


你问是否可以optimize这个代码.. ..我把它当作is it possible to employ less memory?

假设您愿意用计算时间来换取更少的内存消耗,那么您可以编写一个不需要太多内存的算法版本。例如:

def bwtManual(t):
    tt = 2 * t
    res_str = ''
    old_min = None
    for j in xrange(0, len(t)):
        cur_min = None
        print("Round: " + str(j))
        for i in xrange(0, len(t)):
            # generate 1 string at a time
            tmp_str = tt[i:i+len(t)]
            # select an initial minimum string
            # > must not be smaller than previous minimum
            if cur_min is None:
                if old_min is not None:
                    if tmp_str > old_min:
                        cur_min = tmp_str
                    else:
                        continue
                else:
                    cur_min = tmp_str
                continue
            # skip strings that have been already selected
            if old_min is not None and tmp_str <= old_min:
                continue
            # select new minimum among remaining strings
            if (tmp_str < cur_min):
                cur_min = tmp_str
        # store character
        res_str += cur_min[-1]
        old_min = cur_min
    return res_str

在小尺寸上,没问题,只是有点慢。

500.000 个字符?在我的计算机上需要 115 天,它具有平均计算能力。


总结:

rotations(t)生成的字符串真的没有任何理由单独存在。这些字符串的存在只是为了让我们执行sort()然后推断每个字符串的最后一个字符。

有没有可能做得比这更好?我认为是的。

我们的想法是设计您自己的排序函数,该函数使用对tt 子字符串的引用而不是它的副本。因此,每个 rotation 只需要几个指针,而不是原始字符串的完整副本。

我试图寻找在 python 中执行此操作的提示,我发现对象 memoryviewbuffer 看起来很有希望。但是,显然这些包装器似乎并没有原生地实现比较运算符,并且需要您推断(副本)它们指向的字符串。这将破坏在您的上下文中使用这些包装器的全部目的,因此它们可能不会有太大用处。您可以查看它们并自行决定。

我认为设计一个 C++ 模块会容易得多,该模块对引用原始字符串的子字符串的抽象节点进行排序,然后返回您使用 map() 代码构建的最终字符串。然后,您可以将此模块连接到您的 python 代码,或者简单地用 C++ 编写其余代码。

【讨论】:

  • 我根据您的附加问题更新了我的答案,希望对您有所帮助。
  • 其实我只需要每次旋转的第一个和最后一个字符,不需要中间字符。如果我们将返回的 500.000 个字符的列表视为一个矩阵,那就是第一个和最后一个矩阵。这会允许进一步优化吗?我想看看我是否可以删除中间字符
  • AFAIK 你不能,你需要一大堆字符串才能对它们进行排序。正如我所说,您可以做的唯一优化是通过调整代码以便不需要太多内存,这将在实践中大大加快速度,但您的复杂性将保持在排序算法的较低范围内。跨度>
猜你喜欢
  • 2010-12-08
  • 1970-01-01
  • 2013-04-29
  • 2013-04-07
  • 2012-09-21
  • 1970-01-01
  • 2011-09-09
  • 1970-01-01
  • 2010-12-02
相关资源
最近更新 更多