【问题标题】:pytest (py.test) very slow startup in cygwinpytest(py.test)在cygwin中启动非常慢
【发布时间】:2015-06-10 22:15:56
【问题描述】:

在 cygwin 中,py.test 启动非常缓慢。它看起来不像是收集问题,原因有两个: 相同的测试在 linux 中启动很快。有时,如果在 cygwin 中以足够快的速度重新运行相同的测试,它会在不到 1 秒的时间内启动。运行 time 命令告诉它在 0.4 秒或 11.7 秒内启动,当提供 --collection-only 选项以避免运行实际测试时。我还在钩子pytest_configure()pytest_ignore_collect() 上添加了一个打印,以确保它确实是在收集开始之前。

还有其他问题,linkehow to speed up py.test等。但是我没有看到为什么在cygwin下它很慢,以及如何解决问题。

更新:通过python -m cProfile -s cumulat ~/.../py.test conftest.py 分析运行它。以下是结果中的前 20 名。我倾向于认为这是 cygwin 或 cygwin python 包中的posix.stat 或各个地方的existsisfile 的问题。

   104699 function calls (102659 primitive calls) in 12.223 CPU seconds
   Ordered by: cumulative time
 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
      1    0.016    0.016   12.223   12.223 {execfile}
      1    0.000    0.000   12.223   12.223 <string>:1(<module>)
      1    0.000    0.000   12.207   12.207 py.test:4(<module>)
      1    0.000    0.000   12.051   12.051 config.py:23(main)
  48/22    0.000    0.000   12.051    0.548 core.py:526(_docall)
  48/22    0.000    0.000   12.051    0.548 core.py:520(__call__)
 129/82    0.000    0.000   12.051    0.147 core.py:387(execute)
      1    0.000    0.000   11.926   11.926 config.py:634(pytest_cmdline_parse)
      1    0.000    0.000   11.926   11.926 config.py:70(_prepareconfig)
      1    0.000    0.000   11.926   11.926 config.py:741(parse)
    4/3    0.000    0.000   11.926    3.975 core.py:97(wrapped_call)
    4/3    0.000    0.000   11.926    3.975 core.py:121(__init__)
      1    0.000    0.000   11.911   11.911 config.py:706(_preparse)
     70    0.000    0.000   11.817    0.169 local.py:363(check)
    260   11.817    0.045   11.817    0.045 {posix.stat}  <<<<this one???
      1    0.000    0.000    9.302    9.302 config.py:698(_initini)
      1    0.000    0.000    9.286    9.286 config.py:896(determine_setup)
    188    0.000    0.000    9.286    0.049 genericpath.py:15(exists)  <<<<this one???
     18    0.000    0.000    6.861    0.381 config.py:845(exists)  <<<<this one???
      1    0.000    0.000    6.861    6.861 config.py:851(getcfg)  <<<<this one???
      1    0.000    0.000    2.531    2.531 config.py:694(pytest_load_initial_conftests)
      1    0.000    0.000    2.531    2.531 config.py:477(setinitial)
      1    0.000    0.000    2.531    2.531 config.py:503(_try_load_conftest)
     13    0.000    0.000    2.531    0.195 config.py:511(getconftestmodules)
     32    0.000    0.000    2.531    0.079 genericpath.py:26(isfile)  <<<<this one???
      8    0.000    0.000    2.425    0.303 common.py:261(exists)
      1    0.000    0.000    0.156    0.156 pytest.py:4(<module>)
      1    0.000    0.000    0.125    0.125 main.py:73(wrap_session)
      1    0.000    0.000    0.125    0.125 config.py:615(do_configure)
      1    0.000    0.000    0.125    0.125 main.py:115(pytest_cmdline_main)

关于分析中排序的注意事项:很难弄清楚要使用什么关键字。在pstats.py 中添加一行以打印出sort_arg_defs 可以给出一个想法:

$ cat -n /usr/lib/python2.x.x/pstats.py
214         sort_arg_defs = self.get_sort_arg_defs()
215         sort_tuple = ()
216         self.sort_type = ""
217         connector = ""
218         for word in field:
219             sort_tuple = sort_tuple + sort_arg_defs[word][0]
220             self.sort_type += connector + sort_arg_defs[word][1]
221             connector = ", "

