在我学习 Python(特别是 Jython,如果这里的区别很重要的话)时,我正在编写一个简单的终端游戏,该游戏使用技能和基于这些技能水平的骰子掷骰来确定尝试操作的成功/失败。我希望最终能在更大的游戏项目中使用此代码。
在压力测试下,代码使用了 .5GB 的内存,似乎需要相当长的时间才能得到结果(~50 秒)。可能只是任务真的那么密集,但作为菜鸟,我敢打赌我只是在低效地做事。任何人都可以给一些提示吗:
如何提高这段代码的效率
以及如何以更 Pythonic 的方式编写此代码?
import random def DiceRoll(maxNum=100,dice=2,minNum=0): return sum(random.randint(minNum,maxNum) for i in xrange(dice)) def RollSuccess(max): x = DiceRoll() if(x <= (max/10)): return 2 elif(x <= max): return 1 elif(x >= 100-(100-max)/10): return -1 return 0 def RollTesting(skill=50,rolls=10000000): cfail = 0 fail = 0 success = 0 csuccess = 0 for i in range(rolls+1): roll = RollSuccess(skill) if(roll == -1): cfail = cfail + 1 elif(roll == 0): fail = fail + 1 elif(roll == 1): success = success + 1 else: csuccess = csuccess + 1 print "CFails: %.4f. Fails: %.4f. Successes: %.4f. CSuccesses: %.4f." % (float(cfail)/float(rolls), float(fail)/float(rolls), float(success)/float(rolls), float(csuccess)/float(rolls)) RollTesting()
编辑 - 这是我的代码:
from random import random
def DiceRoll():
return 50 * (random() + random())
def RollSuccess(suclim):
x = DiceRoll()
if(x <= (suclim/10)):
return 2
elif(x <= suclim):
return 1
elif(x >= 90-suclim/10):
return -1
return 0
def RollTesting(skill=50,rolls=10000000):
from time import clock
start = clock()
cfail = fail = success = csuccess = 0.0
for _ in xrange(rolls):
roll = RollSuccess(skill)
if(roll == -1):
cfail += 1
elif(roll == 0):
fail += 1
elif(roll == 1):
success += 1
else:
csuccess += 1
stop = clock()
print "Last time this statement was manually updated, DiceRoll and RollSuccess totaled 12 LOC."
print "It took %.3f seconds to do %d dice rolls and calculate their success." % (stop-start,rolls)
print "At skill level %d, the distribution is as follows" % (skill)
print "CFails: %.4f. Fails: %.4f. Successes: %.4f. CSuccesses: %.4f." % (cfail/rolls, fail/rolls, success/rolls, csuccess/rolls)
RollTesting(50)
输出:
Last time this statement was manually updated, DiceRoll and RollSuccess totaled 12 LOC.
It took 6.558 seconds to do 10000000 dice rolls and calculate their success.
At skill level 50, the distribution is as follows
CFails: 0.0450. Fails: 0.4548. Successes: 0.4952. CSuccesses: 0.0050.
值得注意的是,这并不等价,因为我将随机计算更改得足以产生明显不同的输出(原来应该是 0-100,但我忘了除以骰子的数量)。内存使用量现在看起来是 ~.2GB。此外,之前的实现无法进行 1 亿次测试,我已经在多达 10 亿次测试中运行了这个(它花了 8 分钟,内存使用情况似乎没有太大差异)。
请您参考如下方法:
您正在执行 1000 万次循环。仅循环成本可能占总时间的 10%。然后,如果整个循环不能立即放入缓存,它可能会进一步减慢速度。
有没有办法避免在 Python 中执行所有这些循环?是的,您可以用 Java 完成。
最明显的方法是实际编写和调用 Java 代码。但您不必那样做。
列表理解或由 native 内置驱动的生成器表达式也将在 Java 中执行循环。因此,除了更紧凑和更简单之外,这还应该更快:
attempts = (RollSuccess(skill) for i in xrange(rolls))
counts = collections.Counter(attempts)
cfail, fail, success, csuccess = counts[-1], counts[0], counts[1], counts[2]
不幸的是,虽然这在 Jython 2.7b1 中似乎更快,但实际上在 2.5.2 中更慢。
另一种加速循环的方法是使用矢量化库。不幸的是,我不知道人们用什么 Jython 来做这个,但是在 CPython 中使用 numpy,它看起来像这样:
def DiceRolls(count, maxNum=100, dice=2, minNum=0):
return sum(np.random.random_integers(minNum, maxNum, count) for die in range(dice))
def RollTesting(skill=50, rolls=10000000):
dicerolls = DiceRolls(rolls)
csuccess = np.count_nonzero(dicerolls <= skill/10)
success = np.count_nonzero((dicerolls > skill/10) & (dicerolls <= skill))
fail = np.count_nonzero((dicerolls > skill) & (dicerolls <= 100-(100-skill)/10))
cfail = np.count_nonzero((dicerolls > 100-(100-skill)/10)
这将速度提高了大约 8 倍。
我怀疑在 Jython 中,事情远不如 numpy 好,并且您需要导入 Java 库,例如 Apache Commons numerics 或 PColt,并找出 Java-vs。 -Python 问题你自己......但最好搜索和/或询问而不是假设。
最后,您可能想要使用不同的解释器。 CPython 2.5 或 2.7 在这里似乎与 Jython 2.5 没有太大区别,但它确实意味着您可以使用 numpy 获得 8 倍的改进。与此同时,PyPy 2.0 的速度提高了 11 倍,没有任何变化。
即使您需要在 Jython 中执行主程序,如果您的速度慢到足以使启动新进程的成本相形见绌,您也可以将其移至通过 subprocess< 运行的单独脚本。例如:
下标.py:
# ... everything up to the RollTesting's last line
return csuccess, success, fail, cfail
skill = int(sys.argv[1]) if len(sys.argv) > 1 else 50
rolls = int(sys.argv[2]) if len(sys.argv) > 2 else 10000000
csuccess, success, fail, cfail = RollTesting(skill, rolls)
print csuccess
print success
print fail
print cfail
主脚本.py:
def RollTesting(skill, rolls):
results = subprocess32.check_output(['pypy', 'subscript.py',
str(skill), str(rolls)])
csuccess, success, fail, cfail = (int(line.rstrip()) for line in results.splitlines())
print "CFails: %.4f. Fails: %.4f. Successes: %.4f. CSuccesses: %.4f." % (float(cfail)/float(rolls), float(fail)/float(rolls), float(success)/float(rolls), float(csuccess)/float(rolls))
(我使用 subprocess32 模块获取 check_output 的反向端口,这在 Python 2.5、Jython 或其他方面不可用。您也可以只用 borrow the source 获取 check_output 来自 2.7 的实现。)
请注意,Jython 2.5.2 在 subprocess 中有一些严重的错误(将在 2.5.3 和 2.7.0 中修复,但今天这对您没有帮助)。但幸运的是,它们不会影响此代码。
在快速测试中,开销(主要是产生一个新的解释器进程,但也有编码参数和结果等)增加了 10% 以上的成本,这意味着我只得到了 9 倍的改进,而不是 11 倍。这在 Windows 上会更糟。但这还不足以抵消任何需要大约一分钟才能运行的脚本的好处。
最后,如果你正在做更复杂的事情,你可以使用 execnet ,它包装了 Jython<->CPython<->PyPy,让您可以在代码的每个部分中使用最好的东西,而不必做所有显式的 subprocess 事情。
