更快的机器上完全相同的Python代码运行速度慢了20倍?

5
我将尝试执行以下 Python 代码,它将按字母顺序返回“ABCDEGGHIJK”的第一个排列。这需要一个非常简单的排序算法,正如在欧拉计划问题336中定义的那样,需要进行最大数量的迭代才能完成排序。
以下是代码(对于糟糕的变量名称,我表示歉意):
from itertools import permutations

def first_out_letter(st):
    """
    returns the first letter alphabetically  in st which is not in    sorted order
    alphabetically, string must be all in captials.
    """
    def first(string):
        """
        returns the first alphabetical letter in a string, only capitals allowed
        """
        alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

        for i in alpha:
            if i in string:
                return i
        return None
    sor = ''.join(sorted(st))
    for i in range(len(st)):
        if st[i] != sor[i]:
            return first(st[i:])
    return None

def get_arrangement_size(arrang,dictionary):
    """
     returns the number of shifts needed to arrange a string in lexographic order
    using a dumb method of first getting the first digit correct, then the second
    and so on...

    the argument dictionary stores precomputed results and is modified during execution. 
    """
    if arrang in dictionary.keys():
        return dictionary[arrang]
    sor = ''.join(sorted(arrang))
    if arrang == sor:
        dictionary[arrang] = 0
        return 0
    else:
        bing = first_out_letter(arrang)
        num_arr = 0
        pos_bing = 0
        for i in range(len(arrang)):
            if arrang[i] == sor[i]:
                num_arr += 1
            else:
                break
        for i in range(len(arrang)):
            if arrang[i] != bing:
                pos_bing += 1
            else:
                break
        if bing == arrang[-1]:
            low = get_arrangement_size(arrang[:num_arr]+arrang[num_arr:][::-1],dictionary)
        else:
            low = get_arrangement_size(arrang[:pos_bing]+arrang[pos_bing:][::-1],dictionary)
        dictionary[arrang] = low+1            
        return low+1

solutions = {}
letters = ["A","B","C","D","E","F","G","H","I","J","K"]
piv = permutations(letters)
for item in piv:
    get_arrangement_size(''.join(item),solutions) #builds up the solutions dictionary
ma = max(solutions.values())
fir = []
for item in solutions.keys():
    if solutions[item] == ma:
        fir.append(item)
fir = sorted(fir)
print(fir[0])

这段代码在我的两台机器上都可以正常运行且得出正确答案,但我发现在我两台机器上的速度差异非常大,高达20倍。我的理论上更快的i5电脑运行Linux Mint和Python 2.7.6,并且拥有更多内存,但是当我运行此代码时,我发现它的执行速度比我较慢的Celeron计算机要慢得多,后者运行Windows和Python 3.5.1。无论我在哪台机器上运行这个代码时,我都不会同时运行其他任何东西,它们都使用相同的IDE(Spyder),所以我不知道为什么会有这种速度差异?如果你能提供任何帮助或原因来解释这一点,那将不胜感激。
编辑:根据Chriss的建议,我尝试在我较慢的计算机上运行Python 2.7并且它也比在同一台计算机上运行3.5时慢得多。因此,这种差异是由Python版本引起的,但是到底是什么引起了这种差异,我仍然想知道。

你尝试过在 Windows 上使用 Python 2.7 或者在 Linux 上使用 Python 3.5 吗? - Chris
@Chris。我刚刚测试了一下,结果发现Windows上的Python 2.7比Python 3.5慢得多,所以我猜这个差异是由于Python版本引起的。然而,到底是什么原因导致了这种差异,我仍然不清楚,希望能找出答案。 - Hadi Khan
这可能是由于Python 2和Python 3之间的range差异引起的。您可以尝试将所有的range调用转换为xrange,并在Python 2上运行它,看看是否有所帮助?我想亲自操作,但原始代码在我的机器上会导致内存错误... - niemmi
@niemmi 我已经尝试将所有的range更改为xrange,但并没有产生任何显着的差异。另外,如果您想运行代码,请尝试减少变量letters中的字母数量(例如,在letters变量中仅使用A、B、C、D、E、F)。 - Hadi Khan
1个回答

5
这是由于Python 2和3之间dict.keys()的差异引起的。在Python 2中,dict.keys()将创建键的副本列表并返回它。在Python 3中,dict.keys()将返回一个类似于set对象的字典视图。检查元素是否可以从list中找到比检查它是否在set中要慢得多,这解释了差异。
如果您进行以下更改,则可以使代码在Python 2和3上运行时间大致相同:
if arrang in dictionary: # Instead of if arrang in dictionary.keys()
    return dictionary[arrang]

非常感谢,这很有效。但是如果我想检查字典中某个元素的存在,我应该使用if element in set(dictionary.values()): ,如果我没有搞错的话。这正确吗? - Hadi Khan
2
@AbdulHadiKhan:是的,你可以使用那个方法,但它不会比只有element in dictionary.values()更快。生成set和检查值是否在list中都具有O(n)时间复杂度。如果您需要快速检查键和值,最好将值存储到单独的容器中,例如Counter(如果您知道值是唯一的,则也可以使用set)。 - niemmi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接