代码之家  ›  专栏  ›  技术社区  ›  coredump

如何减少C API和Python可执行文件之间的执行时间差异?

  •  0
  • coredump  · 技术社区  · 6 年前

    python3 或者通过使用 libpython3 提供不同的执行时间。

    $ time PYTHONPATH=. ./simple
    real    0m6,201s
    user    1m3,680s
    sys     0m0,212s
    
    $ time PYTHONPATH=. python3 -c 'import test; test.run()'
    real    0m5,193s
    user    0m53,349s
    sys     0m0,164s
    

    (删除 __pycache__ 运行间隔似乎没有影响)

    蟒蛇3 用脚本比较快;在我的实际用例中,与从嵌入式解释器中运行的相同脚本相比,该因子快1.5。

    我想(1)了解差异从何而来,(2)是否可以使用嵌入式解释器获得相同的性能?(例如使用cython目前不是一个选项)。

    simple.cpp
    #include <Python.h>
    
    int main()
    {
            Py_Initialize();
            const char* pythonScript = "import test; test.run()";
            int result = PyRun_SimpleString(pythonScript);
            Py_Finalize();
            return result;
    }
    

     g++ -std=c++11 -fPIC $(python3-config --cflags) simple.cpp \
     $(python3-config --ldflags) -o simple
    
    test.py
    import sys
    sys.stdout = open('output.bin', 'bw')
    import mandel
    def run():
        mandel.mandelbrot(4096)
    

    调整版本从 benchmarks-game's Mandlebrot (见 License )

    from contextlib import closing
    from itertools import islice
    from os import cpu_count
    from sys import stdout
    
    def pixels(y, n, abs):
        range7 = bytearray(range(7))
        pixel_bits = bytearray(128 >> pos for pos in range(8))
        c1 = 2. / float(n)
        c0 = -1.5 + 1j * y * c1 - 1j
        x = 0
        while True:
            pixel = 0
            c = x * c1 + c0
            for pixel_bit in pixel_bits:
                z = c
                for _ in range7:
                    for _ in range7:
                        z = z * z + c
                    if abs(z) >= 2.: break
                else:
                    pixel += pixel_bit
                c += c1
            yield pixel
            x += 8
    
    def compute_row(p):
        y, n = p
    
        result = bytearray(islice(pixels(y, n, abs), (n + 7) // 8))
        result[-1] &= 0xff << (8 - n % 8)
        return y, result
    
    def ordered_rows(rows, n):
        order = [None] * n
        i = 0
        j = n
        while i < len(order):
            if j > 0:
                row = next(rows)
                order[row[0]] = row
                j -= 1
    
            if order[i]:
                yield order[i]
                order[i] = None
                i += 1
    
    def compute_rows(n, f):
        row_jobs = ((y, n) for y in range(n))
    
        if cpu_count() < 2:
            yield from map(f, row_jobs)
        else:
            from multiprocessing import Pool
            with Pool() as pool:
                unordered_rows = pool.imap_unordered(f, row_jobs)
                yield from ordered_rows(unordered_rows, n)
    
    def mandelbrot(n):
        write = stdout.write
    
        with closing(compute_rows(n, compute_row)) as rows:
            write("P4\n{0} {0}\n".format(n).encode())
            for row in rows:
                write(row[1])
    
    0 回复  |  直到 6 年前
        1
  •  2
  •   coredump    5 年前

    很明显,时差来自于与 libpython 静态vs.动态。在旁边的一个Makefile中 python.c (来自参考实现),以下内容构建了解释器的静态链接版本:

    snake: python.c
        g++ \
        -I/usr/include/python3.6m \
        -pthread \
        -specs=/usr/share/dpkg/no-pie-link.specs \
        -specs=/usr/share/dpkg/no-pie-compile.specs \
        \
        -Wall \
        -Wformat \
        -Werror=format-security \
        -Wno-unused-result \
        -Wsign-compare \
        -DNDEBUG \
        -g \
        -fwrapv \
        -fstack-protector \
        -O3 \
        \
        -Xlinker -export-dynamic \
        -Wl,-Bsymbolic-functions \
        -Wl,-z,relro \
        -Wl,-O1 \
        python.c \
        /usr/lib/python3.6/config-3.6m-x86_64-linux-gnu/libpython3.6m.a \
        -lexpat \
        -lpthread \
        -ldl \
        -lutil \
        -lexpat \
        -L/usr/lib \
        -lz \
        -lm \
        -o $@
    

    /usr/lib/.../libpython3.6m.a 具有 -llibpython3.6m -L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu )


    尾声

    速度上的差异是存在的,但并不是我最初问题的全部答案;实际上,“较慢”的解释器是在特定的LD_预加载环境下执行的,该环境改变了系统时间函数的行为方式,从而使