2013-03-03 3 views
0

유일한 동기화가 대기열에서 작업을 가져 오는 한 모든 코어 수를 100 %로 사용하는 일련의 CPU 바인딩 프로세스가 있습니다.Python RLock IO-Bound?

파일 시스템에서 디렉토리를 업데이트 할 때 최악의 시나리오를 피하기 위해 RLock을 추가하자마자 프로세스가 IO 바인딩 된 것처럼 CPU/코어 사용률이 60 %로 떨어집니다.

설명은 무엇입니까?

이것은 전반적인 속도와 관련이 없습니다. CPU/코어 활용에 관한 내용이므로 Python 2/3, Cython 또는 PyPy는 중요하지 않습니다.

업데이트 : 나는 내 자신의 질문에 부분적으로 대답했다. 필자의 특별한 경우에 대한 최종 해결책은 파일 시스템이 액세스 된 방식을 수정하여 동기화가 필요하지 않도록하는 것입니다 ("일종의"맵/축소).

답변

0

측정 결과는 RLock이 I/O 바인딩되지 않음을 나타냅니다. RLock (또는 Semaphore)과 동기화되는 코드는 동기화되지 않은 코드보다 약 6 배 느린 것처럼 보이지만 가장 간단한 I/O를 수행하는 코드는 몇 배 더 느립니다.

다음은 Python과 PyPy에서 모두 RLock 오버 헤드를 측정하는 데 사용한 단일 프로세스 코드입니다. 나는 여전히 RLock의 오버 헤드가 단일 프로세스보다 높거나 오버 헤드가 CPU/코어 활용도를 유지하지 못하는 이유를 이해하지 못한다. 그러나 결과는 표준 동기화 프리미티브를 사용하는 것이 롤링하는 것보다 훨씬 효율적이라는 것을 보여준다. 개인적인.

# lock.py 
import sys 
import os 
import timeit 
from random import random 
from multiprocessing import RLock, Semaphore 

N=8*1024*1024 

lock = RLock() 
semaphore = Semaphore() 

def touch(fname, times=None): 
    if not os.path.isfile(fname): 
     open(fname, 'wa').close() 
    else: 
     with file(fname, 'a'): 
      os.utime(fname, times) 

def wolock(): 
    return random() 

def wlock(): 
    with lock: 
     return random() 

def wsem(): 
    with semaphore: 
     return random() 

def wfile(): 
    os.path.isfile('lock') 
    touch('lock') 
    try: 
     return random() 
    finally: 
     os.unlink('lock') 

def run(get): 
    result = [] 
    for i in xrange(N): 
     result.append(get()) 
    return result 

def main(): 
    t0 = timeit.timeit('lock.wolock()', setup='import lock', number=N) 
    print '%8.3f %8.2f%% %s' % (t0, 100, 'unsynchronized') 
    t1 = timeit.timeit('lock.wlock()', setup='import lock', number=N) 
    print '%8.3f %8.2f%% %s' % (t1, 100*t1/t0, 'rlock') 
    t2 = timeit.timeit('lock.wsem()', setup='import lock', number=N) 
    print '%8.3f %8.2f%% %s' % (t2, 100*t2/t0, 'semaphore') 
    t = timeit.timeit('lock.wfile()', setup='import lock', number=N) 
    print '%8.3f %s' % (t, 'file system') 

if __name__ == '__main__': 
    main() 
+0

독립 엔터티 (즉, 프로세스 또는 호스트)간에 동기화하려면 변경 사항이있을 경우 서로 계속 요청해야합니다. 이것은 모든 엔티티가 동일한 데이터를 볼 수 있도록 캐시를 우회하는 것과 같이 많은 미묘한 비용이 듭니다. 캐싱되지 않은 메모리에 액세스하면 성능이 저하됩니다! – RobM

+0

@RobM 주어진 예제는 단일 프로세스를 사용하며 리소스 경합은 없습니다. – Apalala

1

그것은 모든 multiprocessingRLock을 구현 한 방법에 따라 달라집니다. 나는 멀티 프로세싱 호스트 전체에서 작동 할 수 있다는 것을 알고 있습니다. 이는 동기화 프리미티브가 소켓에서 작동 할 수 있음을 의미합니다. 이것이 사실이라면 많은 (가변적 인) 대기 시간을 초래할 것입니다.

그래서 실험을했습니다.

이 실행
#!/usr/bin/env python 
import multiprocessing 
from time import sleep 

