I'm writing an interface to help me profiling python scripts. I borrowed the code to exec a python script from profile.py source code. I realized that when I profile the same code two consecutive times, it returns me a different number of function calls the second time. For example, executing the following code on myscript.py:

from cProfile import Profile
import sys
import os.path

for i in range(3):
    prof = Profile()

    progname = 'myscript.py'
    sys.path.insert(0, os.path.dirname(progname))
    with open(progname, 'rb') as fp:
        code = compile(fp.read(), progname, 'exec')
    globs = {
            '__file__': progname,
            '__name__': '__main__',
            '__package__': None,
            '__cached__': None,
            }
    prof.runctx(code, globs, None)
    prof.create_stats()
    print(len(prof.stats))

gives me

511
30
30

as output. Why is the number of called function mush smaller on the second time? Which number is the right number? What can I do get get the same result both time?


myscript.py looks like:

import numpy
import numpy.linalg

if __name__ == '__main__':

    r = numpy.random.rand(1000, 1000)
    numpy.linalg.inv(r)
有帮助吗?

解决方案

It seems the reason why the function call count is different is because modules imported by myscript.py aren't imported again the second time I run the code.

A first way to get consistent results is to import myscript.py before performing the profiling. However, it means that if modules I import perform some task when imported, this won't be profiled.

prof = Profile()

progname = 'myscript.py'
sys.path.insert(0, os.path.dirname(progname))
modname, _ = os.path.splitext(os.path.basename(progname))
__import__(modname, globals(), locals(), [], 0)
with open(progname, 'rb') as fp:
    code = compile(fp.read(), progname, 'exec')
globs = {
        '__file__': progname,
        '__name__': '__main__',
        '__package__': None,
        '__cached__': None,
        }
prof.runctx(code, globs, None)
prof.create_stats()
print(len(prof.stats))

The second way I found is to delete all modules registered when I executed my script. The advantage is that if I modify the source while my GUI is running, it will be reloaded with the changes. The drawback I have now is that some atexit registered handlers now crash because needed modules are deleted before:

prof = Profile()

progname = 'myscript.py'
sys.path.insert(0, os.path.dirname(progname))
with open(progname, 'rb') as fp:
    code = compile(fp.read(), progname, 'exec')
globs = {
        '__file__': progname,
        '__name__': '__main__',
        '__package__': None,
        '__cached__': None,
        }
modules = sys.modules.copy()
prof.runctx(code, globs, None)
newmodes = [modname for modname in sys.modules if modname not in modules]
for modname in newmodes:
    del sys.modules[modname]
prof.create_stats()
print(len(prof.stats))

Finally, the best way I found is to execute the profiling in a separate process:

import concurrent.futures
import marshal
from cProfile import Profile
from pstats import Stats
import sys

progname = 'myscript.py'
with concurrent.futures.ProcessPoolExecutor() as executor:
    future = executor.submit(_run, progname)
    stats = Stats()
    stats.stats = marshal.loads(future.result())
    stats.get_top_level_stats()

def _run(progname):
    sys.path.insert(0, os.path.dirname(progname))

    with open(progname, 'rb') as fp:
        code = compile(fp.read(), progname, 'exec')
    globs = {
        '__file__': progname,
        '__name__': '__main__',
        '__package__': None,
    }
    prof = Profile()
    prof.runctx(code, globs, None)
    prof.create_stats()
    return marshal.dumps(prof.stats)
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top