FastAPI 및 협력 멀티스레딩 pt. 2

FastAPI and cooperative multi-threading 문제에 대한 충분한 해결책을 찾은 후에도 결과에 만족하지 못하는 부분이 있었습니다. 동시 요청 수가 크게 감소했습니다.

1643620388 309
1643620389 5
1643620390 3
1643620391 6
1643620392 5
1643620393 322


실제로 또는 현실적인 응답 크기에 대해 이렇게 나쁘지는 않습니다. 위의 숫자는 strace에서 전체 프로세스를 실행하여 얻었지만 스레드가 뮤텍스(futex(...) 함수)에서 대기하고 있고 이를 얻으려고 시도하는 동안 시간이 초과되었음을 나타냅니다. 다른 빈번한 활동은 메모리 할당이었습니다. 그래서 가장 시간이 많이 걸리는 응답 모델의 JSON 인코더를 따라가기 시작했습니다.

먼저 루트 Pydantic 모델을 사전으로 인코딩한 다음 json.dumps function을 호출하고 결과 JSON 문자열을 기다립니다. json 라이브러리에는 Python과 C 구현이 혼합되어 있습니다. 가능하면 멋진 들여쓰기 출력을 요청하지 않는 한 the C implementation for the speed을 사용하려고 합니다.

따라서 전체 JSON은 단일 C 함수 호출로 인코딩됩니다. C로 여러 Python 모듈을 작성했기 때문에 C 코드가 악명 높은 Global Interpreter Lock이 걸린 상태로 호출되고 그 동안 Python 코드를 실행할 수 없다는 것을 알고 있습니다. 가능한 경우 해당 잠금을 해제하는 것은 C 확장의 책임입니다. 그리고 협력적 멀티스레딩 주제로 돌아가지만 이번에는 코루틴과 스레드가 아니라 Python 스레드와 네이티브 스레드입니다.

CPython은 모든 스레드 사이를 전환하고 각 스레드가 잠시 동안 실행되도록 함으로써 병렬 처리의 인상을 주는 단일 CPU 컴퓨터와 같습니다. 어느 시점에서든 Python 코드의 단일 스레드만 실행됩니다. 인터프리터는 스레드가 특정 양의 명령을 실행하면 Python 스레드 간에 전환합니다. 그러나 Python이 C 확장을 호출하면 인터프리터는 더 이상 명령을 계산하고 기본 스레드를 중단할 수 없습니다. C 확장은 장기 실행 시스템 호출 전에 GIL을 완료하거나 명시적으로 해제하고 시스템 호출이 반환되면 GIL을 획득해야 합니다. 다른 모든 Python 스레드는 C 확장이 GIL을 보유하는 동안 굶어 죽습니다.

그러나 JSON이 C 확장으로 인코딩된 경우 왜 나는 여전히 0이 아닌 일부 동시 요청을 관찰했습니까? 코드를 다시 한 번 검토한 후 나는 json 라이브러리가 내장 Python 유형을 인코딩하는 방법만 알고 있다는 것을 깨달았습니다. 직렬화하려는 응답은 Pydantic 모델의 트리입니다. "계정이 JSON 직렬화 가능하지 않음"오류와 함께 이것이 어떻게 실패하지 않습니까?

인식할 수 없는 유형json.dumps의 경우 default라는 선택적 매개변수를 사용합니다. See the documentation for more details about it. Pydantic 라이브러리는 converts the current model to a dictionary 이 함수에 대한 구현을 제공합니다. json.dumps는 결과 사전을 인코딩하고 찾은 Pydantic 모델에 대해 pydantic_encoder를 다시 호출할 수 있습니다.

따라서 Python은 json 모듈 C 확장과 Python으로 작성된 Pydantic 인코더 사이를 왔다 갔다 합니다. 그리고 Python 명령이 실행되는 동안 동시 요청을 실행하는 다른 Python 스레드로 전환하기 위한 임계값에 도달합니다. 그리고 이것은 동시성을 향상시키는 방법을 생각하는 데 막혔습니다.

A) 모든 Pydantic 모델을 미리 사전으로 변환할 수 있으므로 C 확장과 Python 코드 사이를 왔다 갔다 하지 않고도 C 코드가 더 빨리 완료됩니다. 그러나 이는 다른 코드가 동시에 실행되지 않고 내가 시작한 곳으로 돌아갈 것임을 의미합니다.

B) 더 나은 멀티스레딩을 위해 json.dumps의 순수 Python 구현을 사용할 수 있습니다. 그러나 그렇게 하면 속도가 느려지고 응답 시간이 길어집니다. JSON 문자열의 모든 부분을 목록에 수집하고 생성기를 사용하는 경우에도 builds a single string 메모리에 더 많은 압력을 가하는 것 같습니다.

나는 이러한 선택이 마음에 들지 않았기 때문에 더 나은 동시성에 대한 탐구를 끝내고 내 코드를 변경하지 않은 곳입니다.

스포일러 경고! 보이 스카우트 원칙에 따라 내가 찾은 것보다 더 나은 세상을 떠나서 CPython에 몇 가지 패치를 수행하고 json.dumps 의 성능을 개선했습니다.

좋은 웹페이지 즐겨찾기