2015-11-06 41 views
1

我正在嘗試使用posix_spawn而不是fork/exec來獲得一些性能增益。我目前的項目是用Python編寫的,所以我使用this Python綁定。此外,我嘗試了它的一些分支,之後我在Cython中編寫了自己的posix_spawn綁定(以消除一些依賴關係),但獲得了幾乎相同的結果。捕獲過程輸出時的Posix_spawn性能

當我只需要在不捕獲stdout/stderr的情況下運行進程時,確實有顯着的提速。但是當我確實需要它時(對於我的項目來說這是必要的),調用變得和fork/exec調用一樣慢。此外,它依賴於分配內存的數量與fork/exec相同。即使進程實際上沒有產生任何輸出,它也會發生 - 我檢查了/ bin/true。我仍然無法解釋這種行爲。對於fork/exec(通過子進程模塊),只要輸出不是太大,是否讀取過程輸出並沒有顯着差異。

這裏是我的測試腳本(進口並省略分析的代碼)不分配額外的內存

# test.py 
def spawn_no_out(args): 
    command = args[0] 
    pid = posix_spawn(command, args) 
    status, rusage = exits(pid) 

def spawn(args): 

    # Prepare pipes to capture stdout and stderr 
    stdout_read, stdout_write = os.pipe() 
    stderr_read, stderr_write = os.pipe() 
    fa = FileActions() 
    fa.add_dup2(stdout_write, 1) 
    fa.add_close(stdout_read) 
    fa.add_dup2(stderr_write, 2) 
    fa.add_close(stderr_read) 

    # Spawn the process 
    command = args[0] 
    pid = posix_spawn(command, args, file_actions=fa) 

    # Read and close file descriptors 
    os.close(stdout_write) 
    os.close(stderr_write) 
    status, rusage = exits(pid) 
    out = os.fdopen(stdout_read) 
    err = os.fdopen(stderr_read) 
    return out, err 

def fork(args): 
    return Popen(args, stdout=PIPE, stderr=PIPE).communicate() 

def fork_no_out(args): 
    return subprocess.call(args) 

def run_benchmark(func, args, count): 
    for _ in xrange(count): 
     func(args) 
    print "%s: %ds" % (func.__name__, time.time() - start) 

def main(): 
    # Reads from stdout the number of process spawns and size of allocated memory 
    args = ["/bin/true"] 
    count = int(sys.argv[1]) if len(sys.argv) > 1 else 1000 
    mem_size = int(sys.argv[2]) if len(sys.argv) > 2 else 10000000 
    some_allocated_memory = range(mem_size) 
    for func in [spawn, spawn_no_out, fork, fork_no_out]: 
     run_benchmark(func, args, count) 


if __name__ == "__main__": 
    main() 

測試輸出:與1000萬個整數列表分配的內存

 
./test.py 10000 1 
spawn: 34s 
     3754834 function calls (3754776 primitive calls) in 33.517 seconds 

    Ordered by: internal time, cumulative time 
    List reduced from 144 to 5 due to restriction 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    10000 15.475 0.002 15.475 0.002 {posix.wait4} 
    10000 5.850 0.001 5.850 0.001 {_cffi__x2c5d2681xf492c09f.posix_spawn} 
    10000 3.217 0.000 12.750 0.001 /usr/local/lib/python2.7/dist-packages/posix_spawn-0.1-py2.7.egg/posix_spawn/_impl.py:75(posix_spawn) 
    10000 2.242 0.000 33.280 0.003 ./test.py:23(spawn) 
    660000 1.777 0.000 3.159 0.000 /usr/local/lib/python2.7/dist-packages/cffi/api.py:212(new) 



spawn_no_out: 14s 
     3340013 function calls in 14.631 seconds 

    Ordered by: internal time, cumulative time 
    List reduced from 25 to 5 due to restriction 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    10000 7.466 0.001 7.466 0.001 {posix.wait4} 
    10000 2.012 0.000 2.012 0.000 {_cffi__x2c5d2681xf492c09f.posix_spawn} 
    10000 1.658 0.000 6.994 0.001 /usr/local/lib/python2.7/dist-packages/posix_spawn-0.1-py2.7.egg/posix_spawn/_impl.py:75(posix_spawn) 
    650000 1.640 0.000 2.919 0.000 /usr/local/lib/python2.7/dist-packages/cffi/api.py:212(new) 
    650000 0.496 0.000 0.496 0.000 {_cffi_backend.newp} 



fork: 40s 
     840094 function calls in 40.745 seconds 

    Ordered by: internal time, cumulative time 
    List reduced from 53 to 5 due to restriction 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    10000 19.460 0.002 19.460 0.002 {posix.read} 
    10000 6.505 0.001 6.505 0.001 {posix.fork} 
    10081 4.667 0.000 4.667 0.000 {built-in method poll} 
    10000 2.773 0.000 30.190 0.003 /usr/lib/python2.7/subprocess.py:1187(_execute_child) 
    10000 0.814 0.000 32.996 0.003 /usr/lib/python2.7/subprocess.py:650(__init__) 



