F-Lab
🚀
깊이 있는 개발자 커뮤니티, 데브클럽에 함께 하세요

Gevent로 Python 어플리케이션 성능 개선하기

writer_thumbnail

F-Lab : 상위 1% 개발자들의 멘토링

📌 글 작성

F-Lab 백엔드 멘토 Jacob
팀 문화와 엔지니어 생산성에 관심이 많은 실리콘밸리 백엔드 개발자
 

 


 

Python 은 다양한 영역에서 사용되는 언어이지만 다른 언어에 비해 느리다는 인식이 퍼져 있습니다. 그 이유로는 아래와 같은 특징들을 들 수 있습니다.

 

  • 인터프리터 언어이기 때문에 런타임에 bytecode를 생성합니다. 컴파일러 언어에 비해서 런타임에 동일한 행위를 하기 위해서 처리해야 하는 일이 더 많습니다.
  • 가비지 콜렉터의 존재로 인해 CPU 사이클을 소모하게 됩니다. 또 가비지 콜렉터는 언제 실행될지 알기 어렵습니다.
  • 동적 타이핑으로 인해서 Python 인터프리터가 namespace lookup을 하는데 더 많은 노력이 들어갑니다.

 

위와 같은 이유 때문에 Python으로 작성한 프로그램은 C 나 C++로 짜여진 동일한 행위를 하는 프로그램에 비해서 10배, 심지어는 100배가량도 느릴 수 있습니다. 하지만 그것이 Python 이 충분히 빠르지 않다는 의미는 아닙니다.

 

Python 이 느리고 Python 프로그램을 프로덕션 환경에서 사용하기 위해서는 많은 최적화를 필요로 할 것이라고 생각하셨다면 이 블로그 글을 일독하시는 걸 권해드립니다. 이 글은 파이썬의 성능상의 제약에도 불구하고 그것이 서비스의 주요 병목이 될 확률은 몹시 적다고 말하고 있습니다. 아래는 제가 좋아하는 Coding Horror 블로그의 글 The Infinite Space Between Words에서 발췌한 표입니다.

 

 

1 CPU 사이클에 필요한 시간을 1초라고 가정했을 때 메인 메모리에 접근하는데 필요한 시간은 6분 남짓, 디스크에 접근하는데 필요한 시간은 일에서 월 단위로 늘어납니다. 네트워크를 통해 자원에 접근하는 것은 심지어 더 오래 걸립니다. 앞서 가정한 CPU 사이클당 1초가 걸린다는 가정을 기반으로 계산하면 샌프란시스코에서 뉴욕에 있는 데이터센터에 접근하는 데 필요한 시간은 4년이나 걸립니다. 한 번의 네트워크 왕복은 2억 5천만 건의 CPU cycle에 맞먹습니다.

 

그러면 우리가 작성하는 Python 프로그램을 빠르게 할 수 있는 가장 쉬운 방법은 무엇일까요? 디스크/네트워크에 읽고 쓰는 행위에 드는 시간을 줄이면 프로그램이 명백하게 빨라질 것 같습니다. 여기에서 MDN에서 발췌한 비동기 프로그래밍에 대한 설명을 확인하도록 하겠습니다.

비동기 프로그래밍은 작업이 완료될 때까지 기다리지 않고 잠재적으로 오래 실행되는 작업을 시작하여 해당 작업이 실행되는 동안에도 다른 이벤트에 응답할 수 있게 하는 기술입니다. 작업이 완료되면 프로그램이 결과를 제공합니다.

 

디스크/네트워크에 읽고 쓰는 동안 Python 인터프리터가 기다리지 않고 다른 동작을 할 수 있다면 디스크/네트워크에 읽고 쓰는 행위에 드는 시간을 줄일 수 있습니다. 파이썬은 비동기 프로그래밍을 위한 여러가지 라이브러리를 제공합니다. 그중 오늘 소개해 드릴 라이브러리는 Gevent 입니다.

Gevent는 코루틴 기반의 Python 네트워킹 라이브러리입니다. Gevent는 libev 또는 libuv 이벤트 루프 위에서 동기적인 API를 제공하기 위해서 Greenlet을 사용합니다.

 

위의 설명에서 나온 핵심 단어들을 전부 이해하지 못해도 괜찮습니다. 핵심은 Gevent가 어떤 일을 하는지 이해하는 것입니다. Gevent는 우리가 일상적으로 사용하는 동기적인 API를 제공하고 내부 구현체가 요청들을 비동기적으로 처리합니다. 다시 말해서 Gevent 라이브러리에서 제공되는 socket() 과 같은 함수들을 이용하면 Python 인터프리터가 네트워크 요청을 보내고 기다릴 필요 없이 다음 명령을 실행할 수 있게 됩니다.

 

import gevent
import socket

def arbitrary_socket_call():
    _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    _socket.connect(('www.google.com', 80))
    _socket.send(b"GET / HTTP/1.1\r\nHost:www.google.com\r\n\r\n")
    response = _socket.recv(4096)
    print(response)

def run_many_socket_calls():
    for _ in 1000:
        arbitrary_socket_call()


# gevent.socket 을 사용함으로써 동일한 결과를 내는 코드가 더 빠르게 실행된다 
# gevent.socket 은 blocking I/O가 발생하면 그것을 기다리는 동안 다른 작업을 처리할 수 있게 해준다.
from gevent import socket

def arbitrary_socket_call():
    _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    _socket.connect(('www.google.com', 80))
    _socket.send(b"GET / HTTP/1.1\r\nHost:www.google.com\r\n\r\n")
    response = _socket.recv(4096)
    print(response)

def run_many_socket_calls():
    jobs = [gevent.spawn(arbitrary_socket_call) _ for 1000]
    gevent.joinall(jobs)

 

 

