106

『고성능 파이썬』 - 맛보기

Embed Size (px)

Citation preview

Page 1: 『고성능 파이썬』 - 맛보기
Page 2: 『고성능 파이썬』 - 맛보기
Page 3: 『고성능 파이썬』 - 맛보기
Page 4: 『고성능 파이썬』 - 맛보기

고성능 파이썬 : 파이썬 성능 잠재력을 끌어내는 실용적인 개발 전략서

초판발행 2016년 8월 10일

지은이 미샤 고렐릭, 이안 오스발트 / 옮긴이 김영근, 오현석 / 펴낸이 김태헌

펴낸곳 한빛미디어 (주) / 주소 서울시 마포구 양화로 7길 83 한빛미디어(주) IT출판부 전화 02 – 325 – 5544 / 팩스 02 – 336 – 7124등록 1999년 6월 24일 제10 – 1779호 / ISBN 978 – 89 – 6848 – 499 – 5 93000

총괄 전태호 / 책임편집 김창수 / 기획 이복연 / 편집 백지선

디자인 표지·내지 강은영, 조판 백지선

영업 김형진, 김진불, 조유미 / 마케팅 박상용, 송경석, 변지영 / 제작 박성우, 김정우

이 책에 대한 의견이나 오탈자 및 잘못된 내용에 대한 수정 정보는 한빛미디어(주)의 홈페이지나 아래 이메일로

알려주십시오. 잘못된 책은 구입하신 서점에서 교환해 드립니다. 책값은 뒤표지에 표시되어 있습니다.

한빛미디어 홈페이지 www.hanbit.co.kr / 이메일 [email protected]

© 2016 HANBIT Media Inc.

Authorized Korean translation of the English edition of High Performance Python, ISBN 9781449361594 © 2014 Micha Gorelick and Ian OzsvaldThis translation is published and sold by permission of O’Reilly Media, Inc., which owns or controls

all rights to publish and sell the same.이 책의 저작권은 오라일리와 한빛미디어 (주)에 있습니다.

저작권법에 의해 한국 내에서 보호를 받는 저작물이므로 무단 전재와 복제를 금합니다.

지금 하지 않으면 할 수 없는 일이 있습니다.

책으로 펴내고 싶은 아이디어나 원고를 메일 ( [email protected] ) 로 보내주세요. 한빛미디어(주)는 여러분의 소중한 경험과 지식을 기다리고 있습니다.

표지 동물은 큰창머리독사fer-de-lance다. 불어로 ‘창 날’을 의미하는데, 서인도 제도의 마르티니크 섬에서 주

로 서식하는 종Bothrops lanceolatus이 이 이름을 사용한다. 세인트 루시아 창머리독사Bothrops caribbaeus,

보통창머리독사Bothrops atrox, 테르시오펠로Bothrops asper 등의 다른 창머리독사도 이 이름으로 불

리기도 하는데, 모두 살모사 종류로 눈과 콧구멍 사이에 열을 감지하는 기관이

있다.

테르시오펠로나 보통창머리독사는 아메리카에 서식하는 다른 독사속

Bothrops 중에서도 사람을 죽음에 이르게 할 정도로 치명적인 독을 지니고

있는 독사의 대부분을 차지한다. 남아메리카의 커피 농장이나 바나나 농

장의 노동자는 보통창머리독사에 물릴까 두려워하며 이 뱀들이 설치류로

배를 채우길 바랄 뿐이다. 중앙아메리카의 강과 하천 둑에서 발견되는 테르시

오펠로는 매우 공격적이라 일광욕을 즐길 때가 아니라면 매우 위험하다.

오라일리 책의 표지에 등장하는 동물 중 다수가 멸종 위기에 처해 있다. 이들 모두가 우리 세상에 소중한 존재다. 이 동물

들을 돕고 싶다면 animals.oreilly.com에 방문해보자.

표지 그림은 우드Wood의 『Animate Creation』에서 가져왔다.

| 표지 설명 |

Page 5: 『고성능 파이썬』 - 맛보기
Page 6: 『고성능 파이썬』 - 맛보기

4

지은이 미샤 고렐릭 Micha Gorelick

2023년 화성에 착륙한 최초의 인류이자 시간 여행에 대한 공로로 2046년 노벨상을 받았다. 그가

개발한 신기술이 의도와 다르게 사용되고 있는 사실에 분노하여 2012년으로 되돌아와서 물리학 박

사 과정을 그만두고 데이터와 사랑에 빠졌다. bitly에서 실시간 데이터 처리와 관련된 업무를 했다.

사람들에게 미래 기술을 알리고 싶은 욕망에 미치광이 과학자 역할로 패스트포워드랩을 함께 시작

해 고성능 데이터 분석과 관련된 다양한 프로젝트에 컨설팅을 하고 있다. 1857년 센트럴 파크에 그

의 생을 기리는 기념비가 세워졌다.

지은이 이안 오스발트 Ian Ozsvald

10년이 넘는 파이썬 경력을 가지고 있으며 ModelInsight.io에서 데이터 과학자이자 파이썬 강사

로 활동 중이다. 그는 PyCon과 PyData 콘퍼런스에서 강사로 활동하였으며 영국에서 10여 년간

인공지능과 고성능 계산에 대한 컨설팅을 하고 있다. 개인 블로그(IanOzsvald.com )를 운영하고

있으며 맛 좋은 맥주 한 잔에 큰 행복을 느낀다. 파이썬뿐만 아니라 C++, 리눅스와 윈도우 개발, 스

토리지 시스템, 자연어 처리 및 텍스트 처리, 기계 학습, 데이터 시각화에도 관심이 많다. 파이썬 중

심의 비디오 학습 사이트인 ShowMeDo.com의 공동창업자다.

지은이·옮긴이 소개

Page 7: 『고성능 파이썬』 - 맛보기

5

옮긴이 김영근 파이썬 소프트웨어 재단 이사

애플 II에서 베이직으로 처음 프로그래밍을 시작했고, 장래 희망은 항상 프로그래머라고 말하고 다

니다 정신 차리고 보니 어느덧 개발 경력이 10년을 훌쩍 넘긴 중년(?) 개발자가 되었다. 리눅스 커

뮤니티에서 오랫동안 활동했으며 임베디드 환경에서부터 미들웨어, 스마트폰 애플리케이션에 이

르기까지 다양한 분야에서 개발했다. 파이썬 소프트웨어 재단의 이사로 활동 중이며 2014년부터

‘PyCon 한국’을 만들고 있는 사람 중 한 명이다. 현재는 스마트스터디에서 가장 비밀스러운 조직인

D9에 소속되어 있다. 한빛미디어에서 『리눅스 시스템 프로그래밍(개정2판)』, 『파이썬 라이브러리

를 활용한 데이터 분석』을 번역했다.

옮긴이 오현석

KAIST에서 전산학 학사와 석사 학위(프로그래밍 언어 연구실)를 취득했다. 삼성메디슨, 비트앤펄

스 등에서 UI 개발자와 개발 팀장을 지냈고, 호주에서 프리랜서 C++/풀스택 개발자로 일하고 있

다. 웹이나 모바일 등의 분야에서 값 중심의 프로그래밍을 통해 오류 발생 가능성이 더 적으면서 유지

보수가 편한 프로그램을 작성하는 방법과 이를 지원하는 여러 도구를 만드는 일에 관심이 많다. 최

근에는 스칼라와 파이썬을 사용한 대규모 병렬 처리나 액터를 활용한 분산 처리 등을 공부하는 중이

다. 『프로그래밍 스칼라』(한빛미디어), 『스칼라와 기계 학습』, 『파이썬 로보틱스』, 『스칼라 동시성 프

로그래밍』, 『Programming in Scala (Second Edition ) 한국어판』, 『스칼라 동시성 프로그래밍』

(이상 에이콘), 『시스템 성능 분석과 최적화』, 『파이썬 인 프랙티스』(이상 위키북스) 등을 번역했다.

Page 8: 『고성능 파이썬』 - 맛보기

6

파이썬은 이제 이루 말할 수 없을 정도로 다양한 분야에서 폭넓게 사용되고 있다. 그중에서도

특히 데이터 분석과 과학 계산 분야에서 파이썬의 성장은 괄목할 만하다. 프로그래밍을 업으

로 삼고 있는 사람이 아니더라도 쉽게 익힐 수 있는 간결한 문법과 더불어 튼튼한 생태계를 유

지하고 있는 활동적인 커뮤니티의 지원 덕분에, 다양한 배경의 전문가들이 유입되고 또 그렇게

유입된 새로운 사람들이 다시 커뮤니티에 기여하는 선순환을 반복하고 있다.

단순한 취미 목적이든 연구 목적이든, 일단 파이썬을 사용하기로 하고 어느 정도 경험이 쌓이

면 과연 내가 파이썬을 제대로 사용하고 있는 것인지, 코드는 충분히 효율적으로 작성했는지

의문이 생기기 마련이다. 이런 작은 의문의 씨앗이 자라면 어느 순간 스스로 작성한 코드가 못

생겨 보이고 코드에 자신이 없어지게 된다. 이를 예방하기 위해서는 다른 사람이 작성한 코드

를 최대한 많이 읽어보는 게 도움이 되는데, 시간과 노력이 많이 드는 편이라 잘 정리된 중고급

서가 있다면 책을 통해 노하우를 얻는 편이 훨씬 경제적이다. 아직 파이썬 관련 중고급서가 많

지 않은 국내 상황에서 유쾌한 두 저자의 다양한 경험이 그대로 녹아 있는 『고성능 파이썬』이

그 역할을 해줄 것이라 기대한다.

원서가 처음 출간된 시점에는 파이썬 2와 3 중 딱히 어느 한쪽으로 사용자가 기울지 않은 상황

이었지만 이제는 모두가 파이썬 3을 선택하고 있고(또 그래야만 한다!) 커뮤니티에서도 적극

적으로 파이썬 3 지원에 많은 노력을 기울이고 있다. 그 결과 현재는 거의 모든 라이브러리가

파이썬 3을 지원하며 아직까지도 지원하지 않는 일부 라이브러리는 죽은 프로젝트로 인식되어

새로운 대체 라이브러리가 개발되어 나오는 상황이다. 이에 맞춰 파이썬 2를 기준으로 작성된

원서의 예제 코드를 파이썬 3에서 실행할 수 있도록 수정했다.

일과 연애 그리고 번역까지 병행하는 것은 시간적으로나 체력적으로 무척 힘든 일이라는 점을

새삼 깨달았다. 여자 친구가 없어서 다행이다... 함께 고생한 오현석 님과 이복연 님께 진심으

로 감사한 마음을 전하고 싶다. 이 책이 독자 여러분께 조금이나마 도움이 된다면 모두 오현석

님과 이복연 님의 덕분이다. 원서의 코드를 파이썬 3로 변경하는 것에 흔쾌히 동의해준 두 저

자께도 감사하다.

김영근

옮긴이의 말

Page 9: 『고성능 파이썬』 - 맛보기

7

파이썬은 배우기 쉽다. 여러분은 이미 생각한 대로 코드를 작성할 수 있겠지만, 지금보다 더욱

빠르게 실행하고 싶어서 이 책을 선택했을 것이다. 코드를 쉽게 수정하고 빠르게 아이디어를

확인할 수 있다는 사실은 재밌다. 하지만 개발하기 쉬운 것과 원하는 만큼 실행 속도를 빠르게

만드는 것 사이에는 반비례 관계가 있어서 종종 한숨이 나오곤 한다. 하지만 비법은 분명히

있다.

누군가는 일련의 프로세스를 빠르게 실행해야 하고, 다른 누군가는 멀티코어 아키텍처, 클러스

터, 또는 GPU를 제대로 활용하지 못해 애를 먹고 있을 것이다. 또 다른 누군가는 신뢰성을 잃

지 않으면서도 주어진 예산 안에서 필요한 만큼의 프로세스를 사용할 수 있는 확장 가능한 시

스템을 원한다. 어떤 사람은 코딩 실력이 부족함을 느끼기도 한다. 종종 다른 언어에서 차용한

기법이 예제에서 본 것만큼 자연스럽지 않은 경우도 있다.

이 책에서는 이와 같은 주제를 다룬다. 성능상의 병목을 인지하여 더 빠르고 확장성이 뛰어난

해법을 구하기 위한 실용적인 지침을 제공한다. 독자보다 먼저 문제에 직면했던 사람들이 고군

분투했던 이야기도 소개하여 동일한 문제를 가지고 힘 빼지 않도록 도와준다.

파이썬은 빠른 개발과 배포, 그리고 확장성 있는 시스템을 위한 최적의 언어다. 파이썬 커뮤니

티의 많은 사람들은 여러분이 직면한 문제에 집중할 수 있도록 파이썬을 더 나은 언어로 만들

고자 여러분 대신 노력하고 있다.

대상 독자

이 책은 파이썬을 충분히 사용해보았고 특정 부분이 왜 느린지에 대해 생각해본 경험이 있으

며, 이를 위한 해법으로 거론되는 Cython, numpy, PyPy와 같은 기술에 대해서 들어본 적이

있는 독자를 대상으로 쓰였다. 또한 다른 언어로 프로그래밍해본 경험이 있고, 그래서 성능을

개선하는 방법이 여러 가지임을 알고 있는 독자를 대상으로 한다.

이 책은 주로 CPU 성능과 관련된 문제를 겪고 있는 독자를 대상으로 하지만 데이터 전송이나

들어가며

Page 10: 『고성능 파이썬』 - 맛보기

8

메모리 관련 해법도 제시한다. 바로 과학자, 엔지니어, 금융 분석가, 연구원을 괴롭히는 문제들

이다.

또한 데이터 이동 문제를 포함하여 손쉽게 성능을 끌어올릴 수 있는 PyPy와 같은 JIT 컴파일

러를 사용하는 방법 등 웹 개발자가 직면할 수 있는 문제도 다루고 있다.

C 언어(혹은 C++나 자바)를 알고 있다면 더 좋겠지만 필수는 아니다. 파이썬의 대표적인 인

터프리터인 CPython (명령줄에서 python을 입력했을 때 일반적으로 실행되는 표준 인터프

리터)은 C 언어로 작성되었으므로 내부 구현이나 라이브러리에는 C 코드가 난무한다. 그렇지

만 C 언어를 몰라도 되는 기법 또한 다양하게 다루고 있다.

CPU나 메모리 구조, 데이터 버스 같은 저수준의 지식이 있다면 좋겠지만 역시 필수는 아니다.

대상이 아닌 독자

이 책은 중고급 파이썬 프로그래머를 위한 책이다. 의욕이 넘치는 초보 파이썬 프로그래머도

잘 따라올 수 있겠지만, 이 책을 읽기 전에 파이썬 기초를 먼저 다지기를 추천한다.

이 책은 저장 장치 시스템에 대한 최적화는 다루지 않는다. SQL이나 NoSQL 병목으로 고생하

고 있다면 아마도 이 책은 그에 대한 해답을 줄 수는 없을 것이다.

이 책에서 배우게 될 내용

이 책의 두 저자는 연구와 현업에서 대용량 데이터를 다루기 위해 확장성 좋은 아키텍처가 필

요하고 결과를 빠르게 도출해야 하는 일을 오랫동안 해왔다. 그 과정에서 실수로부터 힘들게

배운 경험을 독자들에게 전해주려 한다.

각 장은 해당 장을 읽고 나면 알게 될 내용으로 시작한다. 다 읽고도 여전히 고개가 갸우뚱하다

면 개정판에서 고칠 수 있도록 알려주길 바란다!

Page 11: 『고성능 파이썬』 - 맛보기

9

이 책에서 다루는 내용은 다음과 같다.

● 컴퓨터의 저수준 동작 방식을 이해하여 실제로 어떤 일이 일어나는지 알 수 있다.

● 리스트와 튜플 - 기본적인 자료구조인 리스트와 튜플의 미묘한 의미적인 차이와 속도 차이

● 사전과 셋 - 중요한 자료구조인 사전과 셋의 메모리 할당 전략과 접근 알고리즘

● 이터레이터 - 이터레이터를 통해 데이터를 스트리밍하는 방법과 더 파이썬다운 코드를 작성하는 방법

● 순수 파이썬 방식의 접근 - 파이썬과 모듈을 효율적으로 사용하는 방법

● 행렬과 numpy - numpy 라이브러리를 고수처럼 사용하기

● 컴파일과 JIT - 빠른 처리를 위해 기계 코드로 컴파일하기, 프로파일링 결과 반영하기

● 데이터를 효과적으로 옮기기 위한 동시적인 접근 방법

● multiprocessing - 병렬 컴퓨팅을 위해 기본 라이브러리인 multiprocessing 모듈을 이용하는 다양한 방

법, 효과적으로 numpy 행렬 공유하기, 그리고 프로세스 간 통신(IPC ) 비용과 장점

● 클러스터 컴퓨팅 - multiprocessing을 사용한 코드를 수정하여 로컬 또는 클러스터에서 실행하는 방법

● 메모리 아껴 쓰기 - 비싼 컴퓨터를 구입하지 않고 대용량 문제를 풀기 위한 접근법

● 현업에서 얻은 교훈 - 현업에서 고군분투했던 문제에서 얻는 교훈

파이썬 2.7

파이썬 2.7 버전은 과학과 공학 분야에서 대세인 버전이다. 그리고 이 분야 사람들의 절대다수

는 유닉스 기반(주로 리눅스와 맥)의 64비트 시스템을 사용한다. 64비트 시스템은 많은 용량

의 RAM을 활용할 수 있고 유닉스 기반 시스템은 익숙한 방법과 방식으로 애플리케이션을 설

정하고 배포할 수 있도록 해준다.

윈도우 사용자라면 약간 긴장하는 편이 좋겠다. 이 책에서 소개할 내용의 대부분은 잘 동작하

겠지만, 특정 운영 체제에 국한되는 일부 내용은 여러분이 직접 윈도우에서의 해법을 찾아야

할 것이다. 윈도우를 사용하는 데 가장 큰 골칫거리는 모듈 설치와 관련한 내용인데, 스택오버