fork_no_out: 38s 
     330013 function calls in 38.488 seconds 

    Ordered by: internal time, cumulative time 
    List reduced from 36 to 5 due to restriction 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    10000 18.179 0.002 18.179 0.002 {posix.read} 
    10000 6.904 0.001 6.904 0.001 {posix.waitpid} 
    10000 6.613 0.001 6.613 0.001 {posix.fork} 
    10000 2.633 0.000 28.976 0.003 /usr/lib/python2.7/subprocess.py:1187(_execute_child) 
    10000 0.880 0.000 30.070 0.003 /usr/lib/python2.7/subprocess.py:650(__init__) 

測試輸出(不得不減少通話次數):

./test.py 1000 10000000 
spawn: 20s 
     379834 function calls (379776 primitive calls) in 20.022 seconds 

    Ordered by: internal time, cumulative time 
    List reduced from 144 to 5 due to restriction 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    1000 10.022 0.010 10.022 0.010 {posix.wait4} 
    1000 8.705 0.009 8.705 0.009 {_cffi__x2c5d2681xf492c09f.posix_spawn} 
    1000 0.334 0.000 9.412 0.009 /usr/local/lib/python2.7/dist-packages/posix_spawn-0.1-py2.7.egg/posix_spawn/_impl.py:75(posix_spawn) 
    1000 0.269 0.000 19.998 0.020 ./test.py:18(spawn) 
    66000 0.174 0.000 0.318 0.000 /usr/local/lib/python2.7/dist-packages/cffi/api.py:212(new) 



spawn_no_out: 1s 
     334013 function calls in 1.480 seconds 

    Ordered by: internal time, cumulative time 
    List reduced from 25 to 5 due to restriction 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    1000 0.755 0.001 0.755 0.001 {posix.wait4} 
    1000 0.198 0.000 0.198 0.000 {_cffi__x2c5d2681xf492c09f.posix_spawn} 
    1000 0.171 0.000 0.708 0.001 /usr/local/lib/python2.7/dist-packages/posix_spawn-0.1-py2.7.egg/posix_spawn/_impl.py:75(posix_spawn) 
    65000 0.167 0.000 0.298 0.000 /usr/local/lib/python2.7/dist-packages/cffi/api.py:212(new) 
    65000 0.050 0.000 0.050 0.000 {_cffi_backend.newp} 



fork: 18s 
     84067 function calls in 18.554 seconds 

    Ordered by: internal time, cumulative time 
    List reduced from 53 to 5 due to restriction 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    1000 9.399 0.009 9.399 0.009 {posix.read} 
    1000 7.815 0.008 7.815 0.008 {posix.fork} 
    1054 0.414 0.000 0.414 0.000 {built-in method poll} 
    1000 0.274 0.000 17.626 0.018 /usr/lib/python2.7/subprocess.py:1187(_execute_child) 
    1000 0.078 0.000 17.871 0.018 /usr/lib/python2.7/subprocess.py:650(__init__) 



fork_no_out: 18s 
     33013 function calls in 18.732 seconds 

    Ordered by: internal time, cumulative time 
    List reduced from 36 to 5 due to restriction 

    ncalls tottime percall cumtime percall filename:lineno(function) 
    1000 9.467 0.009 9.467 0.009 {posix.read} 
    1000 8.020 0.008 8.020 0.008 {posix.fork} 
    1000 0.603 0.001 0.603 0.001 {posix.waitpid} 
    1000 0.280 0.000 17.910 0.018 /usr/lib/python2.7/subprocess.py:1187(_execute_child) 
    1000 0.072 0.000 18.000 0.018 /usr/lib/python2.7/subprocess.py:650(__init__) 

Withou分析結果是一樣的。 正如我們所看到的,當我們在調用posix_spawn時和在沒有捕獲過程輸出的情況下,性能有很大差異(1.4s vs 20s!)。沒有額外的沉重電話 - posix.wait4只需要更多的時間。 我在這裏做錯了什麼?有人知道爲什麼會發生這種情況,以及如何爲posix_spawn獲得更好的性能?

P.S.在Linux Mint 17和CentOS 6.5上測試 - 結果相同。

UPDATE: 同樣的性能下降發生,即使我們通過空FileActions反對的posix_spawn,實際上並沒有閱讀標準輸出/標準錯誤:

def spawn(args): 
    command = args[0] 
    pid = posix_spawn(command, args, file_actions=FileActions()) 
    status, rusage = exits(pid) 

回答

0

從函數的源代碼,鏈接在alexgorin答案,在我看來這個標誌規則在其他參數:

POSIX_SPAWN_USEVFORK

嘗試,讓我知道

+0

是的,那是我用來強制posix_spawn使用vfork的。 – alexgorin