我有一个计算速度慢的程序,我希望调试算法。现在总是重新运行所有内容非常繁琐,我宁愿从程序中间重新启动。你能想到一些聪明的方法来实现这个目标吗?
第一个模糊的想法是定义检查点(我在其中进行函数调用),其中我用pickle和/或sqlite保存本地和大数据(sqlite能够检查中间数据)。后来我可以尝试调用程序告诉它在特定检查点重新启动。但是,为了这个目的,我不想在检查点之间拆分所有代码块。
有人聪明地知道如何解决这个调试问题吗?
答案 0 :(得分:6)
使您的程序更加模块化。理想情况下,主要代码块应该类似于
import config
import my_numerics
import post_processing
my_numerics.configure(config.numerics)
values = my_numerics.run()
post_processing.run(values, config.post_processing)
你明白了。然后很容易制作一个“模拟”数字对象,它返回预先计算的数据,并将其传递给后处理。
for _ in range(lots):
do_slow_thing_one()
for _ in range(many):
do_slow_thing_two()
for _ in range(lots_many)
do_slow_thing_three()
也就是说,你想在第三个循环开始时中断运行中途(不是在结尾)中断数字,而不必重新运行前两个?
如果是这样,即使循环中不包含太多代码,也应该将设计模块化:
input_data = np.load(some_stuff)
stage_one = do_thing_one(input_data)
stage_two = do_thing_two(stage_one)
stage_three = do_thing_three(stage_two)
第一种方法是通过隐式接口在不同阶段之间传输数据;即,局部变量字典。这很糟糕,因为您尚未定义正在使用哪些变量,因此您无法模拟它们以进行测试/调试。第二种方式定义了函数之间的(基本)接口。您现在不再关心do_thing_one
做什么,只要它需要一些输入数据并返回一些输出数据。这意味着要调试do_thing_three
,您只需执行
stage_two = np.load(intermediate_stuff)
stage_three = do_thing_three(stage_two)
只要stage_two
中的数据格式正确,它的来源无关紧要。
答案 1 :(得分:2)
单元测试
这就是单元测试存在的原因。尝试使用小型“示例数据”pyunit
,或使用doctest
获取非常简单的功能。
迷你测试程序
如果由于某种原因你确实需要互动性,我通常会编写一个互动的迷你程序作为有效的单元测试。
def _interactiveTest():
...
import code
code.interact(local=locals())
if __name__=='__main__':
_interactiveTest()
如果您只测试特定部分,您通常可以忽略加载主程序的大块;根据需要调整您的体系结构,以避免初始化程序中不需要的部分。这就是人们可能会说“使你的程序更加模块化”的原因,这就是模块化的意思:程序的小块是独立的,让你重复使用它们(或者在这种情况下)单独加载它们。
在程序中调用口译员
您也可以在程序中的任何位置下载到翻译并传入当地人(如上所示)。这有点像“穷人的调试器”,但我发现它足够有效。 =)
单片算法
去过那里,做到了。有时您的工作流程无法进一步模块化,事情开始变得笨拙。
你制作检查站的直觉是非常好的,与我使用的相同:如果你在翻译环境中工作,或者嵌入翻译,你就不必经常处理这个问题,就像你刚才所说的那样你的脚本。序列化数据可以工作,但它会带来从磁盘读取和写入的大量开销;您希望数据集保留在内存中。然后你可以做类似test1 = algorithm(data), test2 = algorithm(data)
的事情(假设你的算法不是就地算法;如果是,请在每次测试之前使用copy-on-write或复制数据结构)。
如果您在尝试以上所有内容后仍然遇到问题,那么或许您要么:
作为最后的手段,您可以对代码进行分析以找到瓶颈。
其他强>
可能有强大的python调试器。我认为Eclipse有一个。
另外,我个人避免reload <modulename>
,我总是发现它比解决问题更令人头痛。
答案 2 :(得分:2)
Joblib以非常透明的方式处理结果缓存。这是一个例子,来自他们的文档:
>>> from joblib import Memory
>>> mem = Memory(cachedir='/tmp/joblib')
>>> import numpy as np
>>> a = np.vander(np.arange(3))
>>> square = mem.cache(np.square)
>>> b = square(a)
________________________________________________________________________________
[Memory] Calling square...
square(array([[0, 0, 1],
[1, 1, 1],
[4, 2, 1]]))
___________________________________________________________square - 0.0s, 0.0min
>>> c = square(a)
>>> # The above call did not trigger an evaluation because the result is cached
计算结果会自动保存在磁盘上,因此Joblib可能会满足您的需求。
答案 3 :(得分:1)
谷歌搜索指向我CryoPID,如果您在基于Linux的系统上进行开发,这可能会起作用。它声称能够暂停进程并将其保存到文件中,然后稍后重新启动它,即使在另一台计算机上也是如此。我没有测试过它。
答案 4 :(得分:0)
来自BuildBot的想法可能有用。它使用python的reload()重新加载更改模块,然后以半聪明的方式将状态从旧对象移动到新对象。
buildbot进程始终在运行,但可以发出信号从外部重新加载,在这种情况下会发生这种情况。
因此,如果您将算法的中间结果存储在对象中(类似于VTK减少计算的内容),您可以重新加载并重新创建算法对象,让它们重新加载旧数据然后写一些如果python模块实际上发生了变化,则重新运行这些对象的计算的逻辑。
这样,只要磁盘上的文件发生变化,您就可以重新加载进程。请注意,如果存在语法错误或运行时错误,则可能需要重新启动(除非您可以尝试传递并回滚到旧对象)。
所以,是的,需要检查站。但无论如何,拥有这样一个框架可能并不坏。 :)
实际上,只是模块化步骤将允许您将数据缓存到磁盘。这可能会解决这个问题。它肯定会对测试有所帮助,就像@katrielalex说的那样。
答案 5 :(得分:0)
我想到了这一点,最后我写下了我第一次只是模糊的想法。建议重新设计仍然会告诉如何跳过块,加载缓存数据等:
class DebugCheckpoints:
def __init__(self, data, start):
self.checkpoint_passed=False
self.start=start
self.data=data
def __call__(self, variables):
return Checkpoint(self, variables.split())
class CheckpointNotReached(Exception): pass
class Checkpoint:
def __init__(self, debug_checkpoints, variables):
self.variables=variables
self.debug_checkpoints=debug_checkpoints
def tag(self, tag_name):
if self.debug_checkpoints.checkpoint_passed or \
tag_name==self.debug_checkpoints.start:
self.debug_checkpoints.checkpoint_passed=True
else:
raise CheckpointNotReached()
def __enter__(self):
return self
def __exit__(self,exc_type, exc_val, exc_tb):
if exc_type==CheckpointNotReached: # check if the context was supposed to be skipped
for v in self.variables:
globals()[v]=self.debug_checkpoints.data[v] # load globals from data
return True
else:
for v in self.variables:
self.debug_checkpoints.data[v]=globals()[v] # save globals to data
return False
#------------------------------------------------------------------------------
data={"x":1, "w":4} # this is supposed to be any persistent dict
checkpoint=DebugCheckpoints(data, start="B") # start from B, skip block A but still load x and w from data
with checkpoint("x w") as c: # variable x and w is to be loaded
c.tag("A") # this will force cancellation of this block, but x and w will be loaded from data
x=1
w=4
print("Doing A")
with checkpoint("y") as c:
c.tag("B") # as the start is B, this tag will no cancel this block
y=2
print("Doing B")
with checkpoint("z") as c:
c.tag("C")
z=3
print("Doing C")
print(checkpoint.data)
print(x,y,z,w)
这是一个简单的框架,可以将检查点引入代码而无需编写太多内容。因为仅为每个小步骤定义数百个一次性函数可能会编码恐怖,而且函数中的变量是局部的(只需将每5行代码放入一个函数中)。我不想从块中返回所有变量并声明一切全局(这也是装饰器破解实现检查点框架的原因)。
也许我在尝试案例时重新设计了一些电话,但我认为这是一个好的开始。不确定什么应该进入with行和块内部(如.tag
)。我没有设法将检查点例外放入__enter__
?