结论总结:格式错误的文件路径,如//setup.py(应该是/setup.py,开头没有双斜杠),揭示了四个软件部分之一的问题: pytest config.determine_setup()、pytest config、cygwin python 库或 cygwin posix 实现使用的 py.path。

【问题讨论】:

    标签: python pytest


    【解决方案1】:

    问题在于 pytest 搜索 //pytest.ini//tox.ini//setup.cfg//setup.py。它们每个都导致genericpath.exists()genericpath.isfile() 消耗大约2.5 秒。

    解决方法是将下面的行添加到genericpath.exists()genericpath.isfile() 以跳过这四个特定路径。

    if path.startswith(r'//'):
        return False
    

    另一种解决方法是修改_pytest/config.py,使其不会在搜索路径中形成那些双斜杠。

    用于找出确切问题的代码粘贴在下面。设置myshow = True,这样它将显示它正在搜索的每个文件是如何消耗时间的。

    $ diff -u /usr/lib/python2.6/genericpath.py genericpath.py
    --- /usr/lib/python2.6/genericpath.py   2012-06-09 08:33:12.000000000 -0700
    +++ genericpath.py      2015-06-11 11:46:33.674285900 -0700
    @@ -9,14 +9,29 @@
     __all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
                'getsize', 'isdir', 'isfile']
    
    +myshow = False
    +import time as mytime
    +mybasetime = mytime.time()
    +def myshowtime():
    +    currenttime = mytime.time()
    +    tmdiff = currenttime - mybasetime
    +    global mybasetime
    +    mybasetime = currenttime
    +    return tmdiff
    
     # Does a path exist?
     # This is false for dangling symbolic links on systems that support them.
     def exists(path):
         """Test whether a path exists.  Returns False for broken symbolic links"""
    +    pretime = myshowtime()
    +    if path.startswith(r'//'):
    +        if myshow: print "\n  genericpath exists  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
    +        return False
         try:
             st = os.stat(path)
    +        if myshow: print "\n  genericpath exists  %8.3f %8.3f True  " % (pretime, myshowtime()), " ", path, "\n"
         except os.error:
    +        if myshow: print "\n  genericpath exists  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
             return False
         return True
    
    @@ -25,9 +40,15 @@
     # for the same path ono systems that support symlinks
     def isfile(path):
         """Test whether a path is a regular file"""
    +    pretime = myshowtime()
    +    if path.startswith(r'//'):
    +        if myshow: print "\n  genericpath isfile  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
    +        return False
         try:
             st = os.stat(path)
    +        if myshow: print "\n  genericpath isfile  %8.3f %8.3f True  " % (pretime, myshowtime()), " ", path, "\n"
         except os.error:
    +        if myshow: print "\n  genericpath isfile  %8.3f %8.3f False " % (pretime, myshowtime()), " ", path, "\n"
             return False
         return stat.S_ISREG(st.st_mode)
    

    【讨论】:

    • 另一种不编辑/破解安装的方法是让 cygwin 调用 Windows 本机 python 而不是 cygwin python。也就是说,如果你的windows python安装在c:/python/path,将py.test替换成c:/python/path/Scripts/python.exe c:/ython/path/Scripts/py.test-script.py
    • 总之,这里学到的是 cygwin 的 Python 上的 Unix 风格的绝对路径很慢。缩短路径的相对路径或使用非 cygwin Python 不会遇到同样的问题。
    • 洛基,这个评论太宽泛了。具体问题是格式错误的文件路径,如//setup.py(应该是/setup.py),揭示了四个软件部分之一的问题:pytest config.determine_setup()、py.path、cygwin python、或 cygwin posix 实现。
    • 绝对路径如果不以双斜杠开头,则效果很好。
    • 关于绝对路径或相对路径。要记住的主要事情是每个路径组件都使用了时间。因此,您想使用路径组件数量最少的那个。在文件系统之间切换存在时间损失,并且绝对路径比相对路径更容易出现这种情况。特别是在 cygwin 上,使用路径似乎很慢。双斜杠加剧了这个问题,但很遗憾,您显然需要牢记这一点。
    猜你喜欢
    • 2012-01-24
    • 2011-10-28
    • 2015-07-11
    • 1970-01-01
    • 1970-01-01
    • 2012-04-18
    • 2014-12-01
    • 2018-08-22
    • 2017-01-22
    相关资源
    最近更新 更多