그렇다면 Gevent를 쓰지 않는 서드 파티 라이브러리들의 경우에는 성능 향상을 누리기 어려울까요? Gevent는 monkey patch라는 기능을 통해서 Python 표준 라이브러리들을 Gevent 라이브러리가 제공하는 구현체로 대체 할 수 있습니다. 이 기능을 이용하면 여러분이 사용하는 서드 파티 라이브러리의 코드도 성능 향상이 가능합니다.

 

import gevent
from gevent import monkey

monkey.patch_all() # 이 함수 호출 이후로 import 되는 모듈에서 gevent가 지원하는 I/O 관련 함수는 모두 gevent 모듈의 비동기 함수를 사용하게 된다

import requests

TARGET_URL_LIST = [
'www.google.com',
'www.youtube.com',
'www.facebook.com',
'www.twitter.com',
'www.instagram.com',
'www.wikipedia.com',
'www.yahoo.com',
'www.live.com',
'www.reddit.com',
'www.netflix.com',
'www.linkedin.com',
'www.office.com',
'www.bing.com',
'www.quora.com',
'www.ebay.com',
]

def send_request(url: str):
    content = requests.get(url).content
    print(content)

def run_concurrent():
    jobs = [gevent.spawn(send_request, url) for url in TARGET_URL_LIST]
    gevent.joinall(jobs)

 

 

물론 Gevent도 만능이 아닙니다. 우선 위에서 언급한 monkey patch는 순수하게 Python으로 작성된 코드에 한해서 지원됩니다. 만약 서드 파티 모듈이 다른 언어로 작성되었다면 성능 향상을 누리기 위해서는 Gevent를 지원하는지 확인하는 것이 필요합니다. 

 

또 Gevent 라이브러리를 사용하는 경우 thread-safe 한 코드 작성이 필요합니다. 하지만 그럼에도 불구하고 Gevent를 현업에서 도입하는 것은 만족스러운 경험이었습니다. 

 

저의 경우에는 Python으로 작성된 레거시 백엔드 어플리케이션에서 스케일링 문제가 발생해서 그것을 해결하는 과정에서 Gevent를 도입한 경험이 있는데 어플리케이션 서버의 성능을 10배까지 끌어올릴 수 있었고 클라우드 비용을 유의미하게 절감하는 성과를 낼 수 있었습니다. 

 

동시에 Gevent가 제공하는 monkey patch를 이용해서 코드 변경을 최소화하고 함께 일하는 엔지니어들이 새로운 기술 도입 과정에서 들이는 노력도 많이 아낄 수 있었습니다. 여러분도 Gevent , 또는 다른 비동기 프로그래밍 라이브러리를 이용해서 Python 어플리케이션 성능을 개선해 보시기 바랍니다.

ⓒ F-Lab & Company

이 컨텐츠는 F-Lab의 고유 자산으로 상업적인 목적의 복사 및 배포를 금합니다.

조회수

멘토링 코스 선택하기

  • 코스 이미지
    Java Backend

    아키텍처 설계와 대용량 트래픽 처리 능력을 깊이 있게 기르는 백앤드 개발자 성장 과정

  • 코스 이미지
    Node.js Backend

    아키텍처 설계와 대용량 트래픽 처리 능력을 깊이 있게 기르는 백앤드 개발자 성장 과정

  • 코스 이미지
    Python Backend

    대규모 서비스를 지탱할 수 있는 대체 불가능한 백엔드, 데이터 엔지니어, ML엔지니어의 길을 탐구하는 성장 과정

  • 코스 이미지
    Frontend

    기술과 브라우저를 Deep-Dive 하며 성능과 아키텍처, UX에 능한 개발자로 성장하는 과정

  • 코스 이미지
    iOS

    언어와 프레임워크, 모바일 환경에 대한 탄탄한 이해도를 갖추는 iOS 개발자 성장 과정

  • 코스 이미지
    Android

    아키텍처 설계 능력과 성능 튜닝 능력을 향상시키는 안드로이드 Deep-Dive 과정

  • 코스 이미지
    Flutter

    네이티브와 의존성 관리까지 깊이 있는 크로스 플랫폼 개발자로 성장하는 과정

  • 코스 이미지
    React Native

    네이티브와 의존성 관리까지 깊이 있는 크로스 플랫폼 개발자로 성장하는 과정

  • 코스 이미지
    Devops

    대규모 서비스를 지탱할 수 있는 데브옵스 엔지니어로 성장하는 과정

  • 코스 이미지
    ML Engineering

    머신러닝과 엔지니어링 자체에 대한 탄탄한 이해도를 갖추는 머신러닝 엔지니어 성장 과정

  • 코스 이미지
    Data Engineering

    확장성 있는 데이터 처리 및 수급이 가능하도록 시스템을 설계 하고 운영할 수 있는 능력을 갖추는 데이터 엔지니어 성장 과정

  • 코스 이미지
    Game Server

    대규모 라이브 게임을 운영할 수 있는 처리 능력과 아키텍처 설계 능력을 갖추는 게임 서버 개발자 성장 과정

  • 코스 이미지
    Game Client

    대규모 라이브 게임 그래픽 처리 성능과 게임 자체 성능을 높힐 수 있는 능력을 갖추는 게임 클라이언트 개발자 성장 과정

F-Lab
소개채용멘토 지원
facebook
linkedIn
youtube
instagram
logo
(주)에프랩앤컴퍼니 | 사업자등록번호 : 534-85-01979 | 대표자명 : 박중수 | 전화번호 : 1600-8776 | 제휴 문의 : info@f-lab.kr | 주소 : 서울특별시 강남구 테헤란로63길 12, 438호 | copyright © F-Lab & Company 2024