플로(http://StackOverflow.com ) 같은 사이트에서 해답을 구할 수 있을 것이다. 윈도우에서는

버추얼박스(https://www.VirtualBox.org/ ) 같은 가상 머신에 리눅스를 설치하고 거기서 여러 가

Page 12: 『고성능 파이썬』 - 맛보기

10

지를 실험해보는 게 좋다.

윈도우 사용자는 Anaconda, Canopy, Python (x,y ) 또는 Sage 같은 패키지 솔루션을 사용

하는 것도 좋은 방법이다. 이 패키지 배포판은 윈도우뿐만 아니라 리눅스와 맥 사용자도 쉽게

설치할 수 있다.

파이썬 3으로 갈아타기

파이썬 3은 파이썬의 미래이고, 모두가 파이썬 3으로 갈아타는 중이다. 그렇기는 하지만 파이

썬 2.7은 앞으로도 수년간 계속 사용되긴 할 것이다(2004년에 나온 파이썬 2.4를 아직도 사용

하는 곳도 있다). 파이썬 2.7은 2020년까지만 지원된다.

라이브러리 개발자에게 파이썬 3.3 이후 버전으로 갈아타는 일은 꽤나 머리 아픈 일이기 때문

에 해당 라이브러리를 파이썬 3.x 버전으로 이식하는 과정이 더딘 것이 사실이다. 따라서 일반

사용자도 파이썬 3을 수용하는 데 주저할 수밖에 없었다. 이는 문자열과 유니코드 문자열을 섞

어 쓴 복잡한 애플리케이션을 파이썬 3에서 유니코드와 바이트 구현으로 변경해야 하는 번거

로움 때문에 초래된 현상이다.

일반적으로 믿을 수 있는 라이브러리를 기반으로 결과를 다시 뽑아내려 한다면 굳이 최신 버전

을 따라가지 않아도 괜찮다. 이 책의 저자는 앞으로도 몇 년간은 파이썬 2.7 버전을 사용하려

한다.

이 책에서 사용한 코드의 대부분은 살짝만 고치면 파이썬 3.3 이후 버전에서도 잘 실행된다1 (가장 큰 차이점은 파이썬 3에서는 print가 함수라는 점이다). 종종 파이썬 3.3 이후 버전에

서 제공하는 개선사항에 대해서도 살펴볼 것이다. 그중 개발자를 곤란하게 만들지도 모르는 변

경사항은 / 연산자가 파이썬 2.7 버전에서는 정수 나눗셈이었지만, 파이썬 3에서는 실수 나눗

1 옮긴이_ ‘옮긴이의 말’에서도 밝혔듯이 원서 출간 후 상황이 빠르게 변해, 저자의 동의를 얻어 한글 번역서에서는 예제 코드와 설명을 파이

썬 3에 맞게 수정해두었다.

Page 13: 『고성능 파이썬』 - 맛보기

11

셈으로 변경되었다는 점이다. 물론 좋은 개발자가 되려면 잘 짜인 단위 테스트를 통해서 기존

에 작성한 코드를 테스트해보고 이와 같은 문제를 해결해야 한다.

scipy와 numpy는 2010년부터 파이썬 3을 지원한다. matplotlib은 2012년부터 파이썬 3

을 지원하기 시작했고, scikit-learn은 2013년부터, NLTK는 2014년부터 파이썬 3을 지원한

다. 장고Django는 2013년부터 파이썬 3을 지원한다. 파이썬 3 지원에 관한 각 프로젝트의 상황

은 해당 프로젝트의 저장소와 그룹에서 확인할 수 있다. 옛날 코드를 파이썬 3으로 포팅할 계

획이 있다면 각 프로젝트가 어떤 과정을 거쳐 파이썬 3을 지원했는지 살펴보는 것도 큰 도움이

될 것이다.

새로운 프로젝트를 시작한다면 파이썬 3을 사용해보기를 권장한다. 하지만 이제 막 파이썬 3

을 지원하기 시작한 라이브러리를 사용할 때는 사용자도 아직은 별로 없고 혹시 모를 버그와

씨름하느라 힘든 시간을 보낼 수도 있으니 주의를 기울이자. __future__ 모듈에 대해서 알아

보고 코드를 파이썬 3과 호환되게 해두면 나중에 업그레이드하기 쉬워질 것이다.

‘Porting Python 2 Code to Python 3’(http://bit.ly/pyporting )와 ‘Porting to Python 3:

An in-depth guide’(http://python3porting.com )는 파이썬 3으로 갈아타기에 대한 훌륭한

지침이다. Anaconda 또는 Canopy 같은 배포판을 사용하면 파이썬 2와 3을 동시에 사용할

수 있고 포팅 작업을 단순화할 수 있다.

예제 웹사이트

코드 예제와 데이터셋은 다음 주소에서 내려받을 수 있다.

•한국어판(파이썬 3 기준) : https://github.com/scari/high_performance_python

•영어판(파이썬 2.7 기준) : https://github.com/mynameisfiber/high_performance_python

Page 14: 『고성능 파이썬』 - 맛보기

12

감사의 말

귀중한 의견과 도움을 준 제이크 밴더플래스, 브라이언 그레인저, 댄 포어만-매키, 카이린 데

일, 존 몽고메리, 제이미 매튜, 캘빈 자일스, 윌리엄 윈터, 크리스티앙 슈 오스빅, 발타자르 루

버롤, 맷 ‘스네이크’ 라이퍼슨, 패트릭 쿠퍼, 마이클 스커판에게 고마운 마음을 전한다. 10개월

간 이 책을 쓰느라 다른 일에 소홀했던 우리를 넓은 아량으로 이해해준 가족과 친구들에게 감

사의 말을 전한다. 또한 오라일리와 함께 작업할 수 있어서 무척 즐거웠다.

힘들게 얻은 경험을 ‘현장에서 얻은 교훈’에 공유해준 벤 잭슨, 라딤 레후렉, 세바스찬 트렙카,

알렉스 켈리, 마르코 타시크, 앤드류 고드윈에게 감사의 말을 전한다.

Page 15: 『고성능 파이썬』 - 맛보기

13

지은이·옮긴이 소개 ..................................................................................................................4

옮긴이의 말 .............................................................................................................................6

들어가며 .................................................................................................................................7

CHAPTER 1 고성능을 위한 파이썬 이해하기

1.1 컴퓨터 시스템의 기본 .....................................................................................................22

1.1.1 연산 장치 ...............................................................................................................22

1.1.2 기억 장치 ...............................................................................................................25

1.1.3 통신 계층 ...............................................................................................................28

1.2 기본 구성 함께 보기 .......................................................................................................30

1.2.1 이상적인 컴퓨팅 vs. 파이썬 가상 머신 .........................................................................30

1.3 파이썬을 쓰는 이유 ........................................................................................................34

CHAPTER 2 프로파일링으로 병목 지점 찾기

2.1 효과적으로 프로파일링하기 ............................................................................................38

2.2 쥘리아 집합 ...................................................................................................................40

2.3 전체 쥘리아 집합 계산하기 .............................................................................................43

2.4 시간을 측정하는 간단한 방법 - print와 데코레이터 ..........................................................46

2.5 유닉스 time 명령어를 이용한 간단한 시간 측정 ...............................................................50

2.6 cProfile 모듈 사용하기 ..................................................................................................52

2.7 line_profiler로 한 줄씩 측정하기 .....................................................................................58

2.8 memory_profiler로 메모리 사용량 진단하기 ....................................................................64

2.9 heapy로 힙 메모리 살펴보기 ..........................................................................................70

2.10 dowser로 변수 생성을 실시간으로 확인하기 .................................................................73

2.11 dis 모듈로 CPython의 바이트코드 조사하기 .................................................................76

2.11.1 방식에 따른 복잡도 ................................................................................................78

CONTENTS

Page 16: 『고성능 파이썬』 - 맛보기

14

2.12 최적화 중에 단위 테스트하기 .......................................................................................81

2.12.1 no-op @profile 데코레이터 ...................................................................................81

2.13 성공적인 코드 프로파일링 전략 ....................................................................................84

2.14 마치며 ........................................................................................................................85

CHAPTER 3 리스트와 튜플

3.1 더 효율적인 탐색 ...........................................................................................................90

3.2 리스트와 튜플 ................................................................................................................93

3.2.1 리스트 : 동적 배열 ...................................................................................................94

3.2.2 튜플 : 정적 배열 ......................................................................................................97

3.3 마치며 ...........................................................................................................................99

CHAPTER 4 사전과 셋

4.1 사전과 셋의 동작 원리................................................................................................. 105

4.1.1 삽입과 검색 ......................................................................................................... 105

4.1.2 삭제 ................................................................................................................... 109

4.1.3 크기 변경 ............................................................................................................ 110

4.1.4 해시 함수와 엔트로피 ............................................................................................ 110

4.2 사전과 네임스페이스 ................................................................................................... 115

4.3 마치며 ........................................................................................................................ 118

CHAPTER 5 이터레이터와 제너레이터

5.1 무한급수와 이터레이터 ................................................................................................ 123

5.2 제너레이터의 지연 실행............................................................................................... 125

5.3 마치며 ........................................................................................................................ 130

CONTENTS

Page 17: 『고성능 파이썬』 - 맛보기

15

CHAPTER 6 행렬과 벡터 연산

6.1 문제 소개 ................................................................................................................... 132

6.2 파이썬의 리스트만으로 충분할까? ................................................................................ 137

6.2.1 너무 잦은 할당에 의한 문제 .................................................................................... 138

6.3 메모리 단편화 ............................................................................................................. 142

6.3.1 perf 이해하기 ...................................................................................................... 144

6.3.2 perf 결과 반영하기 ............................................................................................... 146

6.3.3 numpy 입문 ....................................................................................................... 147

6.4 numpy를 이용한 확산 방정식 해법 .............................................................................. 151

6.4.1 메모리 할당과 제자리 연산 ..................................................................................... 154

6.4.2 선택적 최적화 : 고칠 부분 찾기 ............................................................................... 158

6.5 numexpr : 제자리 연산을 더 빠르고 간편하게 쓰기 ...................................................... 162

6.6 최적화 검증 scipy ....................................................................................................... 164

6.7 마치며 ........................................................................................................................ 166

CHAPTER 7 C 언어로 컴파일하기

7.1 가능한 속도 개선의 종류 ............................................................................................. 172

7.2 JIT 대 AOT 컴파일러 .................................................................................................. 174

7.3 타입 정보가 실행 속도에 영향을 주는 이유 ................................................................... 175

7.4 C 컴파일러 사용하기 .................................................................................................. 176

7.5 쥘리아 집합 예제 다시 보기 ......................................................................................... 176

7.6 Cython ...................................................................................................................... 177

7.6.1 Cython을 사용하여 순수 파이썬 코드 컴파일하기 ...................................................... 178

7.6.2 코드 블록을 분석하기 위한 Cython 애너테이션 ......................................................... 180

7.6.3 타입 지정 ............................................................................................................ 183

7.7 Shed Skin ................................................................................................................. 187

Page 18: 『고성능 파이썬』 - 맛보기

16

7.7.1 확장 모듈 만들기 .................................................................................................. 188

7.7.2 메모리 복사 비용 .................................................................................................. 191

7.8 Cython과 numpy ....................................................................................................... 192

7.8.1 한 컴퓨터에서에서 OpenMP를 사용해 병렬화하기 .................................................... 194

7.9 Numba ...................................................................................................................... 196

7.10 Pythran ................................................................................................................... 198

7.11 PyPy ....................................................................................................................... 200

7.11.1 쓰레기 수집 방식의 차이 ...................................................................................... 202

7.11.2 PyPy 실행 및 모듈 설치 ...................................................................................... 202

7.12 각 기술을 언제 사용할까? ......................................................................................... 204

7.12.1 떠오르는 다른 프로젝트들 .................................................................................... 206

7.12.2 GPU에 대해 ..................................................................................................... 207

7.12.3 다른 컴파일러 프로젝트에 거는 기대 ...................................................................... 208

7.13 외부 함수 인터페이스 ............................................................................................... 208

7.13.1 ctypes ............................................................................................................ 210

7.13.2 cffi .................................................................................................................. 213

7.13.3 f2py ................................................................................................................ 216

7.13.4 CPython 모듈 .................................................................................................. 219

7.14 마치며 ..................................................................................................................... 223

CHAPTER 8 동시성

8.1 비동기 프로그래밍 소개............................................................................................... 226

8.2 순차적 크롤러 ............................................................................................................. 230

8.3 gevent ....................................................................................................................... 232

8.4 tornado ..................................................................................................................... 237

8.5 AsyncIO .................................................................................................................... 242

8.6 데이터베이스 예제 ...................................................................................................... 245

CONTENTS

Page 19: 『고성능 파이썬』 - 맛보기

17

8.7 마치며 ........................................................................................................................ 248

CHAPTER 9 multiprocessing 모듈

9.1 multiprocessing 모듈 소개 .......................................................................................... 255

9.2 몬테카를로 방식을 사용해 원주율 추정하기 .................................................................. 257

9.3 프로세스와 스레드를 사용해 원주율 추정하기 ............................................................... 259

9.3.1 파이썬 객체 사용하기 ............................................................................................ 259

9.3.2 병렬 시스템의 난수 ............................................................................................... 267

9.3.3 numpy 사용하기 ................................................................................................. 268

9.4 소수 찾기 ................................................................................................................... 271

9.4.1 작업 대기열 ......................................................................................................... 277

9.5 프로세스 간 통신을 사용해 소수 검증하기 .................................................................... 283

9.5.1 순차적 해법 ......................................................................................................... 288

9.5.2 단순한 풀 해법 ..................................................................................................... 288

9.5.3 덜 단순한 풀 해법 ................................................................................................. 290

9.5.4 Manager.Value를 플래그로 사용하기 ..................................................................... 291

9.5.5 레디스를 플래그로 사용하기 ................................................................................... 293

9.5.6 RawValue를 플래그로 사용하기 ............................................................................. 296

9.5.7 mmap을 플래그로 사용하기 (1) ............................................................................. 297

9.5.8 mmap을 플래그로 사용하기 (2) ............................................................................. 299

9.6 multiprocessing과 numpy 데이터 공유하기 ................................................................. 302

9.7 파일과 변수 접근 동기화하기 ....................................................................................... 310

9.7.1 파일 락 걸기 ........................................................................................................ 310

9.7.2 값에 락 걸기 ........................................................................................................ 315

9.8 마치며 ........................................................................................................................ 319

Page 20: 『고성능 파이썬』 - 맛보기

18

CHAPTER 10 클러스터와 작업 대기열

10.1 클러스터링의 이점 .................................................................................................... 322

10.2 클러스터링의 단점 .................................................................................................... 323

10.2.1 월스트리트, 형편없는 업그레이드 전략으로 4억 6천 2백만 불을 손해보다 ..................... 325

10.2.2 스카이프가 겪은 24시간 동안의 전 세계 서비스 장애 ................................................ 326

10.3 일반적인 클러스터 설계 ............................................................................................ 326

10.4 클러스터화한 해법을 어떻게 시작할 것인가? .............................................................. 327

10.5 클러스터 사용 시 고통을 피하는 방법 ........................................................................ 329

10.6 세 가지 클러스터링 솔루션 ........................................................................................ 330

10.6.1 Parallel Python 모듈을 간단한 로컬 클러스터에 사용하기 ........................................ 331

10.6.2 IPython Parallel을 사용해 연구 지원하기 .............................................................. 333

10.7 강건한 프로덕션 클러스터링을 위한 NSQ .................................................................. 339

10.7.1 대기열 .............................................................................................................. 340

10.7.2 출판자/구독자 .................................................................................................... 341

10.7.3 분산 소수 계산 ................................................................................................... 343

10.8 살펴볼 만한 다른 클러스터링 도구들 .......................................................................... 347

10.9 마치며 ..................................................................................................................... 349

CHAPTER 11 RAM 아껴 쓰기

11.1 원시 타입 객체는 비싸다 ........................................................................................... 352

11.1.1 array 모듈은 원시 객체를 더 적은 비용으로 저장한다................................................ 354

11.2 컬렉션이 사용하는 RAM 이해하기 ............................................................................. 357

11.3 바이트와 유니코드 .................................................................................................... 360

11.4 RAM에 수많은 텍스트를 효율적으로 저장하기 ........................................................... 361

11.4.1 8백만 개의 토큰을 여러 컨테이너에 넣어보기 .......................................................... 362

11.5 RAM을 덜 사용하기 위한 팁 ..................................................................................... 373

CONTENTS

Page 21: 『고성능 파이썬』 - 맛보기

19

11.6 확률적 자료구조 ....................................................................................................... 374

11.6.1 1바이트 모리스 카운터를 사용해 대략적으로 개수 세기 ............................................. 375

11.6.2 K-최솟값.......................................................................................................... 378

11.6.3 블룸 필터 .......................................................................................................... 382

11.6.4 로그로그 카운터 ................................................................................................. 388

11.6.5 실전 예제 .......................................................................................................... 393

CHAPTER 12 현장에서 얻는 교훈

12.1 어댑티브랩의 소셜 미디어 분석, 소마(SoMA) ............................................................. 397

12.1.1 어댑티브랩에서의 파이썬 ..................................................................................... 398

12.1.2 소마의 설계 ....................................................................................................... 398

12.1.3 개발 방법론 ....................................................................................................... 399

12.1.4 소마 유지보수 .................................................................................................... 400

12.1.5 엔지니어를 위한 조언 .......................................................................................... 400

12.2 RadimRehurek.com의 딥 러닝, 플라이(Fly) ............................................................... 401

12.2.1 스위트 스팟 ....................................................................................................... 401

12.2.2 최적화에서 배운 교훈들 ....................................................................................... 403

12.2.3 정리 ................................................................................................................. 406

12.3 Lyst.com의 대규모 기계 학습 ................................................................................... 406

12.3.1 Lyst에서 파이썬의 위치 ....................................................................................... 406

12.3.2 클러스터 설계 .................................................................................................... 406

12.3.3 빠르게 움직이는 스타트업에서의 코드 진화 ............................................................. 407

12.3.4 추천 엔진 만들기 ................................................................................................ 407

12.3.5 보고와 모니터링 ................................................................................................. 408

12.3.6 몇 가지 조언 ...................................................................................................... 408

12.4 스메시에서의 대규모 소셜 미디어 분석 ...................................................................... 409

12.4.1 스메시에서 파이썬의 역할 .................................................................................... 409

Page 22: 『고성능 파이썬』 - 맛보기

20

12.4.2 플랫폼 .............................................................................................................. 410

12.4.3 고성능 실시간 문자열 매칭 ................................................................................... 410

12.4.4 보고, 모니터링, 디버깅, 배포 ................................................................................. 412

12.5 성공적인 웹과 데이터 처리 시스템을 위한 PyPy ......................................................... 413

12.5.1 선결 조건 .......................................................................................................... 414

12.5.2 데이터베이스 ..................................................................................................... 415

12.5.3 웹 애플리케이션 ................................................................................................. 416

12.5.4 OCR과 번역 ..................................................................................................... 416

12.5.5 작업 배분과 작업자 ............................................................................................. 417

12.5.6 결론 ................................................................................................................. 417

12.7 Lanyrd.com의 작업 대기열 ....................................................................................... 417

12.7.1 Lanyrd에서 파이썬의 역할 .................................................................................. 418

12.7.2 작업 대기열 성능 개선하기 ................................................................................... 419

12.7.3 보고, 모니터링, 디버깅, 배포 ................................................................................. 419

12.7.4 다른 엔지니어를 위한 조언 ................................................................................... 419

찾아보기 ........................................................................................................................... 421

CONTENTS

Page 23: 『고성능 파이썬』 - 맛보기

211장 고성능을 위한 파이썬 이해하기

이 장에서 배울 내용

● 컴퓨터의 구성 요소

● 컴퓨터 구성 요소의 다양한 종류

● 파이썬에서 컴퓨터 구성 요소를 추상화하는 방법

● 고성능을 위한 파이썬 코드를 작성할 때 넘어야 할 장벽

● 성능 관련 문제의 종류

컴퓨터 프로그래밍이란 어떤 결과를 도출하기 위해서 특별한 방법으로 데이터를 가공해서 주

고받는 과정이라고 할 수 있다. 그리고 좋은 프로그램은 기능뿐 아니라 사용자가 원하는 성능

기준을 만족해야 한다. 이를 위해서는 프로그래밍 과정 중에 부하를 최소화(더 효과적인 코드

를 작성)하고 각 단계에 더 적합한 방법(알고리즘, 구조 등)을 찾아 변경해야 하는데, 이 작업

에 상당한 시간이 소요된다.

우선 코드에서 부하를 줄이는 데 집중해서 실제로 데이터를 주고받는 하드웨어에 대한 더 깊은

통찰을 얻어보자. 파이썬은 하드웨어와의 상호작용을 추상화해서 숨기는 것에 꽤나 열심이기

때문에 쉽지 않은 연습이 될 것이다. 하지만 실제 데이터를 하드웨어로 옮기는 최적의 방법과

파이썬에서 데이터를 옮기는 최적의 방법 모두를 이해하면 파이썬으로 고성능 프로그램을 작

성하는 데 도움이 된다.

고성능을 위한 파이썬 이해하기

CHAPTER 1

Page 24: 『고성능 파이썬』 - 맛보기

22 고성능 파이썬

1.1 컴퓨터 시스템의 기본

컴퓨터를 구성하는 요소는 연산 장치, 기억 장치, 그리고 각 장치를 이어주는 연결 장치, 이 세

가지로 나눌 수 있다. 그리고 각 장치는 저마다의 속성을 가지고 있다. 연산 장치는 초당 계산

할 수 있는 처리량, 기억 장치는 얼마나 많은 데이터를 저장할 수 있는지, 또 얼마나 빠르게 데

이터를 읽고 쓸 수 있는지, 마지막으로 연결 장치는 연산 장치와 기억 장치 간에 데이터를 얼마

나 빠르게 옮길 수 있는지를 속성으로 가진다.

이러한 기본 구성 단위를 이해하면 일반적인 워크스테이션의 구조를 다양한 추상화 수준에서

설명할 수 있다. 예를 들어 일반적인 워크스테이션은 CPU라는 연산 장치, 용량과 접근 속도가

서로 다른 RAM과 하드 드라이브라는 저장 장치, 그리고 이들을 포함한 모든 부품을 연결하는

장치인 버스로 생각해볼 수 있다. 하지만 더 자세히 살펴보면 CPU도 자기만의 기억 장치를 가

지고 있다. L1, L2, 경우에 따라서는 L3, L4 캐시가 바로 그것인데 아주 작은 용량(수 킬로바

이트에서 십여 메가바이트)이지만 매우 빠른 속도로 작동한다. 이 캐시는 백사이드 버스backside

bus, BSB라는 특수한 버스로 CPU와 연결되어 있다. 게다가 새로운 컴퓨터 아키텍처는 일반적으

로 새로운 구성으로 출시되는데, 예를 들어 인텔의 네할렘 CPU는 프론트사이드 버스frontside bus,

FSB를 퀵패스 인터커넥트QuickPath Interconnect, QPI로 대체했다. 마지막으로 다른 장치에 비해서 매우

느린 장치인 네트워크 연결도 워크스테이션의 구성 요소로 빼놓을 수 없다.

이런 복잡한 여러 구성 요소에 대한 이해를 돕기 위해 각 장치에 대해서 조금 더 알아보자.

1.1.1 연산 장치연산 장치는 컴퓨터를 사용하는 데 가장 핵심적인 장치로 입력받은 비트를 다른 비트로 변환하

거나 프로세스의 상태를 변경한다. CPU가 가장 흔한 연산 장치지만 최근에는 GPU를 활용하

는 경우도 늘고 있다. 원래 GPU는 그래픽 처리에 사용되었지만 고유의 병렬적인 특성으로 인

해 수많은 계산을 동시에 처리할 수 있어서 최근 인기를 끌고 있다. 연산 장치는 그 종류에 관

계없이 여러 비트(예를 들면 수를 나타내는 비트)를 입력받아 다른 비트 조합(예를 들면 각 수

의 합)으로 변환한다. 또한 정수와 실수의 산술 연산과 이진수의 비트 연산 기능을 제공하며,

어떤 연산 장치는 A, B, C 세 수를 받아서 A * B + C의 결과를 계산하는 아주 특수한 연산 기

능도 제공한다.

Page 25: 『고성능 파이썬』 - 맛보기

231장 고성능을 위한 파이썬 이해하기

연산 장치의 주요 속성은 한 사이클에 처리할 수 있는 연산의 개수와 1초에 처리할 수 있는 사

이클의 횟수다. 한 사이클에 처리할 수 있는 연산의 개수는 IPCinstructions per cycle1로 측정하며, 초

당 사이클 횟수는 클럭 속도로 측정한다. 이 두 값은 새로운 연산 장치를 만들 때마다 경쟁적으

로 언급된다. 예를 들어 인텔 코어 시리즈는 IPC 값이 매우 높지만 클럭 속도가 상대적으로 떨

어지고, 펜티엄4의 경우는 그 반대다. 반면, GPU는 매우 높은 클럭 속도와 IPC 값을 가지지

만 다른 문제가 발목을 잡는다. 이에 대한 자세한 내용은 나중에 살펴보도록 하겠다.

클럭 속도를 높이면 초당 수행할 수 있는 연산량이 증가하므로 그 연산 장치를 사용하는 모든

프로그램의 속도가 바로 개선되며, 마찬가지로 IPC 값이 높아지면 벡터화 수준이 증가하므로

처리 성능이 급격하게 올라간다. 벡터화란 CPU가 여러 개의 데이터를 입력받아 한 번에 처리

하는 것을 뜻한다. 이런 종류의 CPU 명령을 SIMDsingle instruction, multiple data라고 한다.

일반적으로 연산 장치는 지난 십 년간 꽤 천천히 발전했다(그림 1-1 ). 클럭 속도와 IPC의 향

상은 모두 트랜지스터를 더 작게 만들어야 한다는 제약으로 인해 정체되어 있었다. 그 결과

CPU 제조사는 더 빠른 속도를 얻기 위해 하이퍼스레딩HyperThreading이나 비순차 실행out-of-order

execution, 멀티코어 아키텍처 같은 다른 방법을 모색하게 되었다.

그림 1-1 출시 시기에 따른 CPU 클럭 속도의 변화(출처 : CPU DB)

1 9장에서 살펴볼 프로세스 간 통신인 IPC(Inter-Process Communication)와 혼동하지 말자.

Page 26: 『고성능 파이썬』 - 맛보기

24 고성능 파이썬

하이퍼스레딩은 운영체제가 가상의 두 번째 CPU를 인식하게 한 다음, 똑똑한 하드웨어 로직

이 단일 CPU의 실행 유닛에 두 스레드를 섞어 실행하도록 하는 기법이다. 잘만 작동하면 단일

스레드 대비 30%까지 성능을 끌어올릴 수 있다. 이 기법은 두 스레드가 서로 다른 실행 유닛을

사용할 때, 예를 들어 한 스레드가 실수 연산을 하고 다른 스레드가 정수 연산을 한다면 잘 작

동한다.

비순차 실행은 프로그램 실행 과정에서 이전 작업 결과에 의존하지 않는 부분을 찾아내서 두

작업을 순서에 관계없이 실행하거나 혹은 동시에 실행하는 기법이다. 순서가 중요한 일만 순차

적으로 처리한다면 나머지 작업이 어떤 순서로 실행되든 전체 프로그램은 정상적으로 동작하

게 된다. 이 기법은 한 명령이 메모리에서 데이터를 가져오는 등의 이유로 대기하는 동안에 다

른 명령을 실행할 수 있도록 함으로써 사용 가능한 자원을 최대한 활용할 수 있게 한다.

마지막으로 언급할 내용은 고급 개발자에게 특히 중요한 멀티코어 아키텍처의 보급이다. 멀티

코어 아키텍처는 하나의 실행 유닛에 여러 개의 CPU를 두어 전체적인 처리량이 단일 CPU의

처리량을 능가하도록 한다. 이게 바로 요즘 단일 코어 시스템을 찾아보기 어려운 이유다. 듀얼

코어의 경우 두 개의 물리 연산 유닛이 서로 연결되어 있다. 멀티코어 아키텍처는 초당 처리할

수 있는 전체 연산의 수를 늘려주지만 모든 연산 유닛을 동시에 제대로 활용하기는 쉽지 않다.

이는 단순히 CPU에 더 많은 코어를 넣는다고 프로그램의 실행 시간이 항상 단축되는 것은 아

님을 뜻한다. 암달의 법칙Amdahl’s law 때문인데, 암달의 법칙이란 멀티코어에서 작동하도록 설계

된 프로그램일지라도 하나의 코어에서 실행되어야 하는 루틴이 존재하고, 이 루틴이 더 많은

코어를 투입했을 때 기대할 수 있는 최대 성능 향상치의 병목으로 작용한다는 법칙이다.

예를 들어 각 1분이 소요되는 설문 조사를 100명을 대상으로 한다고 가정해보자. 이때 첫 번

째 참여자에게 응답을 받은 후에 두 번째 참여자에게 질문하는 식으로 한 번에 한 명에게만 질

문한다면 총 100분이 소요된다. 한 번에 한 명에게만 질문하고 응답을 기다리는 방식은 순차

프로세스 방식과 유사하다. 순차 프로세스에서는 한 번에 하나의 작업을 수행하고 이전 작업이

완료되기까지 대기한다.

하지만 한 번에 두 명에게 질문하는 식으로, 즉 설문 조사를 병렬로 수행한다면 모든 설문을 끝

내는 데 50분밖에 걸리지 않을 것이다. 설문 조사는 각 조사원이 다른 조사원의 진행 상황을

알 필요가 없기 때문에 동시에 진행할 수 있다. 조사원 간의 의존성 없이 작업을 쉽게 나눌 수

있는 것이다.

Page 27: 『고성능 파이썬』 - 맛보기

251장 고성능을 위한 파이썬 이해하기

설문 조사원을 100명까지 늘리면 더 빨리 끝낼 수 있다. 100명이 진행하면 참여자가 응답하는

데 걸리는 시간이 1분이므로 전체 설문 조사를 1분이면 끝낼 수 있다. 이 상태에서는 설문 조

사원을 추가로 늘려봤자 더 이상의 시간 단축은 되지 않는다. 모든 참여자가 설문지를 작성하

고 있으므로 설문 조사원이 더 할 일이 없기 때문이다. 이 경우 전체 시간을 더 줄일 수 있는 유

일한 방법은 설문 응답에 소요되는 시간을 단축하는 방법뿐이다. 이와 유사하게 CPU 역시 특

정 코어가 작업을 끝내는 데 걸리는 시간이 병목이 되는 지점까지는 코어 수를 늘릴수록 성능

을 더 끌어올릴 수 있다. 다시 말하면, 병렬 계산에서의 병목 지점은 흩어져 있지만 순차적으로

실행해야 하는 작은 작업들이 된다는 뜻이다.

게다가 파이썬에서는 전역 인터프리터 락 global interpreter lock, GIL 때문에 여러 개의 코어를 활용하

기가 쉽지 않다. GIL은 현재 사용 중인 코어가 몇 개든, 한 번에 하나의 명령만 실행되도록 강

제한다. 즉, 파이썬에서 여러 개의 코어에 동시에 접근하더라도 한 번에 하나의 파이썬 명령만

실행된다. 앞서 살펴본 설문 조사의 예를 들면, 100명의 조사원이 있더라도 한 번에 한 사람

만 조사할 수 있다는 뜻이다. 이렇게 되면 다수의 코어를 사용하는 장점이 사라진다. 최근 경향

이 고성능의 단일 코어보다는 여러 개의 코어를 사용하는 쪽으로 흐르고 있기에 이는 꽤나 골

치 아픈 문제라고 할 수 있지만, 다른 표준 라이브러리인 multiprocessing 모듈을 사용하거나

numexpr, Cython 같은 기술을 이용하거나, 아니면 분산 컴퓨팅 모델을 사용하는 방법으로

회피할 수 있다.

1.1.2 기억 장치컴퓨터에서 기억 장치는 비트를 저장한다. 비트는 프로그램 내의 변수를 나타낼 수도 있고 그

림의 픽셀을 나타낼 수도 있다. 즉, 기억 장치에는 메인보드의 레지스터나 RAM, 하드디스크도

포함된다. 이들 기억 장치의 가장 큰 차이는 바로 데이터를 읽고 쓰는 속도다. 더 골치 아픈 문

제는 읽고 쓰는 속도가 데이터를 읽는 방식에 크게 의존적이라는 것이다.

예를 들어 대부분의 기억 장치는 데이터를 조금씩 자주 읽을 때보다는 한꺼번에 많이 읽을 때

훨씬 빠르게 작동한다. 전자를 임의 접근 random access, 후자를 순차 접근 sequential access이라 한다. 기

억 장치에 저장된 데이터를 두꺼운 책의 페이지로 생각한다면 임의의 페이지를 읽고 쓰는 것보

다 페이지 순서대로 읽고 쓰는 방식이 더 빠른 것과 비슷하다.

Page 28: 『고성능 파이썬』 - 맛보기

26 고성능 파이썬

일반적으로 이러한 현상은 모든 기억 장치의 공통 특성이지만, 영향을 미치는 정도는 기억 장

치의 유형에 따라 매우 다르다.

읽기/쓰기 속도와 더불어 기억 장치는 지연시간을 가진다. 기억 장치에서 지연시간이란 장치에

서 데이터를 찾기까지 걸리는 시간을 의미한다. 하드디스크의 경우 물리적으로 헤드를 움직여

정확한 위치에서 데이터를 읽어야 하므로 지연시간이 긴 편이다. 반면, RAM의 경우 지연시간

이 무척 짧다. 일반적인 워크스테이션에 탑재되어 있는 몇 가지 기억 장치의 특징을 읽기/쓰기

속도에 따라 나열해두었다.

하드디스크

컴퓨터의 전원이 꺼진 상태에서도 데이터를 오랫동안 보관할 수 있는 저장 장치다. 물리적

으로 헤드를 움직여야 하므로 읽기/쓰기 속도가 느리다. 임의 접근 성능이 떨어지지만 테라

바이트 수준의 대용량 데이터를 저장할 수 있다.

솔리드 스테이트 드라이브(SSD)

하드디스크와 유사하지만 하드디스크보다 읽기/쓰기 속도가 우수하나 가격에 비해 용량이 작

다.

RAM

애플리케이션 코드나 (사용 중인 변수 같은) 데이터를 저장하는 데 쓰인다. 읽기/쓰기 속도

가 빠르며 임의 접근의 경우에도 성능 하락이 적다. 일반적으로 용량은 기가바이트 단위로

제한적이다.

L1/L2 캐시

읽기/쓰기 속도가 매우 빠르다. CPU로 전달되는 데이터는 이 캐시를 거쳐 가게 된다. 킬로

바이트에서 메가바이트 단위로 용량이 매우 작다.

[그림 1-2]에 다양한 기억 장치 간의 차이를 도표로 나타냈다.

Page 29: 『고성능 파이썬』 - 맛보기

271장 고성능을 위한 파이썬 이해하기

그림 1-2 다양한 기억 장치의 특성(2014년 2월 기준)

읽기/쓰기 속도와 용량은 반비례하는 경향이 뚜렷하다. 즉, 빠른 속도를 원한다면 작은 용량을

감수해야 한다. 이 때문에 많은 시스템은 메모리를 단계별로 운용한다. 먼저 하드디스크에 전

체 데이터를 저장하고 일부를 RAM으로 옮긴다. 그리고 그중 매우 작은 일부를 L1/L2 캐시로

옮기는 식이다. 이렇게 메모리를 단계별로 운용하는 방식은 프로그램에서 요구하는 메모리 접

근 속도에 따라 데이터를 다양한 곳에 저장할 수 있도록 도와준다. 프로그램의 메모리 사용 패

턴을 최적화하려면 단순히 어떤 데이터를 어디에 저장할 것인지, 또 어떻게 저장할 것인지(순

차적인 읽기 속도를 높이려면), 그리고 몇 번이나 데이터를 옮길 것인지를 최적화하면 된다.

여기에 추가로 비동기 I/O와 선점형 캐시를 사용하면 시간을 낭비하지 않고 데이터가 필요할

때 바로 읽을 수 있도록 할 수 있다. 이 모든 과정은 다른 계산을 수행 중일 때도 독립적으로 일

어난다!

Page 30: 『고성능 파이썬』 - 맛보기

28 고성능 파이썬

1.1.3 통신 계층마지막으로, 앞서 살펴본 기초적인 구성 요소가 어떻게 서로 통신하는지 알아보자. 다양한 통

신 수단이 있지만 모두 버스bus라고 하는 것의 변형이다.

예를 들어 FSB는 RAM과 L1/L2 캐시를 연결한다. FSB는 처리할 준비가 된 데이터를 옮겨서

프로세서가 계산할 수 있도록 하며, 계산이 완료되면 다시 돌려준다. 외부 버스 같은 다른 버스

도 존재하는데, CPU와 시스템 메모리가 외부 하드웨어(하드디스크나 네트워크 카드 같은)와

통신하기 위한 것이다. 외부 버스는 일반적으로 FSB보다 느리다.

사실 L1/L2 캐시의 장점 대부분은 빠른 속도에 의한 것이다. RAM과 캐시 사이의 버스는 상대

적으로 느리고, 캐시와 CPU 사이의 버스는 훨씬 빠르다. 그래서 계산에 필요한 데이터를 한

번에 대량으로 캐시에 올려두면 CPU가 오랫동안 데이터를 기다리는 일 없이 더 많은 계산을

할 수 있다.

이와 유사하게, GPU를 사용하는 데 가장 큰 걸림돌은 바로 연결되어 있는 버스다. GPU는 주

변장치이므로 일반적으로 FSB보다 훨씬 느린 PCI 버스로 연결되어 있다. 따라서 GPU에 데

이터를 보내고 받는 것은 매우 부담이 큰 작업이다. 이기종 컴퓨팅heterogeneous computing 혹은 CPU

와 GPU를 FSB로 연결한 시스템의 출현은 바로 이런 전송 비용을 줄여 많은 데이터를 전송하

는 경우에도 GPU를 사용하기 위해서다.

컴퓨터 안에서의 데이터 통신과 더불어 네트워크를 통한 데이터 전송도 통신 블록의 한 요소

다. 하지만 네트워크 통신은 앞서 설명한 것보다 훨씬 더 유연해서 네트워크 결합 스토리지

network attached storage, NAS 같은 기억 장치와 바로 연결되거나, 클러스터의 컴퓨팅 노드 같은 다른

컴퓨팅 블록과 연결될 수 있다. 하지만 일반적으로 네트워크 통신은 지금까지 살펴본 다른 통

신과는 비교할 수 없을 정도로 느리다. FSB로는 초당 십여 기가비트를 전송할 수 있지만 네트

워크는 고작 수십 메가 내지 1기가 언저리의 비트를 전송할 뿐이다.

버스의 가장 주요한 속성은 주어진 시간에 얼마나 많은 데이터를 전송할 수 있는지를 나타내는

속도다. 이 속성은 한 번에 전송할 수 있는 데이터의 양을 나타내는 버스 폭width과 초당 몇 번을

전송할 수 있는지를 나타내는 버스 주파수frequency에 의해 결정된다. 한 번의 전송으로 데이터를

옮기는 과정은 순차적이다. 즉, 메모리에서 한 무더기의 데이터를 읽은 후 다른 장소로 옮긴다.

따라서 버스 속도는 이 두 가지 세부 속성으로 나뉘며 각 속성은 다른 방식으로 계산에 영향을

미친다. 버스 폭이 넓으면 필요한 데이터를 한 번에 옮길 수 있으므로 코드를 벡터화할 수 있

Page 31: 『고성능 파이썬』 - 맛보기

291장 고성능을 위한 파이썬 이해하기

다. 버스 폭이 좁더라도 버스 주파수가 높다면 임의의 메모리 영역을 자주 읽어야 하는 경우에 도

움이 된다. 재미있게도 이런 속성은 컴퓨터 설계자가 메인보드의 물리적인 구조를 변경함에 따

라 바뀐다. 두 칩을 가깝게 배치하면 그 둘을 연결하는 물리적인 선의 길이가 짧아지므로 버스 주

파수가 높아지고 연결하는 선의 수가 많아지면 버스 폭이 넓어진다.

인터페이스는 특정 애플리케이션의 용도에 적합하게 속도를 조율할 수 있으므로 그 종류가 수

백 가지가 있다고 해도 그리 놀랄 일은 아니다. 위키미디어 커먼즈에서 가져온 [그림 1-3]은

잘 알려진 몇 가지 인터페이스의 속도를 보여준다. 이 그림에서는 데이터 요청에서 응답까지

걸리는 시간인 지연시간은 보여주지 않는다. 지연시간은 컴퓨터에 따라 천차만별이지만, 사용

하는 인터페이스가 가지고 있는 기본적인 제약에 영향을 받는다.

그림 1-3 다양한 인터페이스의 연결 속도(출처 : Leadbuffalo [CC BY-SA 3.0])

Page 32: 『고성능 파이썬』 - 맛보기

30 고성능 파이썬

1.2 기본 구성 함께 보기

컴퓨터의 기본적인 구성 요소를 잘 안다고 해도 고성능 프로그래밍에서 맞닥뜨릴 문제를 모두

이해할 수 있는 건 아니다. 이 모든 구성 요소가 함께 어우러져서 훨씬 더 복잡한 문제를 야기

한다. 이번에는 간단한 문제를 한 번 살펴보고 이상적인 해결 방법이 어떤 것인지, 또 파이썬에

서는 어떤 방식으로 접근하고 있는지를 알아보자.

성능에 관한 한 파이썬은 태생적으로 부족하다는 암울한 결론을 성급히 내리는 사람들도 있지

만, 두 가지 이유로 이는 사실이 아니다. 첫째, 성능을 좌우하는 아주 중요한 구성 요소인 개발

자를 고려하지 않았다. 파이썬이 성능에서 약간 부족한 부분은 빠른 개발 속도로 상쇄하고 남

는다. 또한 이 책 전반에 걸쳐, 이번 절에서 소개하는 문제를 비교적 쉽게 완화할 수 있는 모듈

과 철학을 소개할 것이다. 이 둘을 합치면 성능 제약을 많은 부분 제거하면서도 파이썬의 빠른

개발 속도를 그대로 살릴 수 있다.

1.2.1 이상적인 컴퓨팅 vs. 파이썬 가상 머신고성능 프로그래밍의 구성 요소를 더욱 잘 이해하기 위해 어떤 수가 소수인지 판별하는 다음

예제 코드를 살펴보자.

import math

def check_prime(number):

sqrt_number = math.sqrt(number)

number_float = float(number)

for i in range(2, int(sqrt_number)+1):

if (number_float / i).is_integer():

return False

return True

print "check_prime(10000000) = ", check_prime(10000000) # 소수가 아님

print "check_prime(10000019) = ", check_prime(10000019) # 소수!

앞서 설명한 컴퓨터의 추상 모델을 사용해서 이 코드를 분석한 다음, 파이썬에서 실행했을 때

벌어지는 일과 비교해보자. 추상 모델이므로 최적화된 컴퓨터와 파이썬이 코드를 실행하는 방

식의 미묘한 부분은 일단 무시하자. 알고리즘의 일반적인 구성 요소를 파악하고 해법을 위한

컴퓨팅 구성 요소들의 최적 조합을 고민하는 방식은 문제를 풀기 전에 해볼 수 있는 좋은 연습

Page 33: 『고성능 파이썬』 - 맛보기

311장 고성능을 위한 파이썬 이해하기

이다. 이상적인 상황과 파이썬 내부에서 실제로 어떤 일이 일어나는지를 이해한다면 파이썬 코

드를 점점 최적에 가까운 상태로 끌어올릴 수 있다.

이상적인 컴퓨팅

앞의 코드가 실행되면 수가 RAM에 저장된다. sqrt_number와 number_float를 계산하려면

이 값을 CPU로 보내야 한다. 이상적으로는 이 값은 한 번에 전송되어 CPU의 L1/L2 캐시에

저장되고 CPU가 계산한 결과를 다시 RAM으로 되돌려준다. 이 시나리오는 RAM에서 수를 읽

어 들이는 횟수를 최소화하고 수를 RAM보다 훨씬 빠른 L1/L2 캐시에서 읽어오게끔 했으므로

이상적인 경우라 할 수 있다. 또한 FSB를 통하는 데이터 전송 횟수를 최소화하고 CPU와 여러

캐시를 잇는 좀 더 빠른 BSB를 선택했다. 데이터를 필요한 곳에 저장하고 이동을 최소화하는

전략은 최적화에 있어서 매우 중요한 주제다. ‘무거운 데이터heavy data’라는 개념은 데이터를 옮

기는 데 시간과 노력이 필요하다는 사실을 의미하며, 이는 피해야 한다.

앞 코드의 루프에서 한 번에 i 값 하나씩만 CPU로 보내는 대신, number_float와 i 값 여러 개

를 동시에 검사하도록 할 것이다. CPU의 벡터화한 연산은 추가 시간을 들이지 않고 동시에 여

러 독립된 계산을 수행할 수 있기에 이것이 가능하다. 그래서 number_float를 CPU 캐시로

보낼 때 캐시에 담을 수 있는 만큼의 i 값을 함께 보내려고 한다. 각각의 number_float / i 계

산 결과가 정수인지 확인하고 그 결과를 다시 되돌려준다. 결과가 참이라면 함수를 종료하고

그렇지 않다면 다시 반복한다. 이런 식으로 매번 느린 버스를 통하지 않고 여러 i 값에 대한 결

과를 한 번에 돌려주면 된다. 이는 CPU의 벡터화 연산, 또는 여러 데이터를 입력받는 명령을

한 클럭에 실행하는 기능의 장점을 이용한 것이다.

이 벡터화 개념은 다음 코드로 설명할 수 있다.

import math

def check_prime(number):

sqrt_number = math.sqrt(number)

number_float = float(number)

numbers = range(2, int(sqrt_number)+1)

for i in range(0, len(numbers), 5):

# 아래 코드는 유효한 파이썬 코드가 아니다.

result = (number_float / numbers[i:(i+5)]).is_integer()

if any(result):

return False

return True

Page 34: 『고성능 파이썬』 - 맛보기

32 고성능 파이썬

여기서는 한 번에 다섯 개의 i 값에 대한 나눗셈을 수행하고 그 결과가 정수인지 확인한다. 이

과정이 제대로 벡터화된다면 CPU는 i 값을 매번 하나씩 따로 계산하지 않고 한 번에 다섯 개

의 연산을 수행하게 된다. 이상적으로는 any (result ) 연산 역시 결과를 RAM에 되돌려주는

대신 CPU 안에서 처리할 것이다. 벡터화에 대한 자세한 내용과 동작 방식, 그리고 언제 사용

하는 게 좋을지는 6장에서 살펴보기로 하자.

파이썬의 가상 머신

파이썬 인터프리터는 컴퓨터의 구성 요소를 추상화해두었다. 따라서 배열을 위한 메모리 할당,

메모리 정렬, CPU로 데이터를 보내는 순서 등을 개발자가 고민할 필요가 없게끔 한다. 이는

구현해야 할 알고리즘에만 집중할 수 있게 해주는 파이썬의 장점이지만, 여기에는 엄청난 성능

상의 비용을 지불해야 한다.

사실 파이썬은 내부적으로 잘 최적화된 명령어 집합을 실행하지만, 더 나은 성능을 위해서

는 명령어 집합을 올바른 순서로 실행하도록 하는 게 요령이다. 다음 예제를 보면 쉽게 확인

할 수 있는데, search_fast와 search_slow는 둘 다 똑같은 O (n ) 복잡도를 가지고 있지만,

search_fast는 불필요한 계산을 건너뛰어서 루프를 일찍 끝낼 수 있으므로 search_slow보다

더 빠르게 작동한다.

def search_fast(haystack, needle):

for item in haystack:

if item = = needle:

return True

return False

def search_slow(haystack, needle):

return_value = False

for item in haystack:

if item = = needle:

return_value = True

return return_value

프로파일링을 통해서 코드의 느린 부분을 찾아내고 같은 계산을 더 효율적인 방법으로 처리하

는 것은 이런 불필요한 명령을 찾아서 제거하는 것과 비슷하다. 최종 결과는 같지만 계산 횟수

와 데이터 전송 횟수는 어마어마하게 줄어든다.

Page 35: 『고성능 파이썬』 - 맛보기

331장 고성능을 위한 파이썬 이해하기

이런 추상화 계층으로 인해, 이상적인 소수 찾기 예제에서 사용했던 벡터화 개념을 즉시 달성

할 수는 없다. 처음 살펴본 소수 루틴은 몇 번의 반복을 한 번에 처리하지 않고 매 i 값마다 반

복하면서 계산을 수행한다. 하지만 벡터화 개념을 사용한 예제에서 사용한 코드는 실수를 리스

트로 나눌 수 없기 때문에 유효한 파이썬 코드가 아니다. 이와 같은 문제는 numpy 같은 외부

라이브러리가 지원하는 벡터화된 수학 연산을 이용하여 해결할 수 있다.

더욱이 파이썬의 추상화는 다음 계산에 사용할 데이터를 L1/L2 캐시에 유지해야 하는 최적화

에 방해가 된다. 여기에는 여러 가지 요인이 있는데, 첫 번째는 파이썬 객체가 메모리에 최적화

된 형태로 저장되지 않는다는 사실이다. 파이썬이 메모리를 자동으로 할당하고 해제하는 쓰레

기 수집기garbage collector, GC를 사용하기 때문인데, 이는 CPU 캐시에 데이터를 전송하는 데 영향

을 미치는 메모리 단편화를 일으킨다. 게다가 어디에서도 메모리에 저장되는 데이터 구조를 직

접 변경할 수 없기 때문에 심지어 버스 폭이 충분히 넓더라도 한 번의 계산에 필요한 정보를 한

번에 전송할 수 없다.

두 번째는 좀 더 원초적인 문제다. 바로 파이썬이 동적 타입을 사용하며, 컴파일되지 않는다는

점이다. 대부분의 C 개발자는 수년간의 경험을 통해 컴파일러가 사람보다 더 똑똑한 경우가

많음을 알고 있다. 정적인 코드라면 컴파일러는 최적화를 위해 많은 부분을 변경할 수 있으며

CPU가 특정 명령을 어떻게 실행시킬지도 변경할 수 있다. 하지만 파이썬은 컴파일되지 않는데

다 코드의 기능이 런타임에 변경되는 동적 타입 언어라 최적화 알고리즘이 제 기능을 발휘하기

어렵다. 이 문제를 극복하는 여러 방법 중에는 Cython이 가장 대표적이다. Cython은 파이썬

코드를 컴파일하고 컴파일러에게 동적인 코드가 실제로 어떻게 동작하는지 힌트를 줄 수 있다.

마지막으로 이전에도 잠깐 언급했던, 코드를 병렬로 실행하려고 할 때 성능을 해치는 GIL이

다. 예를 들어 2부터 sqrtN까지의 수를 몇몇 그룹으로 나눠서 여러 CPU 코어를 활용하도록

코드를 변경한다고 가정해보자. 각 코어는 할당받은 수들에 대해서만 계산하고 모든 코어에서

계산이 끝나면 결과를 비교한다. 비록 루프를 일찍 끝낼 수는 없지만 코어 수가 많을수록 전체

계산 횟수가 적어지므로 괜찮은 방법처럼 보인다(M개의 코어를 사용한다면 각 코어는 sqrtN

/ M번의 검사를 수행한다). 하지만 GIL 때문에 동시에 사용할 수 있는 코어는 하나뿐이다. 즉,

결과적으로는 코드를 변경하기 전과 동일하게 작동하며 루프를 일찍 끝내지도 못한다. 이 문제

는 멀티스레드가 아니라 멀티프로세스(multiprocessing 모듈을 사용)를 사용해서 회피할 수

있으며, Cython이나 외부 함수foreign function를 사용해도 회피할 수 있다.

Page 36: 『고성능 파이썬』 - 맛보기

34 고성능 파이썬

1.3 파이썬을 쓰는 이유

파이썬은 표현력이 좋고 배우기 쉽다는 장점이 있다. 처음 시작하는 사람도 짧은 시간에 많은 것을

해볼 수 있다. 많은 파이썬 라이브러리는 타 언어로 작성된 도구를 감싸서 다른 시스템도 쉽게 호

출할 수 있도록 하고 있다. 예를 들어 scikit-learn 기계 학습 시스템은 C로 작성된 LIBLINEAR

와 LIBSVM을 사용하며, numpy 라이브러리는 BLAS와 또 다른 C와 포트란 라이브러리를 포

함한다. 그 결과, 해당 모듈을 활용하는 파이썬 코드는 C 코드만큼 빠르게 작동한다.

파이썬은 중요하고 안정적인 다수의 라이브러리를 기본으로 제공하여 ‘배터리를 포함한’2 언어

로 묘사되기도 한다. 다음과 같은 라이브러리가 기본으로 포함되어 있다.

unicode와 bytes

언어 핵심에 녹아 있다.

array

메모리를 효율적으로 사용하는 배열

math

간단한 통계를 포함한 기본적인 수학 연산 모듈

sqlite3

널리 사용되는 파일 기반 데이터베이스인 SQLite3의 래퍼

collections

데크deque, 카운터, 여러 가지 사전을 포함하는 다양한 객체 집합

기본으로 지원하지는 않지만, 다양성을 더해주는 다음과 같은 외부 라이브러리가 있다.

2 옮긴이_ 필요한 것을 이미 갖추고 있어 실무에 곧바로 활용할 수 있다는 뜻이다.

Page 37: 『고성능 파이썬』 - 맛보기

351장 고성능을 위한 파이썬 이해하기

numpy

파이썬 수학 라이브러리(행렬을 사용할 때 필수 라이브러리)

scipy

널리 사용되는 유명한 C와 포트란 라이브러리를 감싼 과학 계산 라이브러리 모음

pandas

R의 dataframe, 혹은 엑셀 스프레드시트와 유사한 데이터 분석 라이브러리. scipy와

numpy를 사용한다.

scikit-learn

빠르게 기계 학습의 기본 모듈이 되고 있다. scipy를 사용한다.

biopython

bioperl과 유사한 생물정보학 라이브러리

tornado

웹 프레임워크이자 비동기 네트워크 라이브러리

데이터베이스 바인딩

레디스Redis, 몽고DBMongoDB, HDF5, SQL 등을 지원하는 데이터베이스 바인딩

웹 프레임워크

웹 사이트 개발을 위한 플라스크flask, 장고django, 피라미드pyramid, 토네이도tornado

OpenCV

컴퓨터 비전을 위한 바인딩

API 바인딩

Page 38: 『고성능 파이썬』 - 맛보기

36 고성능 파이썬

구글, 트위터, 링크드인 같은 인기 서비스에서 제공하는 웹 API를 위한 바인딩

그리고 다양한 목적에 맞는 파이썬 배포판과 셸이 존재한다.

● 기본 배포판. http ://python.org에서 내려받을 수 있다.

● 안정적이고 여러 기능을 제공하는 Enthought 사의 EPD와 Canopy

● 과학 계산에 초점을 맞춘 Continuum 사의 Anaconda 환경

● IDE를 포함하며, 매트랩MATLAB과 유사한 환경을 제공하는 Sage

● Python (x, y )

● 개발자와 과학자들 사이에서 널리 쓰이는 대화형 파이썬 셸인 IPython

● 브라우저에서 돌아가는 IPython인 아이파이썬 노트북IPython Notebook. 교육과 데모 용도로 많이 쓰인다.

● 대화형 파이썬 셸인 BPython

파이썬의 주된 강점 하나는 빠른 프로토타이핑이다. 비록 처음부터 그럴싸하지는 않더라도 다

양한 라이브러리를 활용해서 실현 가능한 아이디어인지를 빠르게 검증해볼 수 있다.

빠른 수학 계산 루틴을 원한다면 numpy를 살펴보자. 기계 학습을 해보고 싶다면 scikit-

learn을 공부해보자. 데이터를 다듬고 분석하고 싶다면 pandas가 훌륭한 선택이 될 것이다.

“시스템이 빨라진다면 장기적으로 봤을 때 팀에 여유가 더 생기지 않을까?’’라는 기대를 할 수

도 있다. 충분한 인력을 투입한다면 어떻게든 시스템의 성능을 쥐어짜낼 수는 있겠지만, 제대

로 된 이해 없이 어설픈 최적화를 시도하면 결과적으로 팀을 문제에 빠뜨릴 뿐이다.

Cython (7.6절)을 예로 들 수 있다. Cython은 파이썬 코드에 C 같은 타입을 지정하여 C 컴

파일러로 컴파일할 수 있도록 코드를 변경해주는 컴파일러 기반의 접근인데, 기대할 수 있는

성능 향상은 놀라운 수준이지만(비교적 적은 노력으로 C에 근접하는 속도를 얻을 수 있다) 코

드를 유지보수하는 비용이 증가한다. 특히, 팀원들이 기존 파이썬 가상 머신 환경을 떠나 새로

운 기술에 익숙해져야 하기 때문에 이런 새로운 모듈은 지원하기 어려워질 가능성도 있다.

Page 39: 『고성능 파이썬』 - 맛보기

372장 프로파일링으로 병목 지점 찾기

이 장에서 배울 내용

● 코드의 속도상 병목과 RAM 사용 병목을 파악하는 방법

● CPU와 메모리 사용량을 프로파일링하는 방법

● 얼마나 자세하게 프로파일링을 해야 하는가

● 장시간 실행되는 애플리케이션을 프로파일링하는 방법

● CPython의 내부 동작

● 성능을 튜닝하는 동안 코드의 올바름을 유지하는 방법

프로파일링을 해보면 병목 지점을 찾을 수 있기 때문에 최소한의 노력으로 성능을 최대한 끌어

올릴 수 있다. 큰 수고를 들이지 않고 속도를 크게 향상시키고 자원을 적게 사용하면서, 실용

적인 측면에서 코드가 충분히 빠르고 유연한 것을 목표로 삼는다. 프로파일링을 통해 최소한의

노력으로 실용적인 결정을 내릴 수 있다.

CPU뿐만 아니라 측정 가능한 자원은 모두 프로파일링을 할 수 있다. 이 장에서는 CPU 시간

과 메모리 사용량에 대해서 살펴볼 것이다. 네트워크 대역폭과 디스크 I/O 측정에도 비슷한 기

법을 적용할 수 있다.

프로그램이 너무 느리거나 RAM을 많이 잡아먹는다면 문제가 되는 코드를 찾아내서 수정하고

싶을 것이다. 물론 프로파일링을 하지 않고 문제의 원인이라고 의심되는 코드를 수정할 수도

있다. 하지만 엉뚱한 코드를 고치게 되는 일이 다반사임을 주의하자. 감에 의존해서 코드 구조

프로파일링으로 병목 지점 찾기

CHAPTER 2

Page 40: 『고성능 파이썬』 - 맛보기

38 고성능 파이썬

를 변경하기 전에 가설을 세우고 프로파일링을 먼저 해보는 게 훨씬 합리적이다.

프로파일링이 습관이 되면 삶에 여유가 생긴다. 프로파일링을 해보면 해결해야 할 병목 지점을

빠르게 찾아낼 수 있고, 그 부분을 수정해서 원하는 만큼의 성능을 끌어낼 수 있다. 프로파일링

하지 않고 바로 최적화를 시도하면 장기적으로 봤을 때 일의 양이 더 많아지므로 항상 프로파

일링 결과를 확인하고 일하는 습관을 들이자.

2.1 효과적으로 프로파일링하기

프로파일링의 첫 번째 목표는 시스템의 어느 부분이 느린지, 혹은 어디서 RAM을 많이 쓰고 있

는지, 디스크 I/O나 네트워크 I/O를 과도하게 발생시키는 부분이 어디인지를 확인하는 것이

다. 보통 프로파일링하게 되면 평소보다 10배에서 100배까지 실행 속도가 느려지는데, 최대한

실제 상황과 유사한 조건에서 검사하고 싶다면 검사할 부분만 따로 떼어내서 검사하자. 되도록

코드를 작성할 때 모듈별로 미리 나눠놓는 편이 좋다.

이 장에서 처음 소개할 기본적인 프로파일링 기법은 IPython의 %timeit 매직 명령어와 time.

time ( ), 그리고 데코레이터timing decorator를 활용한 시간 측정이다.

그 후에 어떤 함수가 가장 오래 걸리는지를 확인할 수 있는 내장 도구인 cProfile을 살펴본다

(2.6절). 이 도구를 이용하면 좀 더 넓은 시각으로 문제를 바라볼 수 있으며 가장 핵심적인 함

수에 집중할 수 있다.

그다음으로, 선택한 함수를 한 줄씩 프로파일링할 수 있는 line_profiler를 알아본다(2.7절).

line_profiler는 각 줄이 몇 번 실행되었는지, 그리고 소요된 총 시간은 얼마인지를 검사한다.

이는 어느 부분이 느린지, 왜 느린지를 이해할 수 있는 정보다.

line_profiler의 결과를 가지고 컴파일러를 사용할 부분을 결정할 수 있다(7장 참조).

6장의 [예제 6-8]을 보면 CPU에서 실행된 명령의 수와 CPU 캐시가 얼마나 효율적으로 활용

되었는지 알아볼 수 있는 perf stat의 사용법을 배울 수 있다. 이 정보는 매트릭스 연산을 튜닝

하는 고급 기법에 활용된다. 이 장을 다 공부한 후 6장에서 해당 예제를 살펴보자.

line_profiler를 살펴본 다음 파이썬 메모리에 상주하는 모든 객체를 살펴볼 수 있는 heapy를

Page 41: 『고성능 파이썬』 - 맛보기

392장 프로파일링으로 병목 지점 찾기

소개할 것이다(2.9절). heapy는 도무지 알 수 없는 메모리 누수를 찾아야 할 때 유용하다. 장

시간 실행되는 시스템이라면 dowser가 유용할 것이다(2.10절). dowser를 사용하면 장시간

실행되는 프로세스의 객체 할당 상황을 웹 브라우저에서 확인할 수 있다.

RAM 사용량이 왜 높은지 찾는다면 memory_profiler를 사용하자(2.8절). memory_

profiler를 이용하면 시간에 따른 RAM 사용 추이를 차트로 확인할 수 있으므로 동료들에게

왜 특정 함수가 생각보다 더 많은 RAM을 사용하고 있는지 설명할 때 특히 유용하다.

NOTE_ 어떤 방식으로 프로파일링을 하든, 잊지 말고 충분한 단위 테스트를 작성해두자. 단위 테스트는 어

이없는 실수를 줄여주고 언제든 결과를 재현할 수 있도록 해준다. 단위 테스트를 작성하지 않겠다면 위험을

각오하는 편이 좋을 것이다.

컴파일하거나 알고리즘을 다시 작성하기 전에는 꼭 프로파일링하자. 코드를 더 빠르게 실행하는 가장 효과적

인 방법을 찾아줄 중요한 단서를 얻을 수 있을 것이다.

마지막으로 CPython 내부에서 쓰이는 파이썬 바이트코드에 대해 소개한다(2.11절). 바이트

코드에 대해 알고 나면 파이썬 내부를 더 잘 이해할 수 있다. 특히, 파이썬의 스택 기반 가상 머

신에 대해 이해하면 왜 특정 코딩 습관이 코드를 느리게 하는지를 알 수 있다.

이 장을 끝내기 전에 코드를 망가뜨리지 않고 최적화 작업을 할 수 있도록 프로파일링과 단위

테스트를 어떻게 통합하는지 살펴볼 것이다(2.12절).

가설을 검증하고 정확한 데이터를 수집할 수 있는 확실한 프로파일링 전략에 대해서도 알아본

다(2.13절). 여기서 터보부스트TurboBoost 같이 CPU 클럭을 동적으로 변경하는 기능이 프로파

일링 결과에 미치는 영향과 이 기능을 비활성화하는 방법에 대해서 알아본다.

여기까지 소개한 단계를 거치려면 분석하기 쉬운 함수가 필요하다. 다음에 소개할 쥘리아 집합

은 RAM과 CPU를 많이 잡아먹는 함수다. 또한 비선형적인 특성이 있어서 결과를 쉽게 예측할

수 없으므로 실행 중에 프로파일링해야 한다.

Page 42: 『고성능 파이썬』 - 맛보기

40 고성능 파이썬

2.2 쥘리아 집합

쥘리아 집합 Julia Set (http://en.wikipedia.org/wiki/Julia_set )은 CPU를 많이 사용하는 흥미로

운 주제다. 이 문제를 처음 발견한 가스통 쥘리아Gaston Julia의 이름을 딴 쥘리아 집합은 복잡한

그림을 생성하는 프랙탈을 말한다.

여기서 살펴볼 코드는 여러분이 직접 작성하는 것보다 조금 길 것이다. 이 코드는 CPU를 많이

사용하는 컴포넌트와 매우 명쾌한 입력으로 구성되어 있다. CPU와 RAM 사용량을 프로파일

링해서 어떤 코드가 CPU나 RAM이 부족한지 확인하기에 충분한 구성이다. 메모리를 소모하

는 부분과 느린 코드를 분석하기 위해 최적화는 일부러 하지 않았다. 이 장의 후반부에서 느린

부분과 메모리를 잡아먹는 부분을 수정할 것이다. 그리고 7장에서 이 함수의 전체 성능을 크게

개선할 것이다.

복소수 c가 -0.62772-0.42193j일 때의 쥘리아 집합 그래프인 [그림 2-1]과 [그림 2-3]을

그리는 코드를 분석해볼 것이다. 쥘리아 집합의 각 픽셀은 독립적으로 계산되는데, 각 점은 데

이터를 공유하지 않으므로 완전하게 병렬로 계산할 수 있다.

그림 2-1 반전된 회색조 그래프로 표현한 쥘리아 집합

c 값을 다르게 하면 다른 그림을 얻을 수 있다. 우리가 선택한 지점은 빠르게 계산되는 부분과

느리게 계산되는 부분이 있어 우리가 해볼 분석에 적합하다.

Page 43: 『고성능 파이썬』 - 맛보기

412장 프로파일링으로 병목 지점 찾기

이 문제의 흥미로운 점은 각 픽셀을 계산하는 데 몇 번이나 반복되는지 가늠하기 어려운 루프

를 사용한다는 것이다. 매 반복마다 이 좌푯값이 무한 루프에 빠지는지 아니면 인력에 의해 끌

어당겨지는지를 검사한다. 반복 횟수가 적을수록 어두운 색을 띠고 반복이 거듭될수록 밝은 색

을 띈다. 하얀색 영역은 계산하기 더 복잡하며, 때문에 생성되는 데 시간이 오래 걸린다.

이제 검사할 z 좌표의 집합을 정의하자. 이 함수는 복소수 z의 제곱에 c를 더한 값을 계산한다.

f(z)=z +c2

z의 절댓값이 2보다 작다면 이 함수를 계속 반복한다. z의 절댓값이 2 이상이면 루프를 빠져

나가고 이 좌표에 대해서 수행한 반복 횟수를 기록한다. 종료 조건을 계속 충족하지 못하면

maxiter 만큼 반복한 후에 루프를 빠져나온다. 이 z 결괏값은 나중에 복소평면 상에 픽셀을 그

리는 용도로 사용한다.

의사코드는 다음과 같다.

for z in coordinates:

for iteration in range(maxiter): # 최대 반복 횟수

if abs(z) < 2.0: # 종료 조건 검사

z = z*z + c

else:

break

# 각 z값에 대해서 반복 횟수를 기록하고 나중에 그린다.

다음 두 좌표를 가지고 이 함수를 살펴보자.

먼저, 그리게 될 쥘리아 집합 그래프의 왼쪽 위 코너 좌표를 -1.8-1.8j로 한다. 값을 계산하기

전에 abs (z ) < 2인 조건을 만족해야 한다.

z = -1.8-1.8j

print(abs(z))

2.54558441227

보다시피 왼쪽 위 좌표의 abs (z ) 값은 검사 조건을 만족하지 못하므로 루프는 한 번도 실행되

지 않고 종료된다. 따라서 z 값을 갱신하지 않는다. 이 좌표의 출력값은 0이다.

이제 그래프의 원점인 z = 0 + 0j의 경우를 보자.

Page 44: 『고성능 파이썬』 - 맛보기

42 고성능 파이썬

c = -0.62772-0.42193j

z = 0+0j

for n in range(9):

z = z*z + c

print("{}: z={:33}, abs(z)={:0.2f}, c={}".format(n, z, abs(z), c))

0: z= (-0.62772-0.42193j), abs(z)=0.76, c=(-0.62772-0.42193j)

1: z= (-0.4117125265+0.1077777992j), abs(z)=0.43, c=(-0.62772-0.42193j)

2: z=(-0.469828849523-0.510676940018j), abs(z)=0.69, c=(-0.62772-0.42193j)

3: z=(-0.667771789222+0.057931518414j), abs(z)=0.67, c=(-0.62772-0.42193j)

4: z=(-0.185156898345-0.499300067407j), abs(z)=0.53, c=(-0.62772-0.42193j)

5: z=(-0.842737480308-0.237032296351j), abs(z)=0.88, c=(-0.62772-0.42193j)

6: z=(0.026302151203-0.0224179996428j), abs(z)=0.03, c=(-0.62772-0.42193j)

7: z= (-0.62753076355-0.423109283233j), abs(z)=0.76, c=(-0.62772-0.42193j)

8: z=(-0.412946606356+0.109098183144j), abs(z)=0.43, c=(-0.62772-0.42193j)

abs (z ) < 2 조건을 만족하므로 계속 z 값을 갱신한다. 이 좌표는 300번을 반복해도 검사 조건

을 만족한다. 조건을 만족하지 않을 때까지 얼마나 많이 반복해야 하는지 알 수 없고 어쩌면 무

한 루프에 빠질 수도 있다. 최대 반복 횟수maxiter를 설정해서 무한 루프를 방지하자.

[그림 2-2]는 위 계산을 50번 반복한 그래프다. 0+0j에 대한 값이 매 8회마다 반복됨을 알 수

있지만 7회까지는 이전 값에서 약간의 편차를 가지고 있으므로, 이 시점에는 루프가 무한 루프

에 빠질지 아니면 오랫동안 실행이 될지 또는 몇 번의 반복으로 끝나게 될 것인지는 알 수 없

다. 점선으로 표시된 cutoff는 +2 경계를 나타낸다.

그림 2-2 쥘리아 집합에서 두 좌표의 변화 과정

탈출선

반복 횟수

c가 -0.62772-0.42193j일 때 두 z값에 대한 abs(z) 값의 변화

Page 45: 『고성능 파이썬』 - 맛보기

873장 리스트와 튜플

이 장에서 배울 내용

● 리스트와 튜플은 어디에 쓰는 물건인가?

● 리스트/튜플 탐색의 복잡도

● 그런 복잡도에 대한 근거

● 리스트와 튜플의 차이점

● 리스트에 원소를 추가하는 과정

● 언제 리스트를 쓰고 언제 튜플을 써야 하는가?

효율적인 프로그램을 작성하려면 사용하려는 자료구조를 확실히 이해하는 게 중요하다. 사실,

성능을 고려한 프로그래밍에서는 어떤 데이터를 어떻게 다룰지를 고민하고 그 상황에서 빠르

게 작동하는 자료구조를 선택하는 일이 큰 비중을 차지한다. 이 장에서는 리스트와 튜플이 빠

르게 작동하는 경우와 어떻게 그렇게 빠르게 작동할 수 있는지를 알아본다.

리스트와 튜플은 배열이라고 하는 자료구조의 한 종류다. 배열은 어떤 정해진 내재적 순서1에

따라 데이터를 나열해둔 것이다. 이렇게 순서를 미리 알 수 있다는 점이 중요하다. 순서를 미리

알 수 있으면, 배열 내 특정 위치에 있는 데이터를 O (1 ) 시간 복잡도로 접근할 수 있다! 그리

고 배열은 여러 가지 방법으로 구현할 수 있는데, 리스트는 동적 배열이고 튜플은 정적 배열이

1 옮긴이_ 어떤 항목이 전체 데이터 중에서 몇 번째 항목인지가 그 항목의 내부 특성으로 존재한다는 뜻이다. 모든 항목에 대해 정해진 내재적 순서가 있다면, 모든 항목의 순서를 미리 정할 수 있고, 그에 따라 어떤 자료구조 상에서 모든 항목이 어느 위치에 들어갈지를 미리 알

수 있다.

리스트와 튜플

CHAPTER 3

Page 46: 『고성능 파이썬』 - 맛보기

88 고성능 파이썬

라고 할 수 있다.

방금 설명한 내용을 곱씹어보자. 컴퓨터의 시스템 메모리는 수를 저장할 수 있는 바구니의 집

합이고, 각 바구니에 일련번호가 붙어 있는 모습으로 생각할 수 있다. 이 바구니에 저장되는 수

는 단순히 데이터(정수, 실수, 문자열, 또는 그 외 다른 자료구조)가 저장되는 메모리 위치를

가리키는 것으로 생각하면 된다.2

배열을 생성하려면 먼저 시스템 메모리 블록을 할당해야 한다. 리스트와 튜플도 마찬가지다.

이때 블록의 각 구역에는 실제 데이터의 위치를 가리키는 포인터가 저장될 것이며, 포인터의

크기는 정수형integer의 크기다. 메모리 블록 할당 과정은 커널까지 내려가 N개의 연속된 바구니

를 요청한다. [그림 3-1]은 크기가 6인 배열이 시스템 메모리에 배치된 예를 보여준다. 파이썬

의 리스트는 그 크기도 저장하기 때문에 할당된 여섯 블록 중 하나는 크기를 저장하고 나머지

다섯 개만 사용할 수 있다.

그림 3-1 크기가 6인 배열의 시스템 메모리 배치 예시

리스트에서 특정 항목을 찾으려면 그 항목이 무엇인지, 또 그 데이터가 어떤 바구니에 저장되

는지만 알면 된다. 모든 데이터는 같은 크기를 점유하므로(하나의 바구니, 더 정확히는 실제

데이터가 있는 위치를 가리키는 정수 크기의 포인터 하나) 데이터의 종류는 알 필요가 없다.

2 64비트 컴퓨터의 총 메모리가 12KB라면 725개의 바구니가 있고, 총 메모리가 52GB라면 3,250,000,000개의 바구니가 있다!

시스템 메모리시작 주소

크기가 6인 배열에 할당된 메모리

Page 47: 『고성능 파이썬』 - 맛보기

893장 리스트와 튜플

NOTE_ 메모리에 저장된, N개의 원소를 가진 리스트의 시작 위치를 알고 있을 때, 그 리스트에서 임의의 항

목을 찾으려면 어떻게 해야 할까?

예를 들어 배열의 첫 번째 항목(인덱스는 0이다)을 찾으려고 한다면 배열이 시작하는 위치(이

를 M이라 하자)에 있는 첫 번째 바구니의 값(즉, M )을 읽으면 된다. 다섯 번째(인덱스는 4다)

항목을 읽으려면 M+4 위치에서 값을 읽으면 된다. 이를 일반화하면, 배열에서 i번째 항목을

읽으려면 M+i 위치를 읽으면 된다. 따라서 연속적인 메모리에 정렬되어 저장된 데이터는 배열

의 크기와 상관없이 한 번에 읽을 수 있다. 따라서 시간 복잡도는 O (1 )이다.

예제 3-1 크기가 다른 리스트에서 항목을 읽는 데 걸린 시간

>>> %%timeit l = range(10)

...: l[5]

...:

10000000 loops, best of 3: 75.5 ns per loop

>>>

>>> %%timeit l = range(10000000)

...: l[100000]

...:

10000000 loops, best of 3: 76.3 ns per loop

순서가 알려져 있지 않은 배열에서 특정 항목을 찾으려면 어떻게 해야 할까? 순서를 이미 알고

있다면 단순히 그 특정 값을 찾으면 되지만, 그렇지 않다면 탐색을 수행해야 한다. 가장 기본적인

탐색 방법은 배열의 모든 항목을 검사해서 원하는 값이 있는지 찾아내는 선형 탐색 기법이다.

예제 3-2 리스트의 선형 탐색

def linear_search(needle, array):

for i, item in enumerate(array):

if item = = needle:

return i

return -1

이 알고리즘은 최악의 경우 O (n )의 시간 복잡도를 가지는데, 찾으려는 항목이 배열에 존재하

지 않는 경우다. 어떤 항목이 배열에 존재하지 않는지 확인하려면 모든 항목을 다 검사해야만

한다. 결국 마지막에는 return -1문을 실행하게 된다. 이 알고리즘은 list.index ( )에서 실제

Page 48: 『고성능 파이썬』 - 맛보기

90 고성능 파이썬

사용하는 것과 동일하다.

이를 개선하려면 데이터가 메모리에 어떻게 저장되는지를 알고 있거나, 데이터를 담은 바구니

가 어떤 순서로 나열되어 있는지를 이해해야만 한다. 예를 들어 파이썬의 사전dictionary과 셋set에

쓰이는 해시 테이블은 데이터의 원래 순서에 상관없는 독특한 방법으로 O (1 ) 만에 이 문제를

해결한다. 만일 데이터가 크기순으로 정렬되어 있다면 시간 복잡도를 O (log n )으로 떨어뜨리

는 방법도 존재한다. 상수 시간 안에 검색하는 방법이 있으니 이런 방식을 채택할 이유가 절대

없을 것 같다. 하지만 검색 알고리즘이 더 유연하고 창의적인 방식으로 검색을 정의할 수 있기

때문에, 경우에 따라 O (log n )이 최선일 때도 있다.

NOTE_ 다음 데이터에서 61의 위치를 찾는 알고리즘을 작성해보자.

[9, 18, 18, 19, 29, 42, 56, 61, 88, 95]

데이터가 정렬되어 있다. 어떻게 더 빠르게 탐색할 수 있을까?

힌트_ 배열을 반으로 나누면 그 왼쪽의 모든 값은 오른쪽의 모든 값보다 더 작다. 이를 이용하자!

3.1 더 효율적인 탐색

앞서 살짝 알려준대로 데이터를 먼저 정렬하면 더 나은 탐색 성능을 얻을 수 있다. 사용자 정의

객체를 사용한다면 __eq__와 __lt__ 매직 함수를 이용해서 크기를 비교할 수 있다.

NOTE_ __eq__와 __lt__ 함수를 사용하지 않으면 같은 타입의 객체만 비교할 수 있으며, 이때 해당 인스턴

스의 메모리 위치를 비교한다.

핵심은 정렬 알고리즘과 탐색 알고리즘이다. 파이썬 리스트는 정렬 알고리즘을 내장하고 있으

며 팀Tim 정렬을 사용한다. 팀 정렬은 최적의 경우 O (n ), 최악의 경우 O (n log n )의 시간 복

잡도를 가진다. 팀 정렬은 다양한 정렬 알고리즘을 활용하여 주어진 데이터에 어떤 알고리즘을

적용하는 것이 최선인지를 추측하는 휴리스틱을 사용한다(더 자세히 말하자면, 삽입 정렬과

병합 정렬 알고리즘을 조합해서 사용한다).

Page 49: 『고성능 파이썬』 - 맛보기

913장 리스트와 튜플

리스트를 정렬하고 나면 평균 O (log n )의 복잡도를 가지는 이진 탐색을 사용해서 항목을 찾

는다. 이진 탐색은 리스트의 가운데 값과 찾고자 하는 값을 비교한다. 가운데 값이 찾고자 하는

값보다 작다면 찾으려는 값은 리스트의 오른쪽에 있을 것이다. 이런 식으로 값을 찾을 때까지

리스트를 작게 나누어 탐색한다. 이렇게 하면 선형 탐색 알고리즘과 달리 리스트의 일부만 살

펴보면 된다.

예제 3-3 정렬된 리스트와 이진 탐색을 이용한 효율적인 탐색

def binary_search(needle, haystack):

imin, imax = 0, len(haystack)

while True:

if imin >= imax:

return -1

midpoint = (imin + imax) // 2

if haystack[midpoint] > needle:

imax = midpoint

elif haystack[midpoint] < needle:

imin = midpoint+1

else:

return midpoint

이 함수는 잠재적으로 무거울 수도 있는 사전에 의지하지 않고도 리스트에서 항목을 찾을 수

있다. 특히, 리스트가 정렬된 상태라면 데이터를 사전으로 바꾸는 것보다 그냥 간단히 이진 탐

색으로 데이터를 찾는 편이 더 효과적이다(사전 탐색의 시간 복잡도가 O (1 )이라 O (log n )

인 이진 탐색보다 빠르지만, 리스트를 사전으로 변경하는 시간이 O (n )만큼 걸린다. 또한 사전

은 같은 이름의 키가 중복되는 것을 허용하지 않는다).

또한 bisect 모듈을 이용하면 잘 최적화된 이진 탐색 기법으로 항목을 찾을 수 있을 뿐 아니

라 새로운 항목을 추가해도 정렬된 상태를 유지할 수 있다. bisect 모듈에 있는 새로운 항목을

추가하는 함수가 이런 기능을 제공한다. 리스트를 항상 정렬된 상태로 유지하면 원하는 항목

을 쉽게 찾을 수 있다(관련 예제는 bisect 모듈 문서에서 찾아볼 수 있다. http://bit.ly/bisect_

doc ). 추가로, bisect를 사용해 우리가 찾는 원소와 가장 가까운 값을 매우 빠르게 찾을 수 있

다. 이런 기능은 비슷한 두 데이터셋을 비교할 때 특히 유용하다.

Page 50: 『고성능 파이썬』 - 맛보기

92 고성능 파이썬

예제 3-4 bisect 모듈을 이용해서 가까운 값 찾기

import bisect

import random

def find_closest(haystack, needle):

# bisect.bisect_left는 haystack에서 needle보다 크거나 같은

# 첫 번째 값의 위치를 반환한다.

i = bisect.bisect_left(haystack, needle)

if i = = len(haystack):

return i - 1

elif haystack[i] = = needle:

return i

elif i > 0:

j = i - 1

# 여기서, i번째 값은 needle보다 크므로(반대로 j번째 값은 needle보다 작다)

# i번째 값과 j번째 값 중 어떤 값이 needle에 가까운지 비교하기 위해

# 절댓값을 사용할 필요가 없다.

if haystack[i] - needle > needle - haystack[j]:

return j

return i

important_numbers = []

for i in range(10):

new_number = random.randint(0, 1000)

bisect.insort(important_numbers, new_number)

# bisect.insort로 추가했기 때문에 important_numbers는 정렬된 상태로 추가되었다.

print("Haystack: ", important_numbers)

closest_index = find_closest(important_numbers, -250)

print("Closest value to -250: ", important_numbers[closest_index])

closest_index = find_closest(important_numbers, 500)

print("Closest value to 500: ", important_numbers[closest_index])

closest_index = find_closest(important_numbers, 1100)

print("Closest value to 1100: ", important_numbers[closest_index])

이처럼 ‘올바른 자료구조를 선택하고 일관되게 그것을 사용하는 것’은 효율적인 코드를 작성하

는 기본 법칙이기도 하다. 비록 특정 연산에는 더 효율적인 자료구조가 따로 존재할 수 있지만,

자료구조를 변환하느라 전체적인 효율성은 떨어질 수도 있다.

Page 51: 『고성능 파이썬』 - 맛보기

1014장 사전과 셋

이 장에서 배울 내용

● 사전과 셋은 어디에 쓰는 물건인가?

● 사전과 셋은 어떤 면이 비슷한가?

● 사전의 오버헤드

● 사전의 성능을 최적화하는 방법

● 네임스페이스를 유지하기 위해서 파이썬에서 사전을 사용하는 방식

셋과 사전은 정해진 내재적 순서가 있지는 않지만, 특정 데이터를 고유하게 참조할 수 있는 별

도 객체가 있는 상황에 이상적인 자료구조다. 참조하는 객체는 일반적으로 문자열이지만, 해시

가 가능한hashable 타입이라면 어떤 것도 상관없다. 이때 참조 객체를 ‘키’, 데이터를 ‘값’이라고 한

다. 사전과 셋은 거의 동일하지만 셋은 값을 가지지 않는다는 점만 다르다. 쉽게 말해 셋은 유

일한 키를 저장하는 자료구조다. 이름에서 알 수 있듯이 셋은 집합 연산을 수행할 때 매우 유용

하다.

NOTE_ 해시가 가능한 타입은 __hash__ 매직 함수, 그리고 __eq__ 혹은 __cmp__ 매직 함수를 구현한 타

입이다. 파이썬의 모든 내장 타입은 이를 구현하고 있고 모든 사용자 클래스는 기본값을 가진다. 자세한 내용

은 4.1.4절 “해시 함수와 엔트로피”를 참고하자.

3장에서 내재적 순서가 없는 리스트와 튜플은 최적의 경우 O (log n ) 시간 복잡도로 값을 찾

사전과 셋

CHAPTER 4

Page 52: 『고성능 파이썬』 - 맛보기

102 고성능 파이썬

을 수 있음을 알아보았다. 반면, 사전과 셋은 주어진 색인에 해당하는 원소를 O (1 ) 만에 찾아

준다. 삽입 연산의 시간 복잡도는 리스트/튜플과 같은 O (1 )이다.1 4.1절 “사전과 셋의 동작

원리”에서 보겠지만 이 시간 복잡도는 개방 주소open address 해시 테이블을 사용했을 경우다.2

하지만 사전과 셋도 공짜로 쓸 수 있는 것은 아니다. 먼저 사전과 셋은 일반적으로 메모리를 많

이 사용한다. 또한 삽입/탐색 시간 복잡도가 O (1 )이긴 하지만, 실제 속도는 사용하는 해시 함

수에 전적으로 의존한다. 만일 해시 함수가 느리다면 사전이나 셋의 모든 연산도 마찬가지로

느릴 것이다.

예제를 살펴보자. 전화번호부에 있는 모든 사람의 연락처 정보를 저장하려고 한다. 나중에 “홍

길동 전화번호”라는 질문에 답할 수 있는 간단한 형식으로 저장할 것이다. 리스트를 이용하면

[예제 4-1]처럼 전화번호와 이름을 연속으로 저장하여 필요한 전화번호를 찾으려면 전체 리스

트를 훑어봐야 한다.

예제 4-1 리스트를 이용한 전화번호 검색

def find_phonenumber(phonebook, name):

for n, p in phonebook:

if n = = name:

return p

return None

phonebook = [

("John Doe", "555-555-5555"),

("Albert Einstein", "212-555-5555"),

]

print("John Doe's phone number is", find_phonenumber(phonebook, "John Doe"))

1 4.1.4절 “해시 함수와 엔트로피”에서 살펴보겠지만 사전과 셋은 해시 함수에 매우 의존적이다. 만일 특정 타입에 대한 해시 함수가 O(1)이 아니라면 해당 타입을 담고 있는 사전이나 셋은 O(1)을 보장하지 못한다.

2 옮긴이_ 해시 테이블은 처음에는 데이터가 각 버킷에 골고루 분포하더라도, 확률적으로 어느 시점에는 해시값이 충돌할 수밖에 없다(생일 문제 참조, https://ko.wikipedia.org/wiki/생일_문제). 이에 따라 충돌 해결 알고리즘이 필요한데, 충돌이 발생한 버킷의 값을 버킷마

다 별도의 연결 리스트로 저장하는 체이닝(chaining) 방식과 충돌 발생 시 미리 정해진 알고리즘을 사용해 다른 버킷을 찾아서 키/값을 저장하는 개방 주소 방식이 있다. 버킷상의 원소 주소가 해시 키에 의해 정해지지 않고 열려 있기 때문에 개방 주소 방식이라 부르지만, 모

든 원소가 버킷이 할당된 영역 안에 다 저장되기 때문에 폐쇄 해싱(closed hasing)이라 부른다. 반면, 체이닝의 경우 원소가 저장되는 버킷의 주소는 해시 함수에 의해서만 정해지기 때문에 폐쇄 주소(closed address) 방식의 해싱이라고 하며, 원소가 버킷이 아닌 다른 영역(연결 리스트)에 들어갈 수도 있기 때문에 개방 해싱(open hashing)이라고 부른다. 용어를 혼동하지 않도록 주의해야 한다.

Page 53: 『고성능 파이썬』 - 맛보기

1034장 사전과 셋

NOTE_ 이 리스트를 정렬한 다음에 bisect 모듈을 이용하면 O(log n)의 시간 복잡도로 검색할 수 있다.

하지만 [예제 4-2]처럼 사전을 이용하면 ‘색인’에서 이름을 찾아서 전화번호를 바로 얻을 수 있

다. 전체 데이터를 살펴보는 대신 직접 참조를 통해 필요한 값을 간단하게 가져오는 것이다.

예제 4-2 사전을 이용한 전화번호 검색

phonebook = {

"John Doe": "555-555-5555",

"Albert Einstein" : "212-555-5555",

}

print("John Doe's phone number is", phonebook["John Doe"])

전화번호부는 꽤 두껍기 때문에 사전의 O (1 ) 시간과 리스트가 제공하는 선형 탐색의 O (n )

시간(bisect 모듈을 이용하더라도 최적의 경우 O (log n )이다)의 차이는 어마어마하다.

NOTE_ 사전을 이용한 전화번호 검색과 리스트와 bisect 모듈을 이용한 전화번호 검색의 성능 차이를 확인

할 수 있는 스크립트를 작성해보자. 전화번호부가 커질수록 얼마나 큰 차이가 날까?

반면, 전화번호부에서 이름이 중복되지 않는 사람은 몇 명인지를 알려면 셋을 이용하면 된다.

셋은 단순히 유일한 키의 모음이라는 점을 기억하자. 여기서의 키는 사람의 이름, 즉 데이터 중

지금 우리에게 필요한 속성이다. [예제 4-3]에서 볼 수 있듯이 리스트를 이용한 방법은 자료구

조에서 이름 속성을 분리하기 위해 모든 이름을 비교해야 한다.

예제 4-3 리스트와 셋에서 유일한 이름 찾기

def list_unique_names(phonebook):

unique_names = []

for name, phonenumber in phonebook: # ❶

first_name, last_name = name.split(" ", 1)

for unique in unique_names: # ❶

if unique = = first_name:

break

else:

unique_names.append(first_name)

Page 54: 『고성능 파이썬』 - 맛보기

104 고성능 파이썬

return len(unique_names)

def set_unique_names(phonebook):

unique_names = set()

for name, phonenumber in phonebook: # ❷

first_name, last_name = name.split(" ", 1)

unique_names.add(first_name) # ❸

return len(unique_names)

phonebook = [

("John Doe", "555-555-5555"),

("Albert Einstein", "212-555-5555"),

("John Murphey", "202-555-5555"),

("Albert Rutherford", "647-555-5555"),

("Elaine Bodian", "301-555-5555"),

]

print("Number of unique names from set method:", set_unique_names(phonebook))

print("Number of unique names from list method:", list_unique_names(phonebook))

❶ ❸ 전화번호부에서 모든 항목을 다 훑어야 하며 O (n )의 시간이 걸린다.

❷ 현재 이름을 이미 찾아둔 유일한 이름들과 대조한다. 현재 이름이 유일한 이름이라면 유일한 이름 보관용 리

스트에 추가한다. 그리고 리스트를 다시 검색한다. 이 과정을 전화번호부의 모든 항목에 대해 수행한다.

❸ 셋을 이용하면 이미 찾아둔 유일한 이름을 순회할 필요 없이 현재 이름을 유일한 이름 보관용 셋에 추가한다.

셋 자체가 저장된 키가 유일함을 보장하기 때문에 이미 셋에 있는 이름을 추가해도 중복되는 이름은 추가되

지 않는다. 또한 이 연산은 O (1 )의 시간 복잡도를 가진다.

리스트를 이용한 알고리즘의 내부 루프는 unique_names를 순회한다. unique_names는 처

음에는 비어 있지만 점점 내용이 채워지면서 중복 이름이 없는 최악의 경우에는 phonebook

크기만큼 커지게 된다. 이는 지속적으로 커지는 리스트에서 전화번호부에 있는 각각의 이름을

선형 탐색하는 것으로 볼 수 있다. 따라서 외부 루프의 시간 복잡도가 O (n )이고 내부 루프는

O (log n )이므로 최종 알고리즘의 시간 복잡도는 O (n log n )이다.

반면, 셋을 이용한 알고리즘은 내부 루프가 없고 set.add 연산은 전화번호부의 크기에 상관없

이 O (1 )의 시간 복잡도로 수행된다(여기에는 사소한 주의사항이 있는데, 사전과 셋의 구현에

대해서 알아볼 때 살펴보겠다). 상수 시간에 수행되지 않는 연산은 전화번호부를 순회하는 루

프뿐이므로 최종 알고리즘은 O (n )의 시간 복잡도를 가진다.

Page 55: 『고성능 파이썬』 - 맛보기

1054장 사전과 셋

10,000개의 항목 중 유일한 이름이 7,422개인 전화번호부를 각각의 알고리즘을 사용해 돌려

보면 O (n )과 O (n log n )의 차이가 얼마나 큰지 확인할 수 있다.

>>> %timeit list_unique_names(large_phonebook)

1 loops, best of 3: 2.56 s per loop

>>> %timeit set_unique_names(large_phonebook)

100 loops, best of 3: 9.57 ms per loop

보다시피 셋을 이용한 알고리즘이 267배나 빠르다! phonebook의 크기가 커질수록 이 차이

는 더 벌어진다(전화번호 10만 개에 유일한 이름이 15,574개일 때 이 차이는 557배였다).

4.1 사전과 셋의 동작 원리

사전과 셋은 모두 해시 테이블을 사용해서 O (1 )의 시간 복잡도를 가진다. 이는 임의의 키(문

자열이나 객체)를 리스트의 색인으로 변환하는 해시 함수를 아주 영리하게 사용한 결과다. 이

해시 함수와 리스트는 나중에 검색을 하지 않고도 특정 데이터가 제대로 들어 있는지 확인하는

용도로 사용된다. 데이터의 키를 리스트의 색인처럼 사용할 수 있도록 변환하는 작업을 통해

리스트와 동일한 성능을 얻을 수 있다. 또한 데이터의 어떤 내재적 순서를 의미하는 수의 색인

대신 임의의 키를 사용해서 데이터를 참조할 수 있다.

4.1.1 삽입과 검색해시 테이블을 처음 생성하면 배열을 사용할 때처럼 메모리를 먼저 할당한다. 배열에서는 데이

터를 추가하려면 사용하지 않은 메모리 블록을 찾아서 데이터를 추가하고 필요한 경우 크기를

조정했다. 해시 테이블에서는 먼저 이 연속적인 메모리에서 데이터를 넣을 위치를 정할 방법을

생각해봐야 한다.

새로운 데이터의 위치는 그 데이터의 두 가지 속성에 의해 결정된다. 하나는 키의 해시값이고,

다른 하나는 그 데이터의 값을 다른 객체와 비교하는 방법이다. 데이터를 삽입하면 먼저 키를

Page 56: 『고성능 파이썬』 - 맛보기

106 고성능 파이썬

해시한 후 마스크 처리하여 배열의 색인으로 쓸 수 있는 형태로 만든다.3 마스크는 해시값이 할

당된 메모리 블록의 수보다 작아지도록 조정한다. 만약에 8개의 메모리 블록을 할당했고 해시

값이 28,975라면, 28975 & 0b111 = 7을 블록 색인으로 사용할 수 있을 것이다. 하지만 만약

에 사전이 512개의 메모리 블록을 차지할 정도로 커지면 마스크는 0b111111111이 되어야 하

고, 블록 색인은 28975 & 0b111111111이 된다. 이제 해당 블록이 사용 중인지 검사해야 한

다. 블록이 비어 있다면 키와 값을 그 블록에 삽입한다. 이때 키도 함께 저장해야 탐색 시에 올

바른 값을 찾았는지 확인할 수 있다. 반대로 해당 블록이 사용 중이고 저장된 값이 삽입하려는

값과 같다면(내장 함수인 cmp로 비교한다) 이미 키/값이 해시 테이블에 들어 있으므로 그냥

반환하면 된다. 하지만 두 값이 다르다면 데이터를 저장할 다른 곳을 찾아야 한다.

이때 새로운 색인을 단순한 선형 검색 함수를 이용해서 계산하는데, 이를 프로빙Probing이라고

한다. 파이썬의 프로빙 메커니즘은 원래 해시값에서 더 상위의 비트를 활용한다(앞서 메모리

블록이 8개인 경우 마스킹을 통해 하위 3비트만 사용했음을 떠올려보자. 이때 사용한 마스크

는 mask = 0b111 = bin (8 - 1 )이었다). 충돌을 피할 수 있도록 이 상위 비트를 사용해서 다

음 위치를 찾는다. 새로운 색인을 생성하는 알고리즘은 다양한 선택지가 있다. 하지만 데이터

가 해시 테이블에 균일하게 분포되도록 가능한 모든 색인값을 고루 출력하는 알고리즘이어야

한다. 해시 테이블에서 데이터가 얼마나 균등하게 분포되어 있는지를 로드 팩터load factor라 하

며, 해시 함수의 엔트로피와 관련이 있다. CPython 2.7에서 사용하는 해시 색인의 계산 과정

을 [예제 4-4]에서 의사 코드로 표현하였다.

예제 4-4 사전 탐색 과정

def index_sequence(key, mask=0b111, PERTURB_SHIFT=5):

perturb = hash(key) # ❶

i = perturb & mask

yield i

while True:

i = ((i << 2) + i + perturb + 1)

perturb >>= PERTURB_SHIFT

yield i & mask

3 마스크는 수를 잘라내기 위한 2진수 값이다. 예를 들어 0b1111101 & 0b111 = 0b101 = 5는 0b1111101을 0b111로 마스킹한 것이다. 이 연산은 주어진 수에서 특정 유효 자릿수만큼만 잘라낸 것으로도 볼 수 있다.

Page 57: 『고성능 파이썬』 - 맛보기

1316장 행렬과 벡터 연산

이 장에서 배울 내용

z 벡터 연산의 병목 지점은 어디인가?

z 계산 중에 CPU를 효율적으로 사용하고 있는지 알아볼 수 있는 도구

z 산술 계산에서 numpy가 순수 파이썬보다 빠른 이유

z 캐시 미스와 페이지 폴트

z 코드 내에서 메모리 할당을 추적하는 방법

어떤 프로그램을 작성하든 언젠가는 벡터 연산과 마주치게 된다. 벡터 연산은 컴퓨터가 계산을

하는 필수적인 방식이자 프로그램을 빠르게 실행하기 위한 필수 불가결한 요소다. 원초적인 수

준에서 보면 컴퓨터는 단순히 수치를 계산할 수 있을 뿐이며, 한 번에 몇 개의 계산을 해낼 수

있느냐가 프로그램 실행 속도에 영향을 미친다.

이 장에서는 비교적 간단한 수학 문제인 확산 방정식과 그 풀이 과정에서 CPU에서 일어나는

일들을 이해해봄으로써 앞에서의 문제를 자세히 살펴보기로 한다. 파이썬 코드의 변화가 CPU

작동에 어떤 영향을 주는지와 이를 효과적으로 조사하는 방법을 이해하면 다른 문제도 더 잘

이해할 수 있다.

먼저 확산 방정식에 대해서 알아보고 순수 파이썬을 이용한 해법을 살펴본다. 이 해법에 숨어

있는 메모리 관련 문제를 밝혀내고 역시 순수 파이썬을 이용해서 이 문제를 해결한다. 여기서

numpy를 소개하며 속도 개선이 어떻게 이루어지는지 알아본다. 그리고 우리 문제를 바로 풀

행렬과 벡터 연산

CHAPTER 6

Page 58: 『고성능 파이썬』 - 맛보기

132 고성능 파이썬

수 있도록 알고리즘을 약간 수정할 것이다. 즉, 라이브러리화 하기 위한 요소를 제거하여 더 빠

르게 만들어볼 것이다. 마지막으로 이런 과정에 도움이 되는 몇 가지 외부 모듈을 소개하고 프

로파일링하기 전에 최적화부터 시도하면 안 되는 이유도 알아본다.

6.1 문제 소개

NOTE_ 여기서는 이번 장에서 우리가 살펴볼 문제를 고찰해본다. 이 장을 읽기 전에 반드시 이해해야 할 필

요는 없다. 단 [예제 6-1]과 [예제 6-2]에 소개한 알고리즘을 최적화할 예정이므로 이 절을 건너뛰더라도 이

들 예제 코드는 확실히 숙지하고 넘어가자.

이 절을 읽고 더 자세한 설명을 원한다면 『Numerical Recipes, 3판』 (윌리엄 프레스 등 지음, 캠브리지대학

교 출판부, 2007)의 17장을 참고하자.

이 장에서는 행렬과 벡터 연산을 살펴보기 위해 유체 확산 예를 계속 사용할 것이다. 확산은 유

체가 이동하여 균일하게 섞이는 메커니즘이다.

여기서는 확산 방정식의 수학적인 정의를 알아본다. 조금 복잡할 수 있지만, 쉽게 이해할 수 있

도록 곧 문제를 단순화할 예정이므로 걱정하지 말자. 우리가 풀어볼 최종 방정식의 기본을 이

해한다면 이 장을 읽는 데 도움이 된다. 앞으로 방정식보다는 코드에서의 다양한 계산식에 초

점을 맞출 것이므로 필수는 아니지만, 방정식을 이해하면 코드를 최적화하는 방식에 대한 직관

을 기를 수 있다. 코드가 의도하는 바와 알고리즘의 세세한 내용까지 이해하면 가능한 최적화

방식에 대한 깊은 통찰을 얻을 수 있다.

확산의 간단한 예로 물감을 물에 푸는 모습을 들 수 있다. 실온의 물에 물감을 몇 방울 떨어트

리면 물감이 천천히 퍼지면서 물을 물들인다. 물을 젓거나 대류가 발생할 정도로 온도가 높지

않으므로 두 액체가 섞이는 과정은 주로 확산에 의해 이루어진다. 이 방정식을 수학적으로 풀

려면 [그림 6-2]처럼 초기 상태가 어떤 모습인지 선택한 다음, 일정 시간 후의 모습이 어떨지

보기 위해 그 초기 상태를 시간에 따라 변화시킬 수 있어야 한다.그러려면 확산 과정을 설명하

는 공식을 알아야 한다. 1차원에서의 편미분 방정식으로 나타내면 확산 방정식은 다음과 같다.

∂∂

= ⋅ ∂∂

( , ) ( , )2

2

Page 59: 『고성능 파이썬』 - 맛보기

1336장 행렬과 벡터 연산

이 공식에서 u는 확산되는 유체의 양을 뜻하는 벡터다. 예를 들어 벡터의 값이 0이라면 순수하

게 물만 존재하는 것이고 값이 1이라면 물감만 존재함을 의미한다(0과 1사이의 값은 두 유체

가 섞여있음을 나타낸다). 일반적으로는 실제 유체를 나타내기 위해서 2차원(넓이)이나 3차

원(부피) 행렬로 표현한다. 이렇게 확장하면 u는 컵에 든 물을 나타내는 3차원 행렬이 되고,

단순히 x에 대한 2차 도함수second derivative를 구하는 대신 모든 축(x, y, z ) 방향에 대해 앞의 식

을 적용해야 한다. 여기서 D는 확산 계수로, D값이 클수록 쉽게 확산됨을 나타낸다. 설명을 단

순화하기 위하여 코드에서는 D = 1로 표현했지만, 어쨌든 계산에는 포함시켰다.

NOTE_ 확산 방정식은 열heat 방정식이라고도 한다. 이 경우 u는 온도를 나타내고 D는 열전도율을 나타낸

다. 열 방정식은 열이 전도되는 과정을 알려준다. 따라서 물에서 물감 몇 방울이 퍼지는 과정을 계산하는 대신

CPU에서 발생한 열이 방열판으로 전도되는 과정을 계산할 수도 있다.

여기에서 우리는 시간과 공간상에서 연속적인 확산 방정식을 불연속적인 공간과 시간으로 근

사하려 한다. 이때 오일러 방법Euler’s method을 이용한다. 오일러 방법은 도함수를 단순한 차를 사

용해 계산한다. 오일러 방법은 다음과 같이 정의한다.

∂∂

≈ + +tu x t

u x t dt u x tdt

( , )( , ) ( , )

여기서 dt는 정수다. 이 정수는 시간 흐름의 기본 단위, 또는 이 방정식을 풀 때 사용할 시간의

정밀도라고 이해해도 된다. 예를 들어 이를 동영상의 프레임률frame rate처럼 생각해도 좋다. 동영

상의 프레임률이 커지면(dt가 줄어들면) 어떤 변화가 생기는지 더 명확하게 확인할 수 있다.

사실 dt가 0에 수렴할수록 오일러 근사치는 정확해진다(이론상으로는 정확해지지만, 컴퓨터

가 사용하는 실수의 정밀도는 유한하므로 dt가 너무 작으면 계산 오류가 계산 결과보다 더 커

진다). 이와 같이 u (x, t )가 주어졌을 때 u (x, t+dt )를 계산할 수 있도록 방정식을 고쳐 쓰자.

이렇게 하는 이유는 초기 상태(컵에 든 물에 물감 한 방울을 막 떨어트린 시점의 상태를 나타

내는 u (x, 0 )에서 미래 특정 시점(u (x, dt ) )에서의 상태로 변화하는 과정을 자세히 관찰하기

위함이다. 이런 문제를 초깃값 문제 또는 코시Cauchy 문제라고 한다.

x를 포함하는 도함수에 유한 차분 근사법finite differences approximation을 이용해서 다음과 같은 최종

방정식을 유도할 수 있다.

Page 60: 『고성능 파이썬』 - 맛보기

134 고성능 파이썬

u x t dt u x t dt Du x dx t u x dx t u x t

dx( , ) ( , )

( , ) ( , ) ( , )+ = + ∗ ∗ + + + + ⋅22

여기서 dt가 프레임률을 나타낸다면 dx는 영상의 해상도를 나타낸다고 할 수 있다. dx 값이

작으면 행렬이 표현하는 셀의 크기도 작아진다. 계산을 단순화하기 위해 D = 1, dx = 1로 정

의하자. 이 두 값은 제대로 된 물리 시뮬레이션을 할 때는 매우 중요한 값이지만, 우리는 단순

히 확산 방정식을 풀어가는 과정을 살펴보는 것이 목적이므로 크게 중요하지 않다.

이 방정식을 사용해서 거의 모든 확산 문제를 풀 수 있다. 하지만 주의해야 할 사항이 몇 가지

있다. 먼저, u로 표시한 공간 색인(x )은 행렬의 색인을 나타낸다. 그러면 x가 행렬의 시작점일

때 x-dx의 값은 어떻게 구해야 하는가? 이 문제를 경계 조건boundary condition이라고 한다. “행렬

의 경계를 벗어나는 모든 값은 0 (혹은 다른 특정 값)으로 둔다”처럼 경계 조건을 고정해둘 수

있다. 아니면 주기적 경계 조건periodic boundary condition을 설정할 수도 있다. 예를 들어 행렬의 한

차원의 크기가 N이라고 할 때 그 차원에서 색인이 -1인 값은 N-1에 위치한 값이고 N 위치의

값은 색인이 0인 위치의 값과 동일하게 하는 것이다. 다시 말하면, i 위치의 값을 읽으면 (i%N )

위치의 값을 얻는다. 시간의 변화에 따른 u 값을 어떻게 저장할지도 주의해야 한다. 계산할 매

순간에 대하여 행렬이 하나씩 필요하다. 유체의 현재 상태와 다음 상태를 나타내려면 최소한 두

개의 행렬이 필요하다. 앞으로 살펴보겠지만 이런 특성이 성능에 미치는 영향은 어마어마하다.

자, 그러면 실제로 이 문제를 풀어보자. 방금 만든 방정식을 사용해서 문제를 풀어가는 과정을

[예제 6-1]에 의사 코드로 표현했다.

예제 6-1 단순(1차원) 확산 의사 코드

# 초기 조건을 생성한다.

u = vector of length N

for i in range(N):

u = 0 if there is water, 1 if there is dye

# 초기 조건을 변경한다.

D = 1

t = 0

dt = 0.0001

while True:

print("Current time is: %f" % t)

unew = vector of size N

Page 61: 『고성능 파이썬』 - 맛보기

1356장 행렬과 벡터 연산

# 행렬의 각 셀을 갱신한다.

for i in range(N):

unew[i] = u[i] + D * dt * (u[(i+1)%N] + u[(i-1)%N] - 2 * u[i])

# 갱신된 해법을 u로 옮긴다.

u = unew

visualize(u)

이 코드는 초기 조건을 취한 후 물감이 물에 퍼지는 과정을 0.0001초 간격으로 보여준다. 물감

한 방울이 시간이 흐름에 따라 서서히 물과 섞이는 과정은 [그림 6-1]에서 확인할 수 있다. 물

감 한 방울이 물에 떨어졌을 때(t = 0 )부터 물과 물감이 고루 섞이기까지(t = 10500 )를 볼 수

있다.

그림 6-1 1차원 확산 예

이제부터는 앞서 소개한 방정식의 2차원 버전을 풀어본다. 즉, 벡터(또는 색인이 하나인 행렬)

대신 2차원 행렬을 다룬다. 방정식에서 바뀌는 부분(관련된 코드도 변경해야 한다)은 y 축에

대한 2차 도함수를 취해야 한다는 점이다. 따라서 방정식도 다음과 같이 바뀐다.

1차원 확산 제곱 함수

위치

Page 62: 『고성능 파이썬』 - 맛보기

136 고성능 파이썬

∂∂

= ⋅ ∂∂

+ ∂∂

⎝⎜

⎠⎟t

u x y t Dx

u x y ty

u x y t( , , ) ( , , ) ( , , )2

2

2

2

전에 사용한 것과 같은 방식을 사용하는 2차원 확산 방정식의 의사 코드는 [예제 6-2]에서 볼

수 있다.

예제 6-2 2차원 확산을 계산하는 알고리즘

for i in range(N):

for j in range(M):

unew[i][j] = u[i][j] + dt * ( \

(u[(i+1)%N][j] + u[(i-1)%N][j] - 2 * u[i][j]) + \ # d^2 u / dx^2

(u[i][(j+1)%M] + u[j][(j-1)%M] - 2 * u[i][j]) \ # d^2 u / dy^2

)

이 모든 것을 조합하면 2차원 확산을 계산하는 파이썬 코드를 작성할 수 있다. 이번 장에서는

작성한 코드를 기초로 벤치마크를 수행할 것이다. 코드가 더 복잡해졌지만 결과는 1차원 확산

의 결과와 유사하다(그림 6-2 ).

그림 6-2 두 초기 조건 집합의 확산 예

0초, 50초, 250초일 때의 확산

t = 0 초

t = 50초

t = 250 초

Page 63: 『고성능 파이썬』 - 맛보기

1717장 C 언어로 컴파일하기

이 장에서 배울 내용

z 파이썬 코드를 저수준에서 동작하도록 만드는 방법

z JIT 컴파일러와 AOT 컴파일러의 차이점

z 순수 파이썬 코드보다 컴파일된 파이썬 코드가 빠른 이유

z 타입 지정이 파이썬 코드의 성능에 도움이 되는 이유

z C나 포트란으로 파이썬 모듈을 작성하는 방법

z C나 포트란으로 작성된 라이브러리를 파이썬에서 사용하는 방법

코드를 빠르게 하는 가장 쉬운 방법을 일을 적게 하도록 만드는 것이다. 이미 최적의 알고리즘

을 사용하고 있고 처리해야 할 데이터를 간소화했다고 가정하면, 수행할 명령어의 수를 줄이

는 가장 쉬운 방법은 코드를 기계어로 컴파일하는 것이다. 이를 위한 방법으로는 순수 C 기반

의 컴파일을 수행하는 Cython, Shed Skin, Pythran, 또는 LLVM 기반의 컴파일을 제공하는

Numba, 그리고 파이썬 가상 머신을 대체하는 내장 JIT 컴파일러를 포함하는 PyPy 등 여러

방법이 있다. 어느 방법을 선택할지는 코드를 적용할 수 있는지와 팀의 생산성에 따라 결정해

야 할 것이다.

앞에서 소개한 각 도구에는 새로운 의존성 문제가 발생하며, 추가로 Cython의 경우 파이썬과

C 언어를 합쳐놓은 새로운 언어를 익혀야 한다. Cython을 사용하면 C 언어를 모르는 팀원이

이를 지원할 수 없게 되므로 팀의 생산성을 떨어뜨리겠지만, 필요한 부분에만 잘 선택하여 적

C 언어로 컴파일하기

CHAPTER 7

Page 64: 『고성능 파이썬』 - 맛보기

172 고성능 파이썬

용한다면 이는 작은 문제에 불과하다.

CPU와 메모리를 프로파일링하다 보면 더 높은 수준의 알고리즘 최적화를 생각하게 된다. 이

런 알고리즘 변경(예컨대 계산을 피하기 위한 추가 로직이나 재연산을 하지 않도록 캐시를 활

용하는 등)은 코드에서 불필요한 작업을 제거하며, 파이썬의 문법적인 표현력은 이런 알고리

즘 개선점을 쉽게 찾아낼 수 있도록 도와준다. 12.2절에서는 파이썬 구현으로 어떻게 순수 C

구현의 성능을 능가했는지를 설명하고 있다.

이번 장에서는 다음과 같은 내용을 살펴본다.

z Cython – C 언어로 컴파일하기 위해 가장 일반적으로 사용되는 도구. numpy와 일반 파이썬 코드를 모두

커버한다(C 언어를 어느 정도 이해해야 한다).

z Shed Skin - numpy를 사용하지 않는 코드를 자동으로 C로 변환해주는 도구

z Numba - numpy 코드에 특화된 새로운 컴파일러

z Pythran - numpy와 비非 numpy 코드 모두를 커버하는 새로운 컴파일러

z PyPy - 일반 파이썬 실행 환경을 대체하는 비非 numpy 코드를 위한 JIT 컴파일러

7.13절에서는 C로 작성된 외부 확장 모듈을 파이썬에서 사용하도록 해주는 외부 함수 인터페

이스(FFI )에 대해서 살펴본다. 파이썬의 내장 API는 ctypes와 PyPy 개발자가 만든 cffi를 사

용하며 포트란-파이썬 변환을 위해서는 f2py를 사용한다.

7.1 가능한 속도 개선의 종류

컴파일을 하면 상당한 수준으로 성능이 개선된다. 단일 코어에서 적용할 수 있는 여러 방법을

먼저 살펴본 후 멀티코어 환경에서 적용할 수 있는 OpenMP를 살펴보자.

컴파일로 빨라질 수 있는 부분은 대체로 수학적인 부분이다. 같은 연산을 무수히 반복하는 코

드를 포함하기 때문이다. 아마도 그 루프에서는 임시 객체를 많이 사용하고 있을 것이다.

외부 라이브러리(정규표현식이나 문자열 연산, 데이터베이스 라이브러리 호출 등)를 호출하

는 코드는 컴파일을 해도 속도 개선을 기대하기 어렵다. 입출력 관련 코드 역시 눈에 띄는 속도

개선을 기대하기 어렵다.

Page 65: 『고성능 파이썬』 - 맛보기

1737장 C 언어로 컴파일하기

이와 유사하게, 이미 numpy의 벡터 연산을 호출하는 코드 역시 컴파일 후에도 속도 개선을

기대하기 어렵다. 반면, 파이썬 코드가 대부분이거나 루프가 코드의 대부분을 차지하는 경우

에는 컴파일로 속도 개선을 기대할 수 있다. 6장에서 살펴본 numpy 연산은 연산 과정에 임시

객체를 많이 생성하지 않으므로 컴파일이 별로 도움이 되지 않았다.

전체적으로 보면 컴파일된 파이썬 코드가 손으로 한땀 한땀 작성한 C 루틴보다 더 빠르기는 어

렵다. 하지만 훨씬 느리지도 않다. C 코드를 대상 아키텍처에 맞춰 튜닝할 수 있는 훌륭한 개발

자가 직접 작성한 C 코드가 아닌 이상 여러분이 작성한 파이썬 코드를 자동으로 C로 변환한 코

드도 손으로 작성한 C 코드만큼이나 빠를 수 있다.

수학 계산에 초점을 맞춘 코드라면 직접 작성한 포트란 루틴이 C로 작성한 동일한 루틴보다 더

빠를 것이다. 하지만 명심해야 할 것은, 지금 얘기하는 내용은 전문가 수준의 지식이 있을 경우

다. 전체적으로 보면 Cython, Pythran 혹은 Shed Skin 등을 이용해 컴파일한 결과물은 직접

작성한 C 코드만큼의 성능을 기대할 수 있다.

프로파일링을 하고 알고리즘을 개선할 때는 [그림 7-1]을 떠올리자. 프로파일링을 통해 코드

가 어떻게 동작하는지 이해하는 데 드는 작은 수고는 알고리즘 수준에서 더 현명한 선택을 할

수 있도록 해준다. 그다음에 컴파일러를 어떻게 사용할 것인지 고민하면 추가적인 성능 개선을

얻을 수 있다. 하지만 그 후로는 알고리즘을 더 개선하더라도 들이는 수고에 비해 얻게 되는 성

능 개선은 크지 않다. 그만둬야 할 시점을 아는 것이 중요하다.

그림 7-1 프로파일링하고 컴파일러를 사용하는 데 드는 노력 대비 얻을 수 있는 성능 개선 효과

쉽게 성능을 개선하는 방법과 한계 수확 체감 법칙

노력

속도 향

프로그램의 동작을 이해하기 위해 프로파일링하라.

증거에 기반해 알고리즘을 개선하라.

컴파일러를 사용해 쉽게 성능을 개선하라.

노력을 더 들이려 한다면 수확 체감에 주의하라.

Page 66: 『고성능 파이썬』 - 맛보기

174 고성능 파이썬

만일 numpy 없이 파이썬 내장 라이브러리만을 사용하고 있다면 Cython, Shed Skin, PyPy

가 주요 선택지가 될 것이다. numpy를 사용하고 있다면 Cython, Numba, Pythran이 옳은

선택이다. 이 도구들은 모두 파이썬 2.7을 지원하며 일부는 파이썬 3.2 이상을 지원한다.

다음에 살펴볼 예제들은 C 컴파일러와 C 코드를 조금 이해해야 한다. 더 깊숙이 살펴보기 전에

C 언어와 컴파일 과정에 대해서 먼저 공부해보자.

7.2 JIT 대 AOT 컴파일러

지금부터 살펴볼 도구들은 크게 두 가지로 나뉜다. 하나는 미리 컴파일하는 방식ahead of time, AOT

으로 Cython, Shed Skin, Pythran이 여기 속하며, 다른 하나는 적절한 때에 컴파일하는 방

식just in time, JIT으로 Numba, PyPy가 속한다.

AOT 방식은 사용할 컴퓨터에 특화된 정적 라이브러리를 생성한다. numpy, scipy, scikit-

learn 패키지를 내려받으면 Cython을 사용해서 라이브러리의 일부를 설치하려는 컴퓨터에

맞게 컴파일한다(혹, 컨티넘의 아나콘다 같은 배포판을 사용하고 있다면 미리 컴파일해둔 라

이브러리를 사용하게 된다). 사용하기 전에 컴파일하므로 코드에서 바로 해당 라이브러리를

사용할 수 있다.

JIT 방식은 어떠한 작업도 미리 하지 않고 적절한 때에 컴파일러가 컴파일을 시작한다. 즉, 아

무 것도 컴파일되지 않은 최초 시점에 프로그램을 실행하면 컴파일을 하느라 프로그램이 느리

게 실행되는 문제가 있다(콜드 스타트 문제). 자주 실행해야 하는 스크립트고 매번 이런 문제

를 겪어야 한다면 이 비용도 무시할 수 없는 수준이다. PyPy는 이런 문제를 안고 있으므로 짧

지만 자주 실행하는 스크립트에 PyPy를 사용하고 싶진 않을 것이다.

현재 상황으로는 AOT 컴파일러를 이용하는 것이 최적의 속도 개선 방법이지만, 직접적인 노

력이 많이 든다. JIT 컴파일러를 사용하는 방법은 직접적인 노력을 거의 들이지 않고도 상당한

속도 개선을 얻을 수 있지만, 방금 언급한 상황과 같은 문제를 만날 수 있다. 어느 쪽이 더 알맞

은 접근인지는 자신의 상황에 따라 신중히 고민하자.

Page 67: 『고성능 파이썬』 - 맛보기

1757장 C 언어로 컴파일하기

7.3 타입 정보가 실행 속도에 영향을 주는 이유

파이썬은 변수가 어떤 타입의 객체라도 참조할 수 있고, 코드 어디에서든 참조하는 객체의 타

입을 변경할 수 있는 동적 타입 언어다. 이런 특징으로 인해 가상 머신이 다음 연산에 사용할

기본적인 타입을 알 수 없으므로 기계어 수준의 최적화를 수행하기 어렵다. 코드를 더 추상화

할수록 실행 속도는 느려진다.

다음 예제에서 v는 부동소수점 타입이거나 복소수를 표현하기 위한 한 쌍의 부동소수점 수이

기도 하다. 두 조건 모두 하나의 루프 안에서 다른 시간에 발생하거나 아니면 일련의 코드 실행

중에 발생할 수도 있다.

v = -1.0

print(type(v), abs(v))

<type 'float'> 1.0

v = 1-1j

print(type(v), abs(v))

<type 'complex'> 1.41421356237

abs 함수는 타입에 따라 다르게 동작하는데 정수나 실수에 대해서는 단순히 음수를 양수로 변

환해주는 반면, 복소수에 대해서는 실수와 허수의 제곱의 합의 제곱근을 반환한다.

abs c c real c imag( ) . .= +2 2

복소수 예제에 대한 기계어는 더 많은 명령어가 관여하므로 실행 시간이 더 오래 걸린다. 변수

를 인자로 abs를 호출하기 전에 파이썬은 먼저 해당 변수의 타입을 살펴보고 어떤 버전의 함수

를 호출할지 결정한다. 이때 호출 횟수가 잦을수록 오버헤드는 더욱 증가한다.

정수 같은 파이썬의 기본 객체는 내부적으로 더 높은 수준의 파이썬 객체로 감싸져 있다(예컨

대 정수는 int 타입으로 감싸져 있다). 높은 수준의 객체는 저장에 필요한 __hash__라든가 출

력을 위한 __str__ 같은 추가 함수를 포함한다.

CPU를 많이 사용하는 코드의 내부에서는 변수의 타입을 잘 바꾸지 않는 편이다. 그래서 정적

인 컴파일과 코드를 빠르게 실행할 수 있는 여지가 있다.

만일 중간 계산 과정을 직접 수행한다면 고수준의 함수가 필요하지도 않고 객체 참조 카운트

Page 68: 『고성능 파이썬』 - 맛보기

176 고성능 파이썬

역시 필요 없다. 직접 기계어 수준으로 내려가서 기계어와 바이트를 조작해서 계산한다면 큰

오버헤드를 초래하는 고수준의 파이썬 객체를 다루는 것보다 훨씬 빠르게 계산을 끝낼 수 있

다. 이를 위해서는 객체를 사용하기 전에 미리 그 타입이 결정되어야 올바른 C 코드를 생성할

수 있다.

7.4 C 컴파일러 사용하기

다음 예제에서는 GNU C 컴파일러 도구 모음의 gcc와 g++를 사용한다. 환경 설정만 올바로

되어 있다면 다른 컴파일러(인텔의 icc나 마이크로소프트의 cl )를 사용할 수도 있다. Cython

은 gcc를 사용하고 Shed Skin은 g++를 사용한다.

대부분의 플랫폼에서 gcc는 아주 좋은 선택이다. 지원도 잘 되고 있고 나름 앞선 컴파일러다.

개선된 컴파일러(인텔 장비에서는 인텔의 icc 컴파일러가 더 나은 성능을 보여줄 가능성이 높

다)를 사용해서 성능을 끌어올리는 것이 가능하지만 관련된 기반 지식이 더 필요하고 대체 컴

파일러의 플래그를 어떻게 최적화할지도 알고 있어야 한다.

포트란 같은 언어와는 다르게 C와 C++는 수많은 외부 라이브러리로 인해 정적 컴파일에 주로

이용된다. 컴파일러와 코드 변환기(Cython 등은 이 경우 코드 변환기다)는 코드의 애너테이

션Annotation을 살펴보고 정적 최적화 과정(함수 인라이닝과 루프 펼치기 같은)을 적용할지 결정

한다. (Pythran, Numba, PyPy처럼) 중간 단계의 추상 문법 트리를 더 적극적으로 분석하면

컴파일러가 파이썬에서 코드를 표현하는 방법을 이해하여 더 나은 결과를 낼 수 있는 기회가

주어진다.

7.5 쥘리아 집합 예제 다시 보기

2장에서 쥘리아 집합 생성기를 프로파일링했다. 그 코드는 정수와 복소수를 이용해서 결과물

이미지를 생성했는데 이는 CPU를 많이 사용하는 작업이다.

그 코드에서 CPU를 가장 많이 사용하는 부분은 최종 리스트를 계산하기 위한 내부 루프다. 이

Page 69: 『고성능 파이썬』 - 맛보기

2258장 동시성

이 장에서 배울 내용

● 동시성의 정의와 유용성

● 동시성과 병렬성의 차이

● 동시에 처리할 수 있는 작업과 그렇지 못한 작업

● 동시성의 여러 패러다임

● 동시성의 이점을 살리기에 적합한 시점

● 동시성을 사용해 프로그램의 속도를 빠르게 하는 방법

I/O는 프로그램의 흐름에 큰 짐이 될 수 있다. 파일이나 네트워크 소켓에서 데이터를 읽을 때

마다 잠시 실행을 멈추고 커널에 연산을 수행하도록 요청을 보낸 다음, 그 작업이 끝날 때까지

기다려야 한다. 메모리를 할당할 때도 매번 이와 비슷한 연산이 일어난다는 사실을 알고 나면,

이 과정이 무슨 세상의 종말과 같아 보이지는 않을 것이다. 하지만 [그림 1-3]을 다시 살펴본

다면 우리가 수행하는 I/O 연산 대부분이 CPU보다 수십 배 이상 느린 장치에서 이뤄지는 일

임을 알 수 있다.

예를 들어 네트워크 소켓에 데이터를 쓰는 연산은 보통 1ms가 걸리는데, 같은 시간에 2.4

GHz CPU에서는 2,400,000개의 명령어를 처리할 수 있다. 더 나쁜 것은, 이 1ms 중 대부분

은 프로그램이 아무 일도 하지 않고 일시 정지된 채로 단지 쓰기 연산이 끝나기만을 기다린다

는 점에 있다. 이런 식으로 일시 정지된 상태를 일컬어 “I/O 대기I/O wait”라 한다.

동시성

CHAPTER 8

Page 70: 『고성능 파이썬』 - 맛보기

226 고성능 파이썬

동시성을 사용하면 I/O 연산이 완료되기를 기다리는 동안 다른 연산을 수행하여 이런 유휴 시

간을 활용할 수 있다. 예를 들어 [그림 8-1]은 세 가지 작업을 수행하는 프로그램이다. 각 작

업에는 I/O 대기 기간이 포함된다. 이 세 작업을 순차적으로 실행한다면 I/O 때문에 벌어지는

지연을 세 번 감수해야 한다.

하지만 이 세 작업을 동시에 실행한다면 한 작업이 대기할 동안 다른 작업을 수행하는 식으로

대기 시간을 감출 수 있다. 단일 스레드에서 한 번에 한 CPU만을 사용하는 작업에서도 마찬가

지 효과를 얻을 수 있다는 사실을 알아두는 것이 중요하다.

I/O에서 얻는 이익이 가장 크긴 하지만, 동시성은 I/O에만 한정된 것이 아니다. 동시성 프로

그램에서는 코드를 한 줄 한 줄 순차적으로 실행하는 대신, 특정 이벤트를 처리하는 코드를 작

성하고, 그 이벤트가 발생하면 담당 코드를 수행하도록 한다.

프로그램을 이런 식으로 모델링하여 지금 요주의 이벤트인 I/O 대기를 처리할 수 있다.

그림 8-1 동시성 프로그램과 순차적 프로그램의 비교

8.1 비동기 프로그래밍 소개

프로그램이 I/O 대기에 들어가면, 실행이 멈추고 커널이 그 I/O 요청과 관련한 저수준 연산을

처리하며(이를 문맥 전환이라 한다), I/O 연산이 끝날 때까지 프로그램은 재개되지 않는다.

문맥 전환은 상당히 비싼 연산이다. 프로그램의 상태를 저장해야 하고(CPU 수준에서는 캐시

에 저장해둔 데이터를 모두 잃어버리게 된다), CPU 사용을 포기해야 한다. 나중에 다시 실행

을 허락받으면, 마더보드에서 프로그램을 다시 초기화하고 재개를 위한 준비 작업을 수행해야

한다(물론, 이런 모든 작업은 사용자에게는 보이지 않는다).

동시성을 활용하려면 프로그램이 실행할 대상과 시점을 관리하는 “이벤트 루프event loop”를 사용

Page 71: 『고성능 파이썬』 - 맛보기

2278장 동시성

하는 것이 보통이다. 근본적으로 이벤트 루프는 단지 실행할 함수의 목록에 지나지 않는다. 목

록의 맨 앞에 있는 함수가 실행되고, 그 다음에는 두 번째 함수가 실행되는 식이다. [예제 8-1]

에서 간단한 이벤트 루프를 표현했다.

예제 8-1 간단한 이벤트 루프

from Queue import Queue

from functools import partial

eventloop = None

class EventLoop(Queue):

def start(self):

while True:

function = self.get()

function()

def do_hello():

global eventloop

print("Hello")

eventloop.put(do_world)

def do_world():

global eventloop

print("world")

eventloop.put(do_hello)

if __name__ = = "__main__":

eventloop = EventLoop()

eventloop.put(do_hello)

eventloop.start()

이런 변경이 그리 대단해 보이지 않을 수도 있다. 하지만 이벤트 루프를 비동기 I/Oasynchronous I/O

연산과 결합하면 I/O 작업을 수행하면서 엄청난 성능 향상을 얻을 수 있다. 이런 연산은 논블

로킹nonblocking으로 이루어진다. 즉, 비동기 함수로 네트워크에 데이터를 쓴다면 데이터를 다 보

낼 때까지 기다리지 않고 함수가 즉시 반환된다. 쓰기가 완료되면 이벤트가 발생하여 프로그램

이 그 사실을 알 수 있다.

이 두 개념을 한 데 모으면, 요청한 I/O 연산이 끝나기를 기다리는 동안 다른 함수를 실행하는

Page 72: 『고성능 파이썬』 - 맛보기

228 고성능 파이썬

프로그램을 만들 수 있다. 이런 기능이 없었다면 I/O 대기 상태에서 낭비해야만 했을 시간에

의미 있는 계산을 수행할 수 있게 된다.

NOTE_ 함수에서 함수로 전환하는 것도 비용이 든다. 커널이 실행할 함수를 메모리에 준비해야 하고, 캐시

의 상태도 원래의 예측과 달라질 것이다. 이로 인해 프로그램에 I/O 대기가 많을 때 동시성에서 얻는 이익이

가장 커진다. 문맥 전환에 따른 비용이 들지만, I/O 대기 시간을 활용해서 얻는 이익이 훨씬 크기 때문이다.

이벤트 루프를 사용하는 프로그래밍에는 콜백callback과 퓨처future의 두 가지 형태가 있다. 콜백

패러다임에서는 각 함수를 호출할 때 콜백이라는 인자를 넘긴다. 불려진 함수는 값을 반환하는

대신, 그 값을 인자로 실어 콜백 함수를 호출한다. 이 구조에서는 호출한 함수의 결과를 받기

위한 함수가 더해지고, 다시 그 함수의 결과를 받기 위한 또 다른 함수가 더해지는 식으로 함수

의 사슬이 만들어진다. [예제 8-2]는 콜백 패러다임의 한 예를 보여준다.

예제 8-2 콜백 예제

from functools import partial

def save_value(value, callback):

print("Saving {} to database".format(value))

save_result_to_db(result, callback) # ❶

def print_response(db_response):

print("Response from database: {}".format(db_response))

if __name__ = = "__main__":

eventloop.put(

partial(save_value, "Hello World", print_response)

)

❶ save_result_to_db는 비동기 함수다. 이 함수가 즉시 반환되면서 함수가 종료되고, 다른 코드를 실행할 수

있다. 하지만 결과 데이터가 준비되면 print_response가 호출될 것이다.

반면 퓨처는 실제 결과가 아닌 미래에 얻을 결과를 담을 프라미스promise, 약속를 반환하는 비동기

함수다. 이로 인해, 우리는 이런 비동기 함수가 반환하는 퓨처가 완료되어 필요한 값이 채워지

기를 기다려야 한다(이런 기다림은 퓨처에 대해 yield를 수행하거나, 값이 준비될 때까지 기다

Page 73: 『고성능 파이썬』 - 맛보기

2298장 동시성

리는 다른 함수를 호출하는 방식으로 이뤄진다). 퓨처 객체에 요청한 데이터가 들어오기를 기

다리는 동안 다른 계산을 수행할 수 있다. 이 개념을 제네레이터(실행을 잠시 중단했다가 나중

에 다시 재개할 수 있는 함수)라는 개념과 짝지으면, 다음과 같이 형태상 순차적인 코드와 매

우 비슷한 비동기적 코드를 작성할 수 있다.

@coroutine

def save_value(value, callback):

print("Saving {} to database".format(value))

db_response = yield save_result_to_db(result, callback) # ❶

print("Response from database: {}".format(db_response))

if __name__ = = "__main__":

eventloop.put(

partial(save_value, "Hello World")

)

❶ 여기서 save_reresult_to_db는 Future 타입의 값을 반환한다. 그 값에 대해 yield를 수행하면 퓨처의 값

이 준비될 때까지 save_value의 실행을 잠시 중단할 수 있다. 값이 준비되면 save_value가 재개되어 연산을 마무리한다.

파이썬에서는 제네레이터를 사용해 코루틴coroutine을 구현한다. 실행을 일시 중단했다가 나중에

재개할 수 있는 모든 설비가 제너레이터에 이미 마련되어 있어 편하게 구현할 수 있기 때문이

다. 우리가 작성한 코루틴은 퓨처를 yield해서 이벤트 루프가 해당 퓨처의 값이 준비될 때까지

대기하게 만든다. 퓨처의 값이 준비되면 이벤트 루프가 해당 함수를 재개하면서 퓨처의 값을

돌려보내 줄 것이다.

파이썬 2.7의 퓨처 기반 동시성 구현에서는 코루틴을 실제 함수처럼 사용하기에는 약간 이상

했다. 제네레이터는 값을 반환할 수 없으니, 이 문제를 해결하기 위해 여러 방식의 라이브러리

가 존재했다. 하지만 파이썬 3.4에서는 쉽게 코루틴을 만들면서 그 함수가 여전히 값을 반환하

도록 도와주는 새로운 방식을 도입했다.

이번 장에서는 약간의 지연이 일어나는 HTTP 서버에서 데이터를 가져오는 웹 크롤러crawler를

분석할 것이다. 이는 I/O를 처리할 때 늘 마주치는 응답 지연 현상을 대표하는 예다. 우리는 먼

저 이 문제에 대한 평이한 해법인 순차적 크롤러를 만들 것이다. 그 후 파이썬 2.7에서 gevent

와 tornado를 사용한 해법을 살펴볼 것이다. 마지막으로 파이썬 3.4가 제공하는 asyncio 라

Page 74: 『고성능 파이썬』 - 맛보기

230 고성능 파이썬

이브러리를 살펴보고, 파이썬에서 비동기 프로그래밍을 사용하는 것이 어떤 모습이 될지 살펴

볼 것이다.

NOTE_ 우리가 구현한 웹 서버는 한 번에 여러 연결을 처리할 수 있다. 여러분이 만든 I/O를 동반하는 서비

스 대부분도 마찬가지일 것이다. 대부분의 데이터베이스는 한 번에 여러 요청을 처리할 수 있고, 대부분의 웹

서버는 1만 개 이상의 동시 연결을 처리할 수 있다. 하지만 한 번에 여러 연결을 처리할 수 없는 서버1와 상호

작용을 하는 경우에는 언제나 순차적인 경우와 동일한 성능을 발휘할 수밖에 없다.

8.2 순차적 크롤러

우리가 진행할 실험에서 동시성을 제어하기 위해 URL 목록을 받아서 각각으로부터 데이터를

가져온 후 각 웹 페이지의 콘텐츠 길이를 합산하는 직렬 웹 크롤러(혹은 스크래퍼scraper )를 작

성할 것이다. name과 delay라는 두 인자를 받는 HTTP 서버에 대해 크롤러를 실행할 것이다.

delay 필드는 응답하기 전에 서버가 얼마나 오래 기다리려야 하는지를 밀리초 단위로 나타내

며, name 필드는 단지 로그에 표시하기 위해 사용하는 이름이다. 서버 소스 코드는 책 본문에

는 없고, 이 책의 예제 깃허브에서 내려받을 수 있다(https://goo.gl/bZfbU2 ).

delay 매개변수를 제어하면 서버가 질의에 응답하는 데 걸리는 시간을 시뮬레이션할 수 있다.

실제 세계에서는 느린 웹 서버, 처리하기 어려운 데이터베이스 호출, 또는 실행하는 데 오래 걸

리는 I/O 호출이 이런 경우라 할 수 있다. 순차적인 경우 이는 우리 프로그램이 I/O 대기 상태

에서 더 오래 머물러 있는다는 뜻이지만, 나중에 보여줄 동시성 예제에서는 프로그램이 다른

작업을 수행할 시간이 더 많아진다는 의미다.

추가로, HTTP 호출을 위해서 requests 모듈을 사용한다. 이 모듈을 사용하는 이유는 단순하

기 때문이다. HTTP가 간단하면서 상당히 쉽게 수행할 수 있는 I/O이기 때문에 이번 절에서는

HTTP를 사용할 것이다. 일반적으로 HTTP 라이브러리에 대한 요청을 다른 어떤 형태의 I/O

로든 바꿀 수 있다. [예제 8-3]은 순차적인 HTTP 크롤러를 보여준다.

1 레디스와 같은 일부 데이터베이스는 데이터의 일관성을 보장하기 위해 한 번에 한 연결만 허용하도록 설계하는 경우도 있다.

Page 75: 『고성능 파이썬』 - 맛보기

2519장 multiprocessing 모듈

이 장에서 배울 내용

● multiprocessing 모듈이 제공하는 기능

● 프로세스와 스레드의 차이

● 프로세스 풀의 적절한 크기를 정하는 방법

● 작업 처리에 영속적이지 않은 대기열을 사용하는 방법

● 프로세스 간 통신의 비용과 효과

● 여러 CPU를 사용해 numpy 데이터를 처리하는 방법

● 데이터 손실을 막기 위해 락을 사용해야 하는 이유

CPython은 기본적으로 여러 CPU를 사용하지 않는다. 이는 파이썬 설계가 단일 코어 시대에

이뤄졌기 때문이기도 하고, 병렬 처리를 효율적으로 실행하기가 어렵기 때문이기도 하다. 파이

썬은 우리가 병렬 처리에 사용할 수 있는 다양한 도구를 제공하지만 어느 것을 사용할지에 대

한 선택권은 우리에게 있다. 하지만 쿼드 코어 컴퓨터에서 한 코어에서만 오랜 시간 실행되는

프로세스를 보고 있는 것은 화가 나는 일이다. 따라서 이번 장에서는 모든 코어를 한꺼번에 사

용하는 방법을 살펴볼 것이다.

multiprocessing 모듈

CHAPTER 9

Page 76: 『고성능 파이썬』 - 맛보기

252 고성능 파이썬

NOTE_ 앞에서 CPython(가장 흔히 쓰이는 파이썬 구현)을 언급했음을 기억하자. 파이썬 언어 자체에는 멀

티코어 시스템의 활용을 막는 요소가 없지만, CPython은 여러 코어를 효율적으로 사용할 수 없게 구현되어

있다. 반면, 다른 구현(예를 들어 앞으로 설명할 소프트웨어 트랜잭션 메모리software transactional memory를 사용하는

PyPy 등) 중에는 이런 제약이 없는 것도 있다.

우리는 멀티코어의 세상에 살고 있다. 대부분의 노트북은 쿼드 코어이며, 데스크톱은 곧 옥타

코어가 대세가 될 것이며, 서버 CPU의 경우 10, 12, 15 코어 이상도 흔하다. 여러분의 작업을

그리 큰 노력을 기울이지 않고도 여러 CPU에 나눠서 실행할 수 있다면, 그런 방법을 고려해보

는 것이 현명할 것이다.

문제를 여러 CPU로 병렬화한다면 n 코어 시스템에서 최대 n배의 속도 향상을 기대할 수 있다.

쿼드코어 컴퓨터의 네 코어를 여러분의 작업에 사용할 수 있다면 실행 시간은 원래의 4분의 1

로 줄어들 것이다. 4배보다 더 개선될 수는 없고, 실제로는 보통 3~4배 정도 빨라질 것이다.

프로세스를 추가하면 통신에 따른 부가비용이 늘어나며 한 프로세스가 사용할 수 있는 RAM은

줄어든다. 따라서 실제로는 n배의 속도 향상을 온전히 얻는 경우는 드물다. 해결하려는 문제에

따라서는 통신 부가비용이 너무 커서 오히려 속도가 엄청나게 느려지는 경우도 볼 수도 있다.

어떤 종류의 병렬 프로그래밍에서도 그런 문제가 발생할 수 있으며, 그런 경우에는 보통 알고

리즘을 변경해야만 한다. 이로 인해 병렬 프로그래밍은 종종 예술로 간주되곤 한다.

암달의 법칙Amdahl’s law (https://en.wikipedia.org/wiki/Amdahl’s_law )에 익숙한 독자가 아니

라면, 몇 가지 배경 자료를 읽어보는 것이 좋다. 암달의 법칙은 여러분의 코드 중 일부만 병렬

화할 수 있다면 그 부분에 얼마나 많은 CPU를 할당하느냐는 중요하지 않다는 사실을 보여준

다. 결과적으로 병렬화한 프로그램도 그리 빠르게 동작하지는 않을 것이다. 여러분의 프로그램

중 상당 부분을 병렬화할 수 있다 할지라도 CPU를 더 추가해서 돌아오는 성능 향상이 점차 줄

어들게 된다. 그래서 전체 작업을 더 빠르게 실행하는 데 기여할 수 있는 CPU의 개수는 한정

되어 있다.

multiprocessing 모듈을 사용하면 프로세스나 스레드 기반의 병렬 처리를 사용해서 작업을

대기열에 분산시키고 프로세스 간에 데이터를 공유할 수 있다. 이 모듈은 주로 단일 컴퓨터에

서의 멀티코어 병렬성에 초점이 맞춰져 있다(여러 컴퓨터를 사용한 병렬 처리에는 더 나은 대

안이 있다). 가장 일반적인 사용법은 CPU 위주의 작업을 여러 프로세스로 병렬화하는 것이

다. 또한 I/O 위주의 문제를 병렬화하는 데 이 모듈을 사용할 수도 있다. 하지만 8장에서 본 것

Page 77: 『고성능 파이썬』 - 맛보기

2539장 multiprocessing 모듈

처럼 그런 목적을 위한 더 좋은 도구가 존재한다(예를 들어 파이썬 3.4부터는 새로운 asyncio

모듈이 있으며, 파이썬 2.x에는 gevent와 tornado가 있다).

NOTE_ OpenMP는 멀티코어를 활용하기 위한 저수준 인터페이스다. 아마 multiprocessing 대신

OpenMP에 집중해야 하는 게 아닌가 궁금한 독자도 있을 것이다. 7장에서는 Cython과 Pythran을 소개

했지만, 이번 장에서 OpenMP를 다루지는 않을 것이다. multiprocessing은 더 고수준에서 동작하며, 파이

썬 자료구조를 공유한다. 반면, OpenMP는 일단 컴파일되고 나면 C 원시 객체(즉 정수int나 실수float 등)

를 사용해 작동한다. 여러분의 코드를 컴파일하는 경우에만 OpenMP를 사용하는 것이 유용하다. 컴파일

하지 않는 경우라면(예를 들어 효율적인 numpy 코드를 사용하며, 이를 여러 코어에서 실행하고 싶은 경우)

multiprocessing을 계속 사용하는 것이 아마 더 나은 결정일 것이다.

작업을 병렬화하려면 순차적 프로세스를 작성하는 일반적 방식과는 약간 다르게 생각해야만

한다. 병렬화한 작업을 디버깅하는 것이 더 어려울 것이라는 사실을 받아들여야만 한다. 때로는

매우 당황스러운 경우도 있다. 개발 속도를 계속해서 높게 유지하기 위해 병렬성은 가능하면

단순하게 (컴퓨터의 성능을 모조리 짜내지 못하는 경우가 생기더라도) 유지할 것을 권장한다.

특히 어려운 주제로는 병렬 시스템에서 상태를 공유하는 것을 들 수 있다. 상태 공유가 쉬워야

만 하리라 느껴질 것이다. 하지만 상태 공유에는 상당한 부가비용이 들어가며, 그것을 제대로

하기도 힘들다. 쓰임새마다 트레이드-오프가 서로 달라서 모든 경우에 잘 들어맞는 유일한 해

법은 절대로 없다. 9.5절 “프로세스 간 통신을 사용해 소수 검증하기”에서는 동기화의 비용에

초점을 맞춰서 상태 공유를 살펴볼 것이다. 상태 공유를 피할 수 있다면 여러분의 코딩 생활이

훨씬 편해질 것이다.

사실, 어떤 알고리즘이 상태를 얼마나 공유하고 있는지만 살펴봐도 그 알고리즘이 병렬 환경

에서 얼마자 잘 작동할지 여부를 알 수 있다. 예를 들어 서로 통신하지 않고 문제를 독립적으로

풀 수 있는 여러 파이썬 프로세스가 있다면(이런 경우를 당황스러울 정도로 병렬적embarrassingly

parallel이라고 한다) 별 손해 없이 새로운 파이썬 프로세스를 추가할 수 있을 것이다.

반대로, 각 프로세스가 다른 파이썬 프로세스와 통신해야만 한다면 통신에 따른 부가비용이 점

차 처리 비용을 능가하고 전체 진행을 느리게 만들 것이다. 이는 새로운 파이썬 프로세스를 추

가할 때마다 전체 성능이 오히려 저하된다는 의미이다.

그 결과, 문제를 병렬로 효율적이게 처리하려면 때로 직관에 반대되는 방향으로 알고리즘을 바

꿔야 할 수도 있다. 예를 들어 6장의 확산 방정식을 병렬적으로 푸는 경우 각 프로세스는 실제

Page 78: 『고성능 파이썬』 - 맛보기

254 고성능 파이썬

로 다른 프로세스가 수행하는 작업을 일부 중복해서 수행하게 된다. 이런 중복은 통신해야 할

상황을 줄여줘서 전체적으로는 계산 속도를 오히려 높여준다!

다음은 multiprocessing 모듈로 처리할 수 있는 전형적인 작업의 예를 보여준다.

● CPU 위주의 작업을 Process나 Pool 객체를 사용해 병렬화한다.

● (이름이 이상하지만) dummy 모듈을 사용해서 I/O 위주의 작업을 스레드를 사용하는 Pool로 병렬화한다.

● Queue를 통해 피클링pickling한 결과를 공유한다.

● 병렬화한 작업자 사이에서 바이트, 원시 데이터 타입, 사전, 리스트 등의 상태를 공유한다.

CPU 위주의 작업에 스레드를 사용하는 언어(C++나 자바 등)를 사용해본 독자라면 파이썬의

스레드가 OS의 네이티브 스레드이며(즉, 파이썬 스레드는 실제 운영 체제가 실행하는 스레드

로, 에뮬레이션된 것이 아니다), GIL에 의해 제한되며, 한 번에 오직 한 스레드만 파이썬 객체

들과 상호작용할 수 있음을 알아야만 한다.

프로세스를 사용하여 여러 파이썬 인터프리터를 병렬로 실행할 수 있고, 각각의 인터프리터는

독립된 메모리 공간과 GIL을 가지며, 각각은 순차적으로 실행된다(따라서 각각의 GIL을 가지

고 경합하지 않는다). 이것이 파이썬에서 CPU 위주 작업의 속도를 높이는 가장 쉬운 방법이

다. 상태를 공유해야 한다면 약간의 통신 부가비용이 들어가야 한다. 이에 대해서는 9.5절 “프

로세스 간 통신을 사용해 소수 검증하기”에서 다룰 것이다.

numpy 배열을 사용해 작업하는 경우 더 커다란 배열(즉, 더 큰 2차원 행렬)을 만들어서 각

프로세스가 그 배열의 여러 부분에 병렬로 작업을 수행하게 만들 수 있는지 궁금할 것이다. 그

렇게 할 수는 있지만, 시행착오를 거치지 않고는 그 방법을 찾아내기 어렵다. 따라서 9.6절

“multiprocessing과 numpy 데이터 공유하기”에서는 6.4GB의 numpy 배열을 4개의 CPU

가 공유하게 만드는 방법을 보여줄 것이다. 데이터의 일부를 복사해서 보내는 대신(그렇게 하

면 작업에 필요한 RAM 용량이 적어도 2배로 늘어나며 통신 부가비용도 상당히 증가할 것이

다), 여러 프로세스가 기저에 있는 바이트 배열을 공유할 것이다. 이렇게 하는 것이 한 컴퓨터

에서 실행되는 지역적인 작업자 사이에 큰 배열을 공유하는 이상적인 접근 방법이다.

NOTE_ 여기서는 유닉스/리눅스 기반 컴퓨터의 multiprocessing에 대해 논의한다(이번 장은 우분투Ubuntu

를 사용해 작성했지만, 여기서 설명한 코드는 맥에서도 변경 없이 실행할 수 있을 것이다). 윈도우를 사용하는

컴퓨터에서는 공식 문서(https://docs.python.org/2/library/multiprocessing.html#windows)를 참고

해야 한다.

Page 79: 『고성능 파이썬』 - 맛보기

2559장 multiprocessing 모듈

여기서부터는 저자의 노트북에 장착된 물리 코어의 개수에 맞춰서 프로세스의 개수를 하드코

딩해 고정했다(NUM_PROCESSES=4 ). 기본적으로 multiprocessing은 가능한 한 많은 코

어를 활용할 것이다(저자의 노트북에는 하이퍼스레딩을 지원하는 4개의 물리 코어가 있어 운

영 체제는 총 8개가 존재한다고 인식한다). 일반적으로 특별히 자원을 구체적으로 관리하려는

의도가 아니라면 프로세스의 수를 하드코딩하는 것은 피해야 할 것이다.

9.1 multiprocessing 모듈 소개

multiprocessing 모듈은 파이썬 2.6에 처음 소개됐으며, 기존의 pyProcessing 모듈을 대체

하면서 파이썬의 내장 라이브러리에 추가됐다. 이 모듈의 주 구성 요소는 다음과 같다.

프로세스Process

현재 프로세스를 포크fork한 복사본이다. 이렇게 하면 새로운 프로세스 식별자가 부여되며,

운영 체제에서 별도의 자식 프로세스로 작업을 실행한다. 프로세스를 시작하고 상태를 질의

할 수 있으며, 실행할 대상 메서드를 지정할 수 있다.

풀 Pool

Process나 threading.Thread API를 감싸서 작업을 공유하고, 합쳐진 결과를 반환해주는

사용하기 편리한 작업자 풀worker pool로 만든다.

대기열 Queue

여러 생산자producer와 소비자consumer가 사용할 수 있게 해주는 FIFO선입선출 대기열이다.

파이프 Pipe

두 프로세스 사이의 단방향 또는 양방향 통신 채널이다.

관리자 Manager

프로세스 간에 파이썬 객체를 공유하기 위한 고수준의 관리된managed 인터페이스다.

Page 80: 『고성능 파이썬』 - 맛보기

256 고성능 파이썬

ctypes

프로세스를 포크한 다음 여러 프로세스가 원시 데이터 타입(정수, 실수, 바이트 등)을 공유

할 수 있게 해준다.

원시적 동기화 도구들

프로세스 간의 제어 흐름을 동기화하기 위한 락과 세마포어들이다.

NOTE_ 파이썬 3.2부터 concurrent.futures 모듈이 생겼다(PEP 3148, http://legacy.python.org/dev/peps/pep-3148/). 이 모듈은 multiprocessing의 핵심 기능을 자바의 java.util.concurrent에 기반한 더 단

순한 인터페이스를 통해 제공한다. 이전 버전의 파이썬에도 이 모듈이 역포팅됐다(https://pypi.python.org/pypi/futures). 해당 모듈이 multiprocessing처럼 유연하지는 못하기 때문에 여기서 따로 다루지는

않을 것이다. 하지만 파이썬 3 이상을 도입하는 사례가 늘고 시간이 지남에 따라 concurrent.futures가

multiprocessing을 대체하게 되리라 여겨진다.

이번 장의 나머지 부분에서는 이 모듈을 사용하는 일반적인 방법을 보여주는 예제를 여럿 다룰

것이다.

프로세스나 스레드 풀을 활용하는 몬테카를로 방식을 사용해 원주율(π )을 추정할 것이다. 이

때 일반 파이썬과 numpy를 사용한다. 이 문제는 단순하며 복잡도도 잘 알려져 있다. 따라서

쉽게 병렬화할 수 있다. 또한 numpy를 사용하는 경우 예기치 못한 결과를 볼 수 있을 것이다.

그런 다음 마찬가지로 Pool을 사용해서 소수를 찾을 것이다. 소수를 찾는 과정의 예측 불가능

한 복잡성을 살펴보고, 어떻게 해야 부하를 효율적으로(또는 비효율적으로!) 분산시켜서 계산

자원을 가장 잘 활용할 수 있는지 본다. 대기열에 대해 다루면서 소수 검색을 마칠 것이다. 그

과정에서 Pool 대신 Process 객체를 사용하고, 작업 목록과 포이즌 필poison pill을 활용해서 각

작업자의 생명 주기를 조정할 것이다.

다음으로, 소수일 가능성이 있는 수의 집합을 검증하는 과정에서 프로세스 간 통신(IPC )을 사

용하는 것을 분석할 것이다. 각 수의 부하를 여러 CPU에 분산하고 어떤 수의 약수를 발견한

경우 IPC를 사용해 검색을 빨리 중단시킴으로써 단일 CPU를 사용한 소수 검색 프로세스보다

훨씬 빠르게 소수를 검사할 수 있다. 파이썬 객체의 공유, 운영 체제가 제공하는 기본 요소들,

레디스Redis 서버에 대해서도 살펴보고, 각 방식의 복잡도와 성능의 트레이드-오프 관계를 검토

할 것이다.

Page 81: 『고성능 파이썬』 - 맛보기

32110장 클러스터와 작업 대기열

이 장에서 배울 내용

● 클러스터가 유용한 이유

● 클러스터링에 드는 비용

● multiprocessing 해법을 클러스터를 사용한 해법으로 바꾸는 방법

● IPython 클러스터는 어떻게 작동하는가?

● NSQ가 강건한 프로덕션 시스템을 만드는 데 어떤 도움을 주는가?

일반적으로 공통의 과업을 해결하기 위해 함께 협력하는 컴퓨터의 모음을 클러스터라 한다. 외

부에서 볼 때 클러스터는 커다란 단일 시스템처럼 볼 수 있다.

1990년대, 로컬 네트워크에 연결된 여러 일반 PC를 사용해서 클러스터화한 처리를 제공하는

개념이 유명해졌다. 이를 베오울프Beowulf 클러스터(http://en.wikipedia.org/wiki/Beowulf_

cluster )라 했다. 구글은 자체 데이터센터에서, 특히 맵리듀스MapReduce 작업에 일반 PC로 구

성된 클러스터를 사용함으로써 이런 방식을 더 유행시켰다(http://en.wikipedia.org/wiki/

Google_platform ). 이런 확장의 다른 극단으로 탑 500top 500 프로젝트(http://en.wikipedia.

org/wiki/TOP500 )는 매년 가장 강력한 컴퓨터 시스템의 순위를 발표한다. 일반적으로 이런

시스템들은 모두 클러스터화한 설계로 되어 있으며 대부분 리눅스를 사용한다.

클라우드상에 프로덕션 클러스터를 만들거나 기계 학습과 같이 일시적으로 수행하는 과업을

위해 필요에 따라 클러스터를 만드는 데 아마존 웹 서비스(AWS )를 자주 사용한다. AWS에서

클러스터와 작업 대기열

CHAPTER 10

Page 82: 『고성능 파이썬』 - 맛보기

322 고성능 파이썬

여러분은 60GB의 RAM과 8개의 인텔 제온 코어나 244GB의 RAM 또는 GPU가 들어간 컴

퓨터를 시간당 1.68달러에 대여할 수 있다. 계산량이 많은 작업에 AWS의 임의 클러스터 기능

을 검토해 보고픈 독자는 10.6.2절의 “IPython Parallel을 사용해 연구 지원하기”와 스타클러

스터StarCluster 패키지를 살펴보라. 서로 다른 계산 작업에는 서로 다른 클러스터 설정, 크기, 용

량이 필요하다. 이번 장에서 몇 가지 일반적인 시나리오를 정의할 것이다.

클러스터화한 해법을 살펴보기 전에 다음을 수행했는지 확인하라.

● 시스템을 프로파일링해서 병목이 어딘지 판별했는가?

● Cython과 같은 컴파일러를 활용해봤는가?

● 단일 컴퓨터에서 다중 코어를 활용해봤는가(코어가 더 많은 더 큰 컴퓨터에서 시도해봤는가)?

● RAM을 덜 사용하기 위한 기법을 활용해봤는가?

시스템을 한 컴퓨터(‘한 컴퓨터’라고 했지만 실제로는 RAM이나 CPU가 아주 많은 큰 컴퓨터

일 수도 있다)에 한정시킨다면 삶이 더 편해질 것이다. 제품이 정말 많은 CPU를 요구하거나,

데이터를 디스크에서 가져와 병렬 처리하는 능력이 필요하거나, 복원력이 높고 응답이 빨라야

한다면 클러스터로 옮겨가라.

10.1 클러스터링의 이점

클러스터의 가장 분명한 이점은 계산 요구사항에 맞게 쉽게 시스템을 키울 수 있다는 것이다.

데이터를 더 많이 처리해야 하거나 답을 더 빨리 얻어야 한다면, 단지 더 많은 컴퓨터(또는 노

드)를 추가하면 된다.

컴퓨터를 추가하면 신뢰성도 높일 수 있다. 각 컴퓨터의 구성 요소는 어느 정도 실패할 확률이

있다. 설계를 잘한다면 구성 요소를 여러 개 사용해서 클러스터의 동작이 멈추는 일이 없도록

만들 수 있다.

또한 클러스터를 사용해 동적으로 규모를 변경하는 시스템을 만들 수도 있다. 일반적인 용례는

웹 요청이나 관련 데이터(사용자의 사진 크기를 변경하거나, 비디오를 변환하거나, 음성을 인

식해 자막으로 만드는 등)를 처리하는 여러 서버를 클러스터화하고, 하루 중 수요가 늘어나는

Page 83: 『고성능 파이썬』 - 맛보기

32310장 클러스터와 작업 대기열

특정 시간대에 더 많은 서버를 활성화하는 것이다.

컴퓨터를 활성화하는 속도가 수요가 변화하는 속도를 따라갈 수 있는 한, 동적 스케일링은 사

용 패턴이 균일하지 않을 때 매우 비용 효율적인 방법이다.

클러스터링의 큰 이점 하나는 지리적으로 클러스터를 분리하더라도 여전히 중앙에서 관리할

수 있다는 것이다. 홍수나 정전 등으로 특정 지역에 장애가 생기더라도 다른 클러스터는 계속

동작할 수 있으며, 혹 남은 클러스터만으로 요구를 다 처리할 수 없다면 더 많은 처리 단위를

배당할 수 있을 것이다. 클러스터를 사용하면 서로 다른 소프트웨어 환경(서로 다른 운영 체제

버전이나 소프트웨어 등)을 실행할 수도 있다. 이렇게 하면 전체 시스템의 강건성을 높일 수

있을 것이다. 하지만 이는 전문가 수준의 주제다!

10.2 클러스터링의 단점

클러스터화한 해법으로 옮겨가려면 사고방식을 바꿔야 한다. 이는 9장에서 소개한 순차 코드

로부터 병렬 처리 코드로 옮겨갈 때 필요했던 종류의 발상의 전환을 말한다. 두 대 이상의 컴퓨

터를 사용하면 어떤 일이 벌어질지를 분명히 염두에 둬야 한다. 예컨대 컴퓨터 사이에는 지연

이 있으며, 다른 컴퓨터가 작동 중인지 알아야 하고, 모든 컴퓨터가 여러분이 작성한 소프트웨

어의 같은 버전을 실행하도록 관리해야 한다. 시스템 관리가 아마도 가장 큰 도전일 것이다.

추가로 여러분이 구현할 알고리즘에 대해 서로 떨어져 동작하지만 동기화해야 하는 추가 요소

가 더해지면 어떤 일이 벌어질지 심사숙고해야 한다. 이처럼 신경 써야 할 것이 엄청나게 늘어

날 것이다. 그로 인해 여러분의 핵심 과업에서 주의가 분산될 수 있으며, 프로그램이 커지면 이

를 위한 전담 엔지니어를 고용해야 할 것이다.

NOTE_ 이 책에서 한 컴퓨터를 효율적으로 사용하는 일에 집중하려 노력해왔던 이유는 여러분이 컴퓨터 여

러 대를 다루는 것보다 한 대를 다루는 편이 더 쉽다고 믿기 때문이다(하지만 클러스터를 다루는 것이 훨씬

재미있을 수 있음을 인정한다. 다만, 그 클러스터 시스템이 깨지기 전까지만…). (RAM이나 CPU를 추가 구매

해서) 규모를 수직 방향의 확장할 수 있다면 그 방법을 클러스터링보다 먼저 시도해보는 게 좋다. 물론 주어진

일이 수직 규모 확장으로 처리할 수 있는 범위보다 클 수도 있고, 한 컴퓨터를 사용하는 것보다 클러스터를

사용해서 얻을 수 있는 강건성이 더 중요할 수도 있다. 하지만 여러분이 이 작업을 수행하는 유일한 사람이라

면 여러분의 시간 대부분을 클러스터링 자체에 쏟아부어야 할 수도 있음을 염두에 두기 바란다.

Page 84: 『고성능 파이썬』 - 맛보기

324 고성능 파이썬

클러스터를 설계할 때는 각 컴퓨터의 설정이 다를 수도 있음을 기억해야 한다. 각 컴퓨터의 부

하도 다르고 시간도 다를 수 있다. 각 컴퓨터에 어떻게 올바른 데이터를 넣을 것인가? 노드 사

이에 작업과 데이터를 옮기는 데 드는 지연시간이 문제인가? 작업 중간에 부분적인 결과를 서

로 주고받아야 하는가? 여러 작업을 실행하는 도중에 어떤 프로세스가 실패하거나, 어떤 컴퓨

터가 죽거나, 일부 하드웨어가 자신을 지워버리면 어떤 일이 벌어지는가? 이런 문제를 고려하

지 않는다면 실패의 쓴 맛을 보게 될 것이다.

여러분은 또한 실패를 용인할 수 있다는 사실을 고려해야만 한다. 예를 들어 콘텐츠 기반의 웹 서

비스에 99.999%의 신뢰성이 필요하지는 않을 것이다. 어떤 작업이 실패하고(예컨대 그림이

충분히 빠른 시간에 표시되지 않음) 사용자가 페이지를 다시 로딩하더라도 그런 일은 모두에

게 이미 익숙한 것이다. 그런 해법이 비록 여러분이 사용자에게 제공하려고 의도한 것은 아니

지만, 약간의 실패를 받아들이면 엔지니어링과 관리 비용은 상당히 줄어들 것이다. 반면, 빈도

가 높은 주식 거래 시스템이 실패한다면 주식 시장이 입는 피해 비용은 상당할 수 있다!

고정된 인프라를 유지하는 비용도 만만치 않다. 컴퓨터를 구매하는 비용은 상대적으로 싸지만,

기계들은 잘못되려는 고약한 성향이 있다. 소프트웨어 자동 업데이트에 문제가 생길 수 있고,

네트워크 카드가 실패할 수 있으며, 디스크에는 쓰기 오류가 발생하고, 전원 공급 장치가 순간

적인 고전압을 발생시켜 데이터를 손상시킬 수 있고, 우주선cosmic ray이 RAM 모듈의 비트를 뒤

집을 수 있다. 더 많은 컴퓨터를 사용할수록 이런 문제를 해결하기 위해 허비하는 시간도 늘어

갈 것이다. 얼마 지나지 않아 전담 시스템 엔지니어를 고용하기를 바라게 될 것이고, 그에 따라

예산에 10만 불을 추가해야 할 것이다. 클라우드 기반의 클러스터를 사용하면 이런 문제 중 상

당수를 완화할 수 있다. 비용은 더 들지만, 적어도 하드웨어 유지보수를 신경 쓸 필요는 없어진

다. 또한 일부 클라우드 회사들은 임시 계산 자원을 값싸게 판매하는 스팟-가격spot-priced 인스

턴스를 제공하기도 한다(http://bit.ly/spot-instances )

조직에서 클러스터를 사용함에 따라 전체 시스템을 안전하게 재시작하는 방법을 아무도 문서

화하지 않을 가능성이 커진다. 재시작 계획을 문서화하지 않는다면, 결국 최악의 상황이 닥쳐

서야 그런 문서를 작성하게 될 수 있다고 가정해야 한다(저자 중 한 명은 이런 종류의 문제를

크리스마스이브에 디버깅한 적도 있다. 이런 크리스마스 선물을 바라는 사람은 아무도 없을 것

이다!). 그 시점에 여러분은 시스템의 각 부분이 제 속도를 내도록 만드는 데 얼마나 오랜 시간

이 걸리는지 배우게 될 것이다. 클러스터의 각 부분이 부팅하는 데 몇 분씩 걸리고, 처리 작업

을 시작하는 데도 시간이 걸린다. 순차적으로 실행해야 하는 부분이 10개라면, 전체 시스템이

Page 85: 『고성능 파이썬』 - 맛보기

32510장 클러스터와 작업 대기열

콜트 스타트cold start하는 데는 1시간이 걸릴 것이다. 그 결과, 한 시간 분량의 백로그 데이터를

처리해야만 하는 상황이 될 수 있다. 그렇다면 여러분의 시스템은 이 거대한 데이터를 제시간

에 처리할 수 있는가?

해이한 행동은 값비싼 실수의 원인이 될 수 있고, 복잡하고 예측하기 어려운 동작은 예측하기

어려운 값비싼 결과를 야기할 수 있다. 두 가지 심각한 클러스터의 실패 사례를 살펴보고, 거기

서 우리가 무엇을 배웠는지 알아보자.

10.2.1 월스트리트, 형편없는 업그레이드 전략으로 4억 6천 2백만 불을 손해보다2012년 고빈도 거래 기업인 나이트 캐피탈Knight Capital은 클러스터의 소프트웨어를 업그레이드

해서 발생한 버그로 4억 6천 2백만 불의 손해를 봤다(http://bit.ly/Wall_Street_crash ). 그 소

프트웨어는 고객의 요청보다 더 많은 주식을 주문했다.

업그레이드된 소프트웨어는 새로운 기능을 위해 오래된 플래그를 다른 목적으로 사용했다. 작

동 중인 8개의 기기 중 7개는 업그레이드되었지만, 여덟 번째 기기는 예전 코드를 사용하여 잘

못된 거래가 생겼다. 증권거래위원회는 나이트 캐피탈에 업그레이드를 검토하는 별도의 기술

자가 없었으며, 업그레이드를 검토하는 프로세스도 없었다는 사실을 지적했다.

이 실수에는 두 가지 원인이 있는 것으로 보인다. 첫 번째는 소프트웨어 개발 프로세스가 불필

요한 기능을 제거하지 않았다는 것이다. 따라서 오래된 냄새나는 코드가 계속 남아 있었다. 두

번째는 업그레이드가 완전히 성공했는지를 확인하는 수동 검토 프로세스가 없었다는 것이다.

기술적 부채는 언젠가 갚아야만 하는 비용을 추가한다(보통은 시간을 더 들여서, 여유가 있

을 때 그 부채를 없애야 한다). 코드를 작성하고 리팩토링할 때는 단위 테스트를 항상 사용하

라. 시스템 업그레이드 시 따라야 하는 점검 목록을 작성하지 않았고, 업그레이드 과정을 살펴

볼 제3의 사람도 없다면 값비싼 실패를 치르게 될 수 있다. 비행기 조종사들이 이륙 시 점검 목

록을 하나하나 따르는 데에는 다 이유가 있다. 그렇게 하면 누구도 중요한 단계를 생략할 수 없

다. 그 단계를 그 전에 얼마나 많이 해봤느냐는 아무런 관계가 없다!

Page 86: 『고성능 파이썬』 - 맛보기

326 고성능 파이썬

10.2.2 스카이프가 겪은 24시간 동안의 전 세계 서비스 장애

스카이프는 2010년 24시간 동안 전 지구적인 장애를 겪었다(http://bit.ly/Skype_outage ). 스

카이프는 P2P피어 투 피어 네트워크에 의해 지탱되고 있다. 시스템의 한 부분(오프라인 인스턴트

메시지를 처리하는)에 과부하가 걸리면 윈도우 클라이언트로부터의 응답이 늦어진다. 윈도우

클라이언트 버전 중 일부는 이렇게 늦어진 응답을 제대로 처리하지 못하고 프로그램이 죽어버

린다. 결국, 동작 중인 클라이언트의 약 40%가 죽었고, 공개된 슈퍼노드supernode 중 25%가 그

속에 들어 있었다. 슈퍼노드는 네트워크에서 데이터를 라우팅하는 데 있어 필수적이다.

25%의 라우팅 기능이 정지되면서(다시 대치되기는 했지만, 그 속도가 느렸다), 전체 네트워

크가 큰 압박을 받았다. 죽어버렸던 윈도우 클라이언트 노드들이 재시작하여 네트워크에 다시

합류하려 시도하면서 이미 과부하 상태인 시스템에 수많은 새로운 트래픽을 추가했다. 슈퍼노

드에는 너무 많은 부하가 걸릴 경우 백오프backoff하는 기능이 있었다. 그래서 트래픽이 폭증하

자 스스로를 종료시키기 시작했다.

24시간 동안 스카이프를 거의 사용할 수 없었다. 복원 프로세스는 늘어난 트래픽을 감당할 수

있도록 설정한 메가-슈퍼노드를 수백 개 설정하는 일부터 시작됐다. 그 후, 수천 개를 더 추가

했다. 다음날이 돼서야 네트워크가 회복됐다.

이 사건은 스카이프에게는 엄청나게 당혹스러운 것이었다. 분명히, 스카이프는 그 후 며칠에

걸쳐 긴박히 피해를 최소화하는 데 초점을 맞췄다. 음성 통화를 하려는 고객들은 다른 선택지

를 찾아야만 했으며, 이는 분명 경쟁자들에게는 요긴한 마케팅 기회였을 것이다.

네트워크의 복잡도와 발생했던 문제의 확산 과정을 살펴볼 때, 이런 류의 실패를 예측하고 대

응 계획을 세우는 것이 어려웠을 것으로 보인다. 스카이프 네트워크의 모든 노드에 문제가 생

기지 않았던 이유는 소프트웨어 버전과 플랫폼이 달랐기 때문이다(균일한 시스템보다는 비균

일 네트워크를 사용하는 쪽이 신뢰성 측면에서 이점이 있다).

10.3 일반적인 클러스터 설계

어느 정도 비슷한 성능의 컴퓨터로 애드혹ad-hoc 로컬 클러스터를 구축하는 것부터 시작하는 것

이 일반적이다. 그런 클러스터에 오래된 컴퓨터를 추가해도 되는지 궁금할 것이다. 하지만 오

Page 87: 『고성능 파이썬』 - 맛보기

35111장 RAM 아껴 쓰기

이 장에서 배울 내용

● RAM을 덜 사용해야 하는 이유

● 원시 타입의 수를 많이 저장하는 경우 numpy와 array를 사용하는 것이 더 좋은 이유

● 많은 텍스트 정보를 RAM에 효율적으로 저장하는 방법

● 1바이트로 1e77 (1077 )을 (대략적으로) 셀 수 있는 방법

● 블룸 필터의 정의와 이 필터가 필요한 이유

RAM이 부족해지기 전까지는 RAM을 얼마나 사용 중인지에 관심을 기울이는 일이 별로 없다.

하지만 코드를 더 큰 규모로 실행하다가 RAM이 부족해지면 갑작스럽게 그 부분이 걸림돌이

된다. 실행 코드를 어떤 컴퓨터의 RAM 용량에 맞게 만든다는 것은 관리해야 할 컴퓨터가 하나

줄어든다는 뜻이며, 더 큰 프로젝트를 감당할 수 있는 용량을 계획할 수 있는 길이 생긴다는 의

미다. 희소한 자원인 RAM을 다 소모해버리는 이유를 알고 더 효율적으로 사용하는 방법을 고

려한다면 규모가변성 문제를 다룰 때 도움이 될 것이다.

압축 능력이 있는 데이터 컨테이너를 활용해 RAM을 절약하는 방법도 있다. 이번 장에서는 트

라이trie (순서가 정해진 트리 자료구조)에 대해 살펴보고, 1.1GB의 문자열을 큰 성능 변화 없

이 254MB 정도로 압축할 수 있는 DAWG를 살펴본다. 세 번째 접근 방법은 정확도를 희생해

서 저장 장치를 적게 사용하도록 만드는 것이다. 이를 위해 수를 ‘대략적으로’ 세는 방법과 집합

에 속하는지 여부를 ‘대강’ 알아내는 방법을 살펴볼 것이다. 이런 기법을 사용하면 정확한 알고

RAM 아껴 쓰기

CHAPTER 11

Page 88: 『고성능 파이썬』 - 맛보기

352 고성능 파이썬

리즘을 사용할 때보다 RAM 사용량을 극적으로 줄일 수 있다.

RAM 사용량과 관련해 고려할 점은 “데이터에는 질량이 있다”는 것이다. 데이터가 크면 클수

록 그 데이터를 여기저기 옮기는 데는 비용이 든다. RAM 사용에 인색해지면 버스를 통해 데

이터를 더 빨리 전달할 수 있고, 한정된 캐시에도 더 잘 들어맞기 때문에 데이터를 더 빨리 소

비할 수 있다. 또한 데이터를 저장하는 데 쓰는 오프라인 저장 장치(예컨대 하드디스크나 원격

데이터 클러스터)는 여러분의 컴퓨터보다 훨씬 느리다. 적절한 자료구조를 선택해서 모든 데

이터를 한 컴퓨터에서 처리하도록 만들기 위해 노력하라.

파이썬 객체가 사용하는 RAM 용량을 측정하기란 놀랍도록 어렵다. 내부적으로 파이썬 객체가

어떤 구조로 표현되는지 꼭 알 필요는 없고, 운영 체제를 통해 몇 바이트가 할당됐는지 알아보

면 프로세스에 할당된 전체 메모리 용량을 알 수 있을 것이다. 어느 경우든, 전체 메모리 사용

량에서 각각의 파이썬 객체들이 얼마나 많은 비율을 차지하는지를 정확히 알 수는 없다.

어떤 객체와 라이브러리는 내부에 할당된 크기를 정확히 보고하지 않는다. 또, 경우에 따라서

는 메모리 할당에 대해 전혀 알려주지 않는 외부 라이브러리를 한 차례 감싸는 파이썬 라이브

러리나 객체도 존재한다. 따라서 우리는 최선을 다해 추정하는 수밖에 없다. 이번 장의 설명은

전체적으로 RAM을 가장 적게 사용하는 데이터 표현 방식이 무엇일지 결정할 때 도움이 될 것

이다.

11.1 원시 타입 객체는 비싸다

리스트와 같은 컨테이너를 사용해서 수백, 수천의 원소를 저장해서 작업하는 경우가 자주 있

다. 하지만 저장하는 원소의 개수가 많아지면 RAM 사용량이 문제가 될 수 있다.

원소가 1억 개인 리스트는 모든 원소가 같은 객체인 경우 대략 760MB의 RAM을 사용한다. 만

약 1억 개의 서로 다른 객체(예컨대 유일한 정수)를 저장한다면, 몇 기가바이트나 되는 RAM

을 사용할 수도 있다! 유일한 객체 하나마다 RAM을 할당해야 한다.

[예제 11-1]에서는 리스트에 정수 0을 많이 집어넣는다. 어떤 객체에 대한 참조 1억 개를 저장

한다면(해당 객체의 인스턴스의 크기와는 관계없다) 리스트가 참조만을 저장하기 때문에 대략

760MB의 메모리 비용이 든다고 하였다. memory_profiler 사용법이 기억나지 않으면 2.8절

Page 89: 『고성능 파이썬』 - 맛보기

35311장 RAM 아껴 쓰기

“memory_profiler를 사용해서 메모리 사용량 진단하기”를 살펴보라. 여기에서는 IPython에

서 %load_ext memory_profiler를 사용해서 새로운 매직 함수로 memory_profiler를 읽어

들인다.

예제 11-1 리스트에 같은 정수를 1억 개 저장하는 경우의 메모리 사용량 측정하기

In [1]: %load_ext memory_profiler # %memit 매직 함수를 로드한다.

In [2]: %memit [0]*int(1e8)

peak memory: 790.64 MiB, increment: 762.91 MiB

다음 예제를 위해 새로운 셸을 시작할 것이다. memit을 처음 호출한 결과에서 알 수 있듯, 새

로 시작한 IPython 셸은 RAM을 대략 20MB 사용한다. 그런 다음 1억 개의 유일한 수로 이뤄

진 임시 리스트를 만든다. 이제 RAM 사용량을 대략 3.1GB까지 치솟는다.

주의_ 실행 중인 프로세스에서는 메모리를 캐시할 수 있다. 따라서 memit을 사용해 메모리를 프로파일링할

때는 항상 파이썬 셸을 종료한 후 다시 시작하는 편이 더 안전하다.

memit 명령이 끝나고 나면 임시 리스트 객체에 할당된 자원이 해제된다. 마지막으로 memit

을 호출한 결과를 보면 메모리 사용량이 대략 2.3GB를 유지한다는 것을 알 수 있다.

NOTE_ 해답을 읽기 전에 쓰레기 수집기가 메모리를 해제했음에도 파이썬 프로세스가 여전히 2.3GB의

RAM을 사용하는 이유가 무엇일지 생각해보라.

예제 11-2 리스트에 1억 개의 서로 다른 정수를 저장하는 경우의 메모리 사용량 측정하기

# 메모리 상태를 초기화하기 위해 새로운 IPython 셀을 사용한다.

In [1]: %load_ext memory_profiler

In [2]: %memit # 이 프로세스가 현재 소용중인 RAM의 양을 보여준다.

peak memory: 20.05 MiB, increment: 0.03 MiB

In [3]: %memit [n for n in xrange(int(1e8))]

peak memory: 3127.53 MiB, increment: 3106.96 MiB

In [4]: %memit

peak memory: 2364.81 MiB, increment: 0.00 MiB

Page 90: 『고성능 파이썬』 - 맛보기

354 고성능 파이썬

비록 더 이상 사용되지는 않지만, 2.3GB의 대부분을 1억 개의 정수 객체가 차지한다. 파이썬

은 나중에 사용하기 위해 정수와 같은 원시 객체를 캐시해둔다. RAM이 한정돼 있는 시스템에

서는 이런 동작이 문제를 일으킬 수 있으니 이런 원시 객체들이 캐시 크기를 키울 수도 있다는

사실을 알아둬야만 한다.

그런 다음에 [예제 11-3]의 memit을 보면, 1억 개의 원소를 넣은 리스트를 다시 만들면 대략

760MB의 메모리만을 사용한다는 사실을 알 수 있다. 그래서 여기서 memit을 호출한 부분에

서의 전체 메모리 사용량은 다시 대략 3.1GB가 된다. 늘어난 760MB는 컨테이너(리스트)에

만 사용된 것이다. 그 안에 들어 있는 정수 객체들은 이미 캐시 안에 존재하기 때문에 새로 만

드는 대신 캐시된 객체를 다시 사용한다.

예제 11-3 1억 개의 서로 다른 정수를 다시 한 번 저장하면서 메모리 사용량 측정하기

In [5]: %memit [n for n in xrange(int(1e8))]

peak memory: 3127.52 MiB, increment: 762.71 MiB

다음 절에서는 array 모듈을 사용해서 비용을 덜 들이면서 1억 개의 정수를 저장하는 방법을

살펴볼 것이다.

11.1.1 array 모듈은 원시 객체를 더 적은 비용으로 저장한다

array 모듈은 정수, 부동소수점 수, 문자와 같은 원시 타입의 데이터를 더 효율적으로 저장한

다. 하지만 복소수나 클래스에 대해서는 그렇지 않다. array는 연속적인 RAM 블록을 만들어

서 저장 대상 데이터를 집어넣는다.

[예제 11-4]에서 1억 개의 정수(각각은 8바이트임)를 연속된 메모리 청크chunk에 저장했다. 전

체적으로 프로세스는 대략 760MB의 메모리를 소비한다. 여기서 사용한 방식과 앞에서 본 유

일한 정수들을 넣은 리스트 방식의 차이는 2,300MB - 760MB = 1.5GB다. 상당한 양의 RAM

을 절약할 수 있다는 것을 알 수 있다.

Page 91: 『고성능 파이썬』 - 맛보기

35511장 RAM 아껴 쓰기

예제 11-4 760MB의 RAM을 사용해 1억 개의 정수로 이뤄진 배열 만들기

In [1]: %load_ext memory_profiler

In [2]: import array

In [3]: %memit array.array('l', xrange(int(1e8)))

peak memory: 781.03 MiB, increment: 760.98 MiB

In [4]: arr = array.array('l')

In [5]: arr.itemsize

Out[5]: 8

array에 저장된 유일한 정수들은 파이썬 객체가 아니라는 사실을 알아두자. 그것들은 배열 내

의 바이트들일 뿐이다. 그중 어떤 정수를 역참조한다면 새로운 파이썬 int 객체를 새로 만들 것

이다. 따라서 모든 배열 원소에 대해 계산을 수행한다면 전체적으로는 메모리를 절약하지 못할

것이다. 그 대신 배열을 외부 프로세스에 전달하거나 원소 중 일부만을 사용하는 경우라면 정

수의 리스트를 사용할 때보다 상당한 양의 RAM을 절약할 수 있다는 사실을 발견할 것이다.

NOTE_ Cython을 사용해 큰 배열에 대해 작업하고 numpy에 대한 외부 의존관계를 만들고 싶지 않다면,

추가 메모리를 사용하지 않고 데이터를 array에 저장한 다음 Cython에 그 배열을 넘길 수 있다.

array 모듈은 정밀도가 바뀌는 타입 중 일부만을 지원한다(예제 11-5 ). 허용 한도 내에서 가

능한 한 가장 낮은 정밀도를 택하라. 그래야 꼭 필요한 만큼의 RAM만을 사용해 낭비를 없앨

수 있다. 각 타입의 바이트 크기는 플랫폼에 따라 달라질 수 있다는 사실도 알아두자. 여기서

보여주는 크기들은 32비트 플랫폼에서의 정보이며(최소 크기를 보여준다), 우리가 실험에 사

용한 것은 64비트 노트북이었다.

예제 11-5 array 모듈이 제공하는 기본 타입들

In [5]: array? # IPython의 마법으로 help(array)와 비슷한 역할을 한다.

Type: module

String Form:<module 'array' (built-in)>

Docstring:

This module defines an object type which can efficiently represent

an array of basic values: characters, integers, floating point

numbers. Arrays are sequence types and behave very much like lists,

except that the type of objects stored in them is constrained. The

type is specified at object creation time by using a type code, which

is a single character. The following type codes are defined:

Page 92: 『고성능 파이썬』 - 맛보기

356 고성능 파이썬

Type code C Type Minimum size in bytes

'c' character 1

'b' signed integer 1

'B' unsigned integer 1

'u' Unicode character 2

'h' signed integer 2

'H' unsigned integer 2

'i' signed integer 2

'I' unsigned integer 2

'l' signed integer 4

'L' unsigned integer 4

'f' floating point 4

'd' floating point 8

The constructor is:

array(typecode [, initializer]) -- create a new array

numpy는 더 다양한 타입을 담을 수 있는 배열을 제공한다. 한 원소당 바이트 수를 더 잘 제어

할 수 있을 뿐 아니라, 복소수나 datetime 객체 등을 사용할 수 있다. complex128 객체는 원

소당 16바이트를 사용하며, 각 원소는 8바이트 부동소수점 수로 되어 있다. 파이썬의 배열에는

complex 객체를 저장할 수 없지만, numpy에서는 아무 노력을 하지 않아도 바로 저장된다.

numpy에 대한 기억을 되살리고 싶은 독자는 6장을 다시 살펴보라.

[예제 11-6]은 numpy의 다른 기능을 보여준다. 원소의 개수, 각 원시 데이터의 크기, 그리

고 내부의 RAM 블록이 차지하는 전체 메모리 용량 등을 질의할 수 있다. 이 용량에는 파이썬

객체로 인한 부가비용은 포함되지 않음을 기억하라(배열에 저장하는 데이터의 크기에 비하면,

보통 이 부가비용은 매우 적다).

예제 11-6 numpy 배열에 더 복잡한 타입 저장하기

In [1]: %load_ext memory_profiler

In [2]: import numpy as np

In [3]: %memit arr=np.zeros(1e8, np.complex128)

peak memory: 1552.48 MiB, increment: 1525.75 MiB

In [4]: arr.size # len(arr)와 동일

Out[4]: 100000000

In [5]: arr.nbytes

Out[5]: 1600000000

Page 93: 『고성능 파이썬』 - 맛보기

39712장 현장에서 얻는 교훈

이 장에서 배울 내용

● 성공적인 스타트업이 많은 양의 데이터와 기계 학습을 다루는 방법

● 어떤 모니터링과 배포 기술이 시스템을 안정적으로 유지해주나?

● 성공적인 CTO가 자신의 기술과 팀으로부터 배운 교훈

● PyPy를 얼마나 널리 배포할 수 있나?

이번 장에서는 대량의 데이터와 속도가 결정적인 상황에서 파이썬을 사용 중인 여러 성공적인

기업의 사례를 소개한다. 해당 기업에서 다년간 근무한 핵심 인물들이 각 사례를 작성했다. 이

들은 자신의 기술적인 선택과 그 과정에서 어렵게 얻은 지혜에 대해서도 풀어 놓았다.

12.1 어댑티브랩의 소셜 미디어 분석, 소마(SoMA)

벤 잭슨(adaptivelab.com )

어댑티브랩Adaptive Lab은 고객의 제품 개발과 혁신을 도와주는 기업으로 런던의 테크 시티Tech City에

자리 잡고 있다. 우리는 린lean 방식의 사용자 중심적인 방법을 제품 설계와 배포에 사용하며,

스타트업부터 대기업에 이르기까지 다양한 회사와 협력하고 있다.

현장에서 얻는 교훈

CHAPTER 12

Page 94: 『고성능 파이썬』 - 맛보기

398 고성능 파이썬

유고브YouGov는 전 세계의 사람들이 어떤 생각을 하고 어떤 일을 하는지에 대한 통찰을 지속적

이고 정확하게 실시간으로 제공하는 것을 목표로 하는 세계적 시장 조사 기업이다. 그리고 우

리가 그들에게 제공하는 것이 바로 그런 것이었다. 어댑티브랩은 소셜 미디어에서 벌어지는 여

러 실제 논의를 모니터링하여 (설정 가능한) 다양한 주제에 대해 사용자가 어떻게 느끼는지에

관한 통찰을 얻는 방법을 설계했다. 우리는 대용량의 스트리밍 정보를 획득/처리한 다음, 무한

정 저장했다가, 강력하면서 필터링 인터페이스를 통해 다양한 정보를 제공할 수 있는 확장성

있는 실시간 시스템을 구축했다. 물론 파이썬으로 만들었다.

12.1.1 어댑티브랩에서의 파이썬파이썬은 우리의 핵심 기술 중 하나다. 우리는 파이썬을 성능 결정적인 애플리케이션에 사용하

며, 파이썬 개발 역량을 갖춘 고객과 일할 때면 우리도 항상 파이썬을 사용하여, 우리가 작업한

결과를 고객이 흡수할 수 있도록 도와준다.

파이썬은 작고, 완결성 있으며, 오래 실행돼야 하는 데몬에 이상적이다. 그리고 장고나 피라미

드와 같이 유연하고 기능이 풍부한 웹 프레임워크와 함께 사용하면 더욱 좋다. 파이썬 커뮤니

티는 번성하고 있다. 그래서 공개된 오픈 소스 도구도 아주 다양하여 우리 개발자들은 사용자

를 위한 문제를 해결하고, 새롭고 혁신적인 요소를 만들어내는 일에만 집중할 수 있다.

어댑티브랩은 파이썬으로 만들었지만 언어와 무관한 방식으로 구축한 여러 도구를 모든 프로

젝트에 재사용한다. 예를 들어 서버 프로비저닝provisioning (요청에 따라 서버를 설정하고 제공하

는 것)에는 솔트스택SaltStack을 사용하고, 장시간 실행되는 프로세스를 관리하기 위해 모질라의

서커스Circus를 사용한다. 우리가 잘 아는 언어로 작성된 오픈 소스 도구를 사용하면 문제가 생

겼을 때 그 문제를 직접 해결한 다음, 그 해법을 커뮤니티에 기여할 수 있다는 장점이 있다.

12.1.2 소마의 설계소셜 미디어 분석 도구는 매순간 생성되는 대량의 소셜 미디어 데이터를 감당할 수 있어야 하

고, 많은 정보를 실시간으로 읽고 쓸 수 있어야 한다. 여러 가지 데이터 저장소와 검색 엔진을

검토한 다음, 우리는 실시간 문서 저장소로 일래스틱서치Elasticearch를 선택했다. 이름이 말해주

는 것처럼 일래스틱서치는 규모 가변성이 높으면서도 사용하기 쉽고, 검색뿐 아니라 통계적인

Page 95: 『고성능 파이썬』 - 맛보기

39912장 현장에서 얻는 교훈

응답 등 다양한 기능을 제공하여 우리의 목적에 딱 들어맞았다. 일래스틱서치 자체는 자바로

만들어졌지만, 잘 설계된 최신 시스템들이 그렇듯이 좋은 API를 제공하고 파이썬 라이브러리

와 튜토리얼도 잘 되어 있다.

우리가 설계한 시스템은 레디스에 들어 있는 셀러리를 대기열로 사용해서 처리나 인덱싱과 무

관하게 많은 양의 데이터 스트림을 빠르게 원하는 개수의 다른 서버에 전달한다. 복잡한 전체

시스템의 각 구성 요소는 작고 단순하며 단독으로도 작동할 수 있도록 설계됐다. 각 구성 요소

는 감정 파악용 대화 분석이나 일래스틱서치에 인덱싱하기 위해 문서를 준비하는 등의 한 가지

작업에 집중한다. 이들 중 몇 가지는 모질라의 서커스를 사용해 데몬으로 작동하도록 설정했

다. 서커스는 그 모든 프로세스를 시작하고 운용하면서 개별 서버에서 규모를 확장하거나 축소

한다.

솔트스택을 사용해 복잡한 클러스터를 정의해 제공하며, 모든 라이브러리, 언어, 데이터베이

스, 문서 저장소의 설정을 처리한다. 또한 명령줄에서 임의의 작업을 실행하게 해주는 파이썬

도구인 패브릭Fabric을 사용한다. 서버를 코드로 정의하면 프로덕션 환경에도 완전히 들어맞고,

설정의 버전을 제어할 수 있으며, 모든 것을 한 곳에 저장할 수 있는 등의 여러 이점이 있다. 또

한 서버 정의 자체가 클러스터가 요구하는 설정이나 의존 관계에 대한 문서 역할도 한다.

12.1.3 개발 방법론우리는 프로젝트에 새로 참여한 사람이 가능한 한 빠르게 코드를 추가하고, 자신 있게 배포할

수 있도록 만드는 것을 목적으로 한다. 바그란트Vagrant를 사용해 복잡한 시스템을 프로덕션 환

경과 완벽히 동등한 가상 머신에서 지역적으로 빌드할 수 있게 한다. 새로 온 사람은 간단히

vagrant up 명령만 내리면 작업하는 데 필요한 모든 의존 관계가 알아서 설정된다.

애자일 방식으로 함께 계획을 세우고, 구조적인 결정을 논의하며, 작업 추정치에 대한 합의를

끌어낸다. 소마의 경우 각 스프린트sprint마다 ‘기술적 부채’를 고치기 위한 작업 몇 개를 추가하

기로 했다. 시스템을 문서화하는 작업도 포함시켰다(프로젝트가 계속 커지면서 관련 지식을

쌓기 위해 결국은 위키를 구축했다). 팀원들은 각 작업마다 다른 팀원의 코드를 검토해서 온전

성을 검증하고sanity check 피드백을 제공하며 시스템에 추가될 새로운 코드를 이해했다.

테스트 스위트를 잘 만들어 두면 변경한 코드가 시스템의 기존 기능을 망가뜨리지 않을 것임을

Page 96: 『고성능 파이썬』 - 맛보기

400 고성능 파이썬

확신할 수 있다. 수많은 요소로 이뤄진 소마와 같은 시스템에서는 통합 테스트가 필수다. 스테

이징 환경이 있으면 새로운 코드의 성능을 테스트해볼 수 있다. 특히 소마에서는 테스트를 실

제 프로덕션 수준의 큰 데이터셋으로 해봐야만 문제가 발생하는지 알아내고 해결할 수 있다.

따라서 그런 대량의 데이터를 독립된 환경에서 재생해봐야 할 때가 종종 있다. 아마존 EC2는

이를 위한 유연성을 제공한다.

12.1.4 소마 유지보수소마 시스템은 중단 없이 실행되며, 처리하는 데이터의 양도 매일 늘어난다. 데이터 스트림의

최고점을 감당하고, 네트워크 문제나 소마가 의존하는 다른 서드파티 서비스에서 일어나는 다

양한 문제를 처리해야만 한다. 쉽게 처리하기 위해 소마는 가능하면 스스로 문제를 고칠 수 있

도록 설계됐다. 서커스를 사용한 덕분에 문제가 생겨 중단된 프로세스는 다시 살아나서 자신을

중단시켰던 그 작업을 계속 수행할 수 있다. 각 작업은 프로세스가 자신을 처리할 때까지 대기

열에 머물며 시스템이 복구되는 동안 작업이 누락 없이 쌓일 수 있도록 충분한 대기 공간을 제

공한다.

많은 소마 서버를 모니터링하기 위해 서버 데스티니Server Destiny를 사용한다. 서버 데스티니는 설

정하기 간단하면서 상당히 강력하다. 문제가 발생할 가능성이 있으면 미리 지정한 엔지니어의

휴대폰으로 메시지가 전달되며, 그 엔지니어는 시간 안에 그 가능성이 실제 문제로 악화되지

못하도록 처리할 수 있다. 서버 데스티니를 사용하면 파이썬으로 전용 플러그인을 쉽게 작성할

수 있다. 예를 들면 일래스틱서치의 동작 중 어떤 측면에 대해 즉각적인 경고를 보내도록 만드

는 플러그인 등이 가능하다.

12.1.5 엔지니어를 위한 조언무엇보다도, 여러분이나 여러분의 팀은 실제 환경에 배포될 코드가 흠 없이 작동하리라는 것을

자신하고, 안정감을 가질 수 있어야 한다. 이런 경지에 도달하려면 일을 거꾸로 해야만 한다.

즉, 안정감이 들 때까지 시스템의 모든 구성 요소에 시간을 들여야 한다. 배포를 단순하고 실수

가 벌어지지 못하도록 만들라. 스테이징 환경을 사용해 실제 데이터로 성능을 테스트하라. 커

버리지가 높은 견고하고 훌륭한 테스트 스위트를 꼭 만들라. 새로운 코드를 기존 시스템에 결

Page 97: 『고성능 파이썬』 - 맛보기

421찾아보기

A

AOT 174

API 바인딩 35

asizeof 359

AsyncIO 242

C

cdef 183

Celery 348

cffi 213

cProfile 모듈 52

CPython 모듈 219

ctypes 210

Cython 176, 177, 192

C 컴파일러 176

D

DAWG(방향성 비순환 단어 그래프) 368

dis 모듈 76

dowser 73

F

f2py 216

FFI(외부 함수 인터페이스) 208

for 루프 120

G

Gearman 348

gevent 232

GIL(전역 인터프리터 락) 25

GPU 207

H

HAT 트라이 371

heapy 70

I

IPython Parallel 330, 333

itertools 125

J

JIT 174

Julia Set 40

K

K-최솟값 378

L

linear probing 107

line_profiler 58, 158

M

memit 353

memory_profiler 64, 353

memoryview 193

mmap 297, 299

mprof 67

multiprocessing 251, 255, 302

multiprocessing.Array 306

multiprocessing.Manager 291

multiprocessing.Pool 288

multiprocessing.Queue 277

multiprocessing.RawValue 296

multiprocessing.Value 315

N

NSQ 331, 339

대기열 340

출판자/구독자 341

소비자 341

Nuitka 207

Numba 196

INDEX

Page 98: 『고성능 파이썬』 - 맛보기

422 찾아보기

numexpr 162

numpy 35, 147, 151, 192, 268, 302, 356

numpy 배열(numpy.array) 147, 192

O

OpenMP 194

P

pandas 35

Parallel Python 330, 331

perf 144

PrettyTable 305

print문 46

PyData 206

PyPy 200

PyRes 282, 348

Pyston 207

Pythran 198

PyViennaCL 207

S

scikit-learn 35

scipy 35, 164

Shed Skin 176, 187

STM(소프트웨어 트랜잭션 메모리) 204

sys.getsizeof 357

T

timeit 모듈 48

time 명령어 50

tornado 35, 237

규모 가변성 블룸 필터 384

그린렛 232

기억 장치 25

네임스페이스 115

다트라이 370

단위 테스트 81

no-op @profile 데코레이터 81

데코레이터 47

동기화 310

값에 락 걸기 315

파일 락 걸기 310

로그로그 카운터 388

루프 표기법 119

리스트 87, 93, 94, 137, 142, 364

리스트와 튜플(배열) 87, 93

리스트(동적 배열) 94

차이점 93

튜플(정적 배열) 97

마리사 트라이 369

멀티코어 아키텍처 24

메모리 단편화 142

numpy 배열 147

perf 도구 144

리스트 142

폰 노이만 병목 143

메모리 사용량 진단 64

메모리 할당 154

메모리 활용 방식

array 모듈 354

HAT 트라이 371

numpy 356

다트라이 370

리스트 352, 364

INDEX

Page 99: 『고성능 파이썬』 - 맛보기

423찾아보기

마리사 트라이 369

방향성 비순환 단어 그래프(DAWG) 368

셋 366

이중 배열 트라이 370

트라이 367

모리스 카운터 375

무한급수 123

바이트와 유니코드 360

방향성 비순환 단어 그래프(DAWG) 368

버스 28

벡터 연산 131

블룸 필터 382

규모 가변성 블룸 필터 384

타이밍 블룸 필터 386

비동기 I/O 227

비동기 프로그래밍 226

비순차 실행 24

사전 101, 115

사전과 셋(해시테이블) 101

검색 105

삭제 109

삽입 105

네임스페이스 관리 115

해시 함수 110

해시 함수 엔트로피 112

선택적 최적화 158

선형 프로빙 107

센티넬 278

셋 101, 366

소수 찾기 271

IPC 방식 283

IPC 방식 : Manager.Value 플래그 291

IPC 방식 : mmap 플래그 297, 299

IPC 방식 : multiprocessing.RawValue 플래그

296

IPC 방식 : 레디스 플래그 293

IPC 방식 : 순차적 288

IPC 방식 : 풀 288, 290

순수 파이썬 방식 271

작업 대기열 방식 277

소프트웨어 트랜잭션 메모리(STM) 204

암달의 법칙 24, 252

연산 장치 22

외부 함수 인터페이스(FFI) 208

cffi 213

CPython 모듈 219

ctypes 210

f2py 216

원주율 추정하기 257

numpy 방식 268

몬테카를로 방식 257

순수 파이썬 방식 259

이벤트 루프 226

이중 배열 트라이 370

이중 해싱 382

이터레이터 121, 123, 124

전역 인터프리터 락(GIL) 25

제너레이터 120, 123, 125

제자리 연산 154, 162

쥘리아 집합 40, 176

지연 실행 125

지연 할당 145

최적화 검증 164

Page 100: 『고성능 파이썬』 - 맛보기

424 찾아보기

캐시 미스 145

컴파일러 171, 176

AOT 컴파일러 174

Cython 177

JIT 컴파일러 174

Numba 196

OpenMP 194

PyPy 200

Pythran 198

Shed Skin 191

컴파일러 선택 기준 205

코루틴 229

콜백 228

클러스터링 321

NSQ 339

Parallel Python 331

Python Parallel 333

단점 323

이점 322

일반적인 설계 326

타이밍 블룸 필터 386

탐색 90

통신 계층 28

튜플 87, 93, 97

트라이 367

트리 367

파이썬 개선 제안(PEP) 243

파이썬 배열 192

파이썬 인터프리터 32

포이즌 필 256, 278

폰 노이만 병목 143

퓨처 228

프라미스 228

프로파일링 37

%timeit 매직 명령어(시간) 49

cProfile 모듈(시간) 52

dis(바이트코드) 76

dowser(실시간 변수 생성) 73

heapy(힙 메모리 조사) 70

line_profiler(시간) 58

memory_profile(메모리 사용량) 64

mprof(메모리 사용량) 67

print(시간 측정) 46

timeit 모듈(시간) 48

데코레이터(시간) 47

목표 38

유닉스 time 명령어(시간) 50

전략 84

하이퍼스레딩 24

행렬 131

확률적 자료 구조 374

K-최솟값 378

로그로그 카운터 388

모리스 카운터 375

블룸 필터 382

기 타

__builtin__ 115

__cmp__ 110

__eq__ 90

__hash__ 110

__lt__ 90

@jit 데코레이터 197

%timeit 매직 명령어 49

INDEX

Page 101: 『고성능 파이썬』 - 맛보기
Page 102: 『고성능 파이썬』 - 맛보기

www.hanbi t .co.kr

이것이 프로그래밍이다!

이것이 안드로이드다

진정한 안드로이드 개발자로 이끌어줍니다.

SDK 5.0 롤리팝 호환!

책만 보고,

동영상 강좌로도 만족하지 못했다면 Daum 카페 '슈퍼드로이드'에서 만나요

cafe.daum.net/superdroid

박성근 저 | 1,164쪽 | 45,000원

이것이 C언어다

세상에 없던 새로운C언어 입문서 탄생!

삼성, LG에서 펼쳐졌던 전설의 명강의를 풀타임 동영상 강좌로!

이보다 더 확실한 방법은 없다, 칠판강의

전체 동영상 강좌 유투브 전격 공개!

http://goo.gl/tJK3Tu

서현우 저 | 708쪽 | 25,000원

이것이 자바다

가장 중요한 프로그래밍 언어를 하나배워야 한다면, 결론은 자바다!

중급 개발자로 나아가기 위한 람다식,

JavaFX, NIO 수록

자바의 모든 것을 알려주는 인터넷 강의궁금한 것은 카페에서!

cafe.naver.com/thisisjava

신용권 저 | 1,224쪽 | 30,000원

저자 직강 동영상 제공!

Page 103: 『고성능 파이썬』 - 맛보기

모던 웹을 위한

JavaScript + jQuery 입문

www.hanbi t .co.kr

지금은모던 웹 시대!

HTML5 분야 부동의 1위 도서HTML5 표준안 확정에 맞춘 완전 개정판의 귀환!

HTML5 권고안과 최신 웹 브라우저 환경 대응

윤인성 저 | 624쪽 | 30,000원

모던 웹 디자인을 위한

HTML5 + CSS3 입문

자바스크립트에서 제이쿼리, 제이쿼리 모바일까지 한 권으로 끝낸다!

시대의 흐름에 맞춰 다시 쓴 자바스크립트 교과서

윤인성 저 | 980쪽 | 32,000원

페이스북, 월마트, 링크드인은 왜 Node.js를 선택했는가?

이 물음에 대한 답은 Node.js가 보여주는 빠른 처리 능력 때문이다.

윤인성 저 | 484쪽 | 25,000원

모던 웹을 위한

Node.js프로그래밍

필요한 것만 배워 바로 현장에서 쓰는 HTML5순서대로 읽으며 실습할 수 있는 HTML5 자습서

김상형 저 | 700쪽 | 32,000원

HTML5 + CSS3 정복

Page 104: 『고성능 파이썬』 - 맛보기

Hanbit eBook Realtime

DRM free!어떤 디바이스에서도 자유롭게

eBook Oriented!전자책에 꼭 맞는 최적의 내용과 디자인

Hanbit eBook

Realtime 70

김세훈 지음

MFC프로그래밍주식분석 프로그램 만들기

이연복 지음

Hanbit eBook

Realtime 49Hanbit eBook

Realtime 89

자바 개발자를 위한

Vert.x애플리케이션 개발

Hanbit eBook

Realtime 49Hanbit eBook

Realtime 92

모바일/웹 메시징STOMP와 MQTT로 개발하는 IoT

모바일/웹 애플리케이션

제프 메스닐 지음 / 조건희 옮김

Mobile and Web Messaging

Hanbit eBook

Realtime 90

JavaScriptPromise azu 지음 / 주우영 옮김

www.hanbi t .co.kr /ebook

www.hanbi t .co.kr

Page 105: 『고성능 파이썬』 - 맛보기

전자부품 백과사전 vol.1찰스 플랫 지음 / 배지은 옮김 / 30,000원

취미공학에 필요한 핵심 전자부품을 사전식으로 정리한 안내서.

Zero to Maker: 누구나 메이커가 될 수 있다데이비드 랭 지음 / 장재웅 옮김 / 14,000원

일반인에서 메이커로. 날백수에서 무인 잠수정 회사 CEO가 된 사나이, 데이비드 랭의 메이커 도전기.

처음 시작하는 센서키모 카르비넨,테로 카르비넨 지음임지순 옮김 / 13,000원

세상을 수치로 읽어내는 부품인 센서를 알려주는 책. 이 책을 통해 자신만의 프로젝트에 다양한 센서를 사용해보자.

프로젝트로 배우는 라즈베리 파이 도날드 노리스 지음 / 임지순 옮김

다양한 실전 프로젝트를 통해 라즈베리 파이를 쉽고 재미있게 배워본다.

Maker Pro 존 베이첼 지음 / 가격미정

메이커라면 반드시 읽어야 할 필수 계발서. 프로 메이커들과의 인터뷰 및 에세이 수록.

Make: 센서 키모 카르비넨, 테로 카르비넨, 빌 발토카리 지음 / 가격미정

필수 전자부품인 센서를 마이크로 컨트롤러 보드에 응용하는 방법을 담았다.

전자부품 백과사전 vol.2 찰스 플랫 지음 / 가격미정

<전자부품 백과사전> 시리즈의 두 번째 도서다.

즐거운 상상이 가득! 2015년 화제의 신간

전자부품 백과사전 vol.1찰스 플랫 지음 / 배지은 옮김 / 30,000원

취미공학에 필요한 핵심 전자부품을 사전식으로 정리한 안내서.

Zero to Maker: 누구나 메이커가 될 수 있다데이비드 랭 지음 / 장재웅 옮김 / 14,000원

일반인에서 메이커로. 날백수에서 무인 잠수정 회사 CEO가 된 사나이, 데이비드 랭의 메이커 도전기.

처음 시작하는 센서키모 카르비넨,테로 카르비넨 지음임지순 옮김 / 13,000원

세상을 수치로 읽어내는 부품인 센서를 알려주는 책. 이 책을 통해 자신만의 프로젝트에 다양한 센서를 사용해보자.

프로젝트로 배우는 라즈베리 파이 도날드 노리스 지음 / 임지순 옮김

다양한 실전 프로젝트를 통해 라즈베리 파이를 쉽고 재미있게 배워본다.

Maker Pro 존 베이첼 지음 / 가격미정

메이커라면 반드시 읽어야 할 필수 계발서. 프로 메이커들과의 인터뷰 및 에세이 수록.

Make: 센서 키모 카르비넨, 테로 카르비넨, 빌 발토카리 지음 / 가격미정

필수 전자부품인 센서를 마이크로 컨트롤러 보드에 응용하는 방법을 담았다.

전자부품 백과사전 vol.2 찰스 플랫 지음 / 가격미정

<전자부품 백과사전> 시리즈의 두 번째 도서다.

즐거운 상상이 가득! 2015년 화제의 신간

www.hanbi t .co.kr

Page 106: 『고성능 파이썬』 - 맛보기