비동기 프로그래밍: 더 빠르지만 얼마나 빠릅니까?

11880 단어

TL;DR: Python asyncio is about 3.5 times faster than threading. Golang is very performant and has better scalability. It is curious to see why.


최근 몇 년 동안 우리는 주류 프로그래밍 언어에서 비동기 프로그래밍의 흥기를 보았다.promises,async/await,coroutines 같은 구조는 현재Javascript,Python에서 찾을 수 있고, 심지어goodoldC++에서도 찾을 수 있다.모두가 알다시피 이것은 또 다른 추세의 부작용이다. 현재 점점 더 많은 응용 프로그램이 네트워크 IO에 의존하고 있기 때문에 IO는 시간이 필요하다.귀중한 CPU 주기를 낭비하고 서버의 회신을 기다리기보다는 UI 렌더링과 같은 다른 기본 작업을 계속하고 데이터를 사용할 수 있을 때 실행 중인 작업을 복구하는 것이 좋습니다.
최근에 나는 비동기 프로그래밍을 응용하기에 적합한 프로젝트를 해 왔다.이 프로젝트의 일부 요구 사항은 HTTP 단점 목록을 조회하고 응답을 얻으며 조합해서 결과를 만드는 것입니다.나는 이 용례가 비동기 프로그래밍의 유용성을 보여주는 좋은 플랫폼이라고 생각한다.또한 서로 다른 프로그래밍 언어가 이종 원어에 대한 선택과 실현을 테스트하여 현실 장면에서의 성능을 이해하는 것은 흥미로운 일이다.내가 가장 관심을 가지는 주요 지표는 1) 이러한 비동기 프로그래밍 원어를 사용하는 공효학, 그리고 2) 이런 언어에서 비동기 프로그래밍을 사용하면 얻는 성능 이득이다.

활용단어참조


노정


장기 사용자로서 내가 생각한 첫 번째 언어는 파이톤이다.전통적으로 파이톤은 병발성에 대한 지원에 한계가 있다.이것은 threading 라이브러리를 가지고 있지만 파이톤에서는 전역 해석기 자물쇠 (GIL) 로 주어진 시간마다 하나의 루트만 실행되어야 하기 때문에 루트 1 의 용도를 제한합니다.파이썬은 표준 라이브러리에 multiprocessing 모듈이 하나 더 있습니다.그러나 프로그래머가 프로세스 간 통신 (IPC) 을 처리하도록 하는 것이 단점이다. 기본 선택이 너무 제한되어 있다.
파이썬이 루트와 (하위) 프로세스에 대한 지원이 좋더라도, 나는 파이썬을 사용하는 데 열중하지 않는다. 왜냐하면 파이썬의 등급이 너무 낮아서 도움을 줄 수 없기 때문이다.너는 의사소통과 동기화를 처리해야 한다.루틴/프로세스는 시스템급 자원이기 때문에 항상 적당한 정리를 해야 하기 때문에 응용 프로그램 코드에 불필요한 복잡성을 증가시킨다.그 밖에 다른 병행 모델(예를 들어 협동 루트)에 비해 라인과 프로세스는 항상 더 비싸다. 메모리를 더 많이 소모하고 시작 속도가 느리다.

Asyncio!


다행히도 파이톤 3.6부터 이 언어2에 새로운 asyncio 표준 라이브러리를 추가했습니다.
비동기 프로그래밍의 장점을 예를 들어 보여 드리겠습니다. Pokemon API를 통해 Pokemon의 이름 목록을 얻고 싶습니다.다음은 내가 전통적인 파이톤에서 실현한 방식이다.
BASE_URL = "https://pokeapi.co/api/v2/pokemon/"
result = []
for i in range(1, number+1):
    r = httpx.get(BASE_URL + str(i))
    result.append(r)
        jsons = []
for r in result:
    try:
        jsons.append(r.json())
    except json.decoder.JSONDecodeError:
        print(r.text)
print(f"Got {len(jsons)} pokemons")
위의 코드는 이해하기 쉽다.여기서 유일하게 주의해야 할 것은 http.get가 동기화되었다는 것이다.HTTP 클라이언트가 응답하지 않으면 Python 해석기가 더 이상 실행하지 못하게 합니다.시간, 너의 귀중한 시간이 낭비되었다!
다음 코드는 같은 효과를 냈지만 asyncio를 사용했습니다.
async def get_pokemons(number: int):
    tasks = []

    async with httpx.AsyncClient() as client:
        for i in range(1, number+1):
            r = client.get(BASE_URL + str(i))
            tasks.append(r)
        result = await asyncio.gather(*tasks)

    return result

result = asyncio.run(get_pokemons(number))
jsons = []
for r in result:
    try:
        jsons.append(r.json())
    except json.decoder.JSONDecodeError:
        print(r.text)