lock = multiprocessing.RLock() 

def noop(myname): 
    # nonlocal lock 
    sleep(0.5) 
    print myname, "acquiring lock" 
    with lock: 
     print myname, "has lock" 
     sleep(0.5) 
    print myname, "released lock" 

sProc1 = multiprocessing.Process(target=noop, args=('alice',)) 
sProc2 = multiprocessing.Process(target=noop, args=('bob',)) 

sProc1.start() 
sProc2.start() 

sProc1.join() 
sProc2.join() 

는 출력이 보입니다 :

여기에 (모든 잠금이 같은 프로세스 내에서 어디에있는 빠른 경로를 방지하기 위해) 하나 개 이상의 프로세스에서 사용하고 RLock의 검은 제비 갈매기 예입니다 다음과 같이하십시오 :

alice acquiring lock 
alice has lock 
bob acquiring lock 
alice released lock 
bob has lock 
bob released lock 

위도 있으므로 strace을 통한 시스템 호출 추적으로 실행하십시오.

아래의 명령에서 -ff 옵션은 도구가 "fork()"을 따르는 호출 즉, 주 인터페이스에서 시작한 모든 프로세스를 추적합니다. 간결하게하기 위해 나는 -e trace=futex,write도 사용하고 있습니다.이 필터는 게시하기 전에 작성한 결론에 따라 출력을 필터링합니다. 일반적으로 -e 옵션없이 실행하고 텍스트 편집기/grep을 사용하여 사실 이후에 발생한 문제를 조사합니다. 인쇄의 패턴에서

# strace -ff -e trace=futex,write ./traceme.py 
futex(0x7fffeafe29bc, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 1, NULL, 7fb92ac6c700) = -1 EAGAIN (Resource temporarily unavailable) 
futex(0x7fb92a8540b0, FUTEX_WAKE_PRIVATE, 2147483647) = 0 
futex(0x7fb92aa7131c, FUTEX_WAKE_PRIVATE, 2147483647) = 0 
write(3, "\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 32) = 32 
Process 25873 attached 
Process 25874 attached 
Process 25872 suspended 
[pid 25873] write(1, "alice acquiring lock\n", 21alice acquiring lock 
) = 21 
[pid 25873] write(1, "alice has lock\n", 15alice has lock 
) = 15 
[pid 25874] write(1, "bob acquiring lock\n", 19bob acquiring lock 
) = 19 
[pid 25874] futex(0x7fb92ac91000, FUTEX_WAIT, 0, NULL <unfinished ...> 
[pid 25873] futex(0x7fb92ac91000, FUTEX_WAKE, 1 <unfinished ...> 
[pid 25874] <... futex resumed>)  = 0 
[pid 25873] <... futex resumed>)  = 1 
[pid 25874] write(1, "bob has lock\n", 13 <unfinished ...> 
bob has lock 
[pid 25873] write(1, "alice released lock\n", 20 <unfinished ...> 
alice released lock 
[pid 25874] <... write resumed>)  = 13 
[pid 25873] <... write resumed>)  = 20 
Process 25872 resumed 
Process 25873 detached 
[pid 25872] --- SIGCHLD (Child exited) @ 0 (0) --- 
Process 25872 suspended 
[pid 25874] write(1, "bob released lock\n", 18bob released lock 
) = 18 
Process 25872 resumed 
Process 25874 detached 
--- SIGCHLD (Child exited) @ 0 (0) --- 

은 ( write()) 메시지 및 블록 나중에 이력서 futex 전화, 그것은 RLockfutex, 또는 "빠른 사용자 공간 뮤텍스"를 사용하여 구현되는 것이 분명 보인다. 이름에서 알 수 있듯이 이것은 동기화에 좋은 선택입니다.

futex과 같이 시스템 호출에서 프로세스가 차단되면 모든 의도와 목적으로 프로세스가 I/O를 차단합니다.

이 모든 내용은 multiprocessing.RLock이 효율적이고 어떻게 설계 되었는가를 나타냅니다. 따라서 동기화를 사용할 때 응용 프로그램의 성능이 예상보다 낮 으면 알고리즘이 잘못 될 가능성이 있습니다.

+0

스스로 대답하기 전에 내 대답을 읽지 않은 것 같지만 분석 해줘서 고마워. 'RLock'은 효율적으로 구현할 수 있지만, 프로그램에서 보았던 것만 큼 비싸다. – Apalala