코드가 너무 길어서 다 올리긴 어려울 것 같아요.
for i in range(100):
데이터 읽기..
nh = len(계산할 대상)
# Multiprocessing -----------------------------------------------------------
m = mp.Manager()
out_q = m.Queue()
print("Looking for galaxies inside {} halos".format(nh))
pool = mp.Pool(processes=8) # I am using ATLAS, initializer=set_affinity_on_worker)
for i in range(nh):
pool.apply_async(mk_gal, args=(h.data[i], out_q, s.info, i, final_gal[i]))
pool.close()
pool.join()
for i in range(nh):
dictout.append(out_q.get(timeout=0.1))
이런 식입니다.
저기서 call하는 mk_gal은 "데이터 읽기"에서 읽은 numpy array를 사용하고요.
모든 process가 전체 데이터에 각각 접근하지만 읽기만 하고 변경을 하지 않기 때문에 shared memory로 사용하는 것으로 알고 있습니다.
(전체 프로세서에 데이터를 복사할 만큼 메모리가 많지는 않아요 데이터가 좀 큽니다.)
돌려보면.. 속도가 영 별로예요.
전체 코어는 48개이고 하이퍼쓰레딩은 꺼놓은 상태입니다.
8코어를 기준으로 pool 없이 1코어 버전으로 돌릴 때랑 전체 계산시간이 비슷하고요 (빨랐다가 느렸다가 하는데 지금은 그정도로 느립니다. 아래 아래 캡쳐에 속도 변화가 나와요.)
top에서는 8 코어가 100%로 나오는데, htop으로 보면 시뻘겋게 나옵니다.
빨간색은 kernel thread를 나타낸다는데 그게 어떤 의미인지는 잘 모르겠네요ㅠ
여튼 kernel thread 비율이 많아지면 계산이 느릿느릿한 것 같더라고요.
처음엔 cpu affinity 때문인가 싶어서 affinity 변경하는(사실 정확한 개념을 모르겠네요..) 것도 넣어봤다가, 그래도 변화가 없어서
아예 OPENBLAS를 지우고 ATLAS를 설치한 다음에 numpy를 다시 설치했습니다. lsof -p로 atlas 라이브러리를 쓰는 것도 확인했고요.
여전히 저 상태입니다.
코어를 16개 쓰면 top에서도 대부분 50% 언저리 값을 보여주고요, 8개씩 두 개를 돌려도 마찬가지입니다.
측정해보진 않았지만 체감상으론 루프를 나누어서 1 코어씩 8개로 돌리든 8코어로 1개를 돌리든, 혹은 (2*2, 2*4, 4*4, 2*16 ,...) 어떤 조합을 하든간에 결국 끝나는 시간에는 큰 차이가 없습니다. 서로 다른 프로그램 사이에서도 간섭이 생기고 속도가 느려진다는게 좀 신기하네요.
더욱 신기한건, 어젯 밤에 오랜만에 이 코드를 돌릴때는 바깥 루프 (100개짜리)가 첫번째는 엄청 빨리 돌았는데 점점 속도가 떨어졌습니다.
루프 한번 돌 때마다 텍스트 파일이 나오는데 파일 생성된 시간이 웃겨요. 처음엔 1분 간격이었다가 서서히 늘어나더니 나중에는 거의 20분이 걸리네요.
(순서는 129에서 63까지 거꾸로 갑니다. )
처음 한 두 번은 데이터가 메모리에 있어서 그랬을 수 있지만, 그 뒤로는 데이터를 읽은 적도 없는데 이렇게 됩니다. 속도가 점차 느려지는걸 보면 메모리 캐시랑 상관 없을 것 같기도 하고요.. 아래에서 파일 생성시간 간격을 보시면 됩니다.
속도가 점점 느려진다는 부분에서 메모리가 새는가 싶은데, 어디서 어떻게 찾아야할지 잘 모르겠네요.
여러가지 의심스러운게 있는데, 한번 나열해볼게요.
1. 계산할때 전체 어레이에 여러번 접근합니다. shared memory라서 프로세서별로 데이터를 복사할 필요는 없지만. 혹시 한 번에 한 프로세서만 접근할 수 있어서 여러 프로세서가 여러번씩 접근해야하면 서로서로 기다리는 일이 생기나요? 테스트해보려는 중인데 고쳐야할 부분이 많아서 우선 물어봅니다.
2. 이건 CPU affinity랑 관련된 것으로 알고있는데 (그래서 잘 이해를 못하는..) numpy가 몇몇 함수에서는 자체적으로 multi thread 계산을 한다고 들었습니다. BLAS 라이브러리를 무얼 쓰든 상관없이 무조건 일어나는 일인가요? 혹시 이것 때문에 내부적으로 thread끼리 코어를 두고 싸우는 중인 것인지..
3. 바깥 루프를 100번 돌고, 1회 도는 동안 계산할 대상은 대략 100여개 됩니다. (nh ~ 100). 싱글 스레드로 계산하면 하나당 몇 초 정도 걸리는 것 같아요. 혹시 너무 작은 계산을 pool로 나누어서 오버헤드가 엄청 커진걸까요?
4. 실질적으로 하나당 계산 크기는 좀 많이 차이납니다. 어떤건 1초만에 끝나기도 하고 어떤건 30초 정도 걸리기도 하고요. multiprocessing에서 한 프로세스가 작은 계산을 빨리 끝내면 같이 생성된 다른 프로세스가 계산이 끝나든 말든 먼저 끝난 프로세스는 다음 일을 받아가는게 맞나요?
5. 어디가 병목인지를 알아보려면 무슨 짓을 해야할지요.. Python 문서에서 The Python Profiler를 읽어보아도 대단히 유용한 기능은 못 찾겠습니다.
예를 들어 메모리 접근하는데 시간이 얼마나 소비되는지, OS 가 thread를 만들고 없애고 옮기는데 시간이 얼마나 걸리는지, 뭐 그런 정보를 알아야할 것 같지만..
본격적으로 프로파일링을 해본 적이 없어서 어떤 툴을 써야할지도 막막하네요..
근 한달에 걸쳐 여러번 해결하려고 시도중인데 차도가 없어서 슬픕니다. ㅠㅠ
SO 검색해서 나오는글은 다 읽어본 것 같네요. 물론 다 이해하지못해서 문제이지만... 엉엉