물론 코드가 좀 복잡해졌다.그러나 우리 프로그래머들은 우리가 합병성을 위해 약간의 대가를 치러야 한다는 것을 안다. 그렇지?threading version3에 비해 asyncio 코드는 가독성이 뛰어나 응용 프로그램 개발자들이 무관심한 세부 사항을 숨긴다.
그럼 asyncio의 활약은 어땠나요?

의심할 여지없이sync and wait 버전의 속도는 매우 느리지만, 이 그림은 스레드 탱크 버전이 비동기 버전보다 훨씬 느리다는 것을 알려주지 않았습니다.확대해 보겠습니다.

또한 asyncio에 대해서만 드로잉을 수행합니다.

일반적으로 파이톤에서 다중 스레드를 사용하더라도 우리가 필요로 하는 모든 응답을 얻는 것은 사용asyncio보다 3.5배 느리다.이것은 직관적이지 않을 수도 있다. 왜냐하면 asyncio 한 라인만 사용하기 때문이다.그러나 앞에서 언급한 바와 같이 GIL은 이곳에서 작용하기 때문에 병발성이 없다. 비록 우리는 여러 개의 라인이 있지만.이에 비해 asyncio에서 병발은 제공된 운영체제IO multiplexing를 통해 이루어진 것이다. 단일 라인은 epoll 시스템에서 여러 개의 IO 이벤트를 호출하고 이벤트 순환으로 작업을 배정할 수 있다.
난 마음에 안 들어.asyncio가 더 빠르지만 블루라인을 보면 모든 응답을 얻을 수 있는 총 시간은 우리가 보낸 요청 수와 선형 관계를 가진다.나의 이론은 asyncio를 사용할 때 GIL이 우리를 구속하지 않기 때문에 우리는 서버에 대한 많은 TCP 연결을 동시에 구축했다.서버 자체가 병발적이라고 가정하면, 우리는 거의 같은 시간에 모든 응답을 받아야 한다.따라서 파란색 선의 경사율이 그렇게 뚜렷하지 않아야 한다.

단거리 선수가 결승선을 앞다투어 통과하는 것처럼 우리의 HTTP 응답은 거의 같은 시간에 도착해야 한다.
하지만 사실은 그렇지 않다.나는 몇 가지 이론이 있지만, 현재 이 문제를 완전히 해석할 수 있는 것은 하나도 없다.
그런데 다른 언어는요?그들은 asyncio의 집행에 대해 같은 문제에 부딪혔습니까?

그랜니


Python에서 저희가 사용한 똑같은 생각의 Golang 실현을 보면...

주의, Golang에서 우리는 협동 절차의 개념이 없다.고로틴은 고랑에서만 찾을 수 있는 특수한 원시 생물이다.이 코드는 함수 getPokemon 가 라인에서 실행되는 것처럼 보일 수도 있지만, 두 가지를 기억해야 한다.
  • goroutines는green threads;운영 체제 레벨의 스레드가 아니므로 시작 비용이 저렴합니다
  • .
  • Golang의 HTTP나 모든 네트워크 IO 호출은 비동기적이지만 Golang은 Goroutines4에서 쉽게 사용할 수 있도록 동기화되어 있습니다.
  • 이제 성능을 살펴보겠습니다.

    뚜렷한 이상값을 무시하면, 우리는 이 선이 거의 평평하다는 것을 볼 수 있다.이것이 바로 내가 바라는 바이다. Golang의 실현은 비동기 IO를 사용할 때 Python보다 신축성이 더 좋다는 것을 보여준다.
    또한 주의하십시오: Golang의 y축 단위는 밀리초입니다. 초가 아니라, x축의 최대치는 거의 1600입니다. Python의 두 배입니다. Python에 비해 Golang은 훨씬 빠릅니다.

    깊이 생각할 만하다


    우리는 GolangPython가 모두 내부에서 epoll를 사용하여 IO 멀티플렉스를 하는 것을 알고 있음을 감안하여 libevent의 기준에서 epoll의 확장성은 거의 O(1)라는 것을 알았다.Python의 선형 확장성은 이상한 것 같고asyncio는 최적화해야 할 부분이 있음을 나타낸다.

    Special thanks for Pokemon API. I have spaced out my experiments, so it should be well in the range of their fair use policy's tolerance.


    일부 C 확장자는 GIL을 우회할 수 있지만, 우리의 토론과는 무관하다. 
    파이썬 커뮤니티는 이전에 제3자 라이브러리를 가진 언어에 비동기 프로그래밍을 융합시키려고 여러 차례 시도했다. 예를 들어 twistedtornado.하지만 너무 늦게 왔어요.파이톤에서 다른 단계에 대한 나의 유일한 접촉은 업데이트를 통해 교체되는 것이다. 예를 들어 asyncio 또는 trio
    여기서, 우리는 concurrent.futures를 사용하여 생성과 연결 라인을 자동으로 처리합니다.따라서 이 코드는 순수threading보다 더 잘 읽혔지만 asyncio버전에 뒤떨어졌다. 
    자세한 내용은 여기를 참조하십시오.https://morsmachine.dk/netpoller

    좋은 웹페이지 즐겨찾기