내부적으로 스레드에 의존 할 때 Node.js가 본질적으로 어떻게 더 빠릅니까?


281

방금 다음 비디오를 시청했습니다. Node.js 소개 및 여전히 속도 이점을 얻는 방법을 이해하지 못합니다.

주로 Ryan Dahl (Node.js의 작성자)은 Node.js가 스레드 기반이 아니라 이벤트 루프 기반이라고 말합니다. 쓰레드는 비싸고 동시 프로그래밍 전문가에게만 활용되어야한다.

그런 다음 내부적으로 자체 스레드 풀이있는 기본 C 구현이있는 Node.js의 아키텍처 스택을 보여줍니다. 따라서 분명히 Node.js 개발자는 자신의 스레드를 시작하거나 스레드 풀을 직접 사용하지 않을 것입니다 ... 비동기 콜백을 사용합니다. 이해합니다

내가 이해하지 못하는 것은 Node.js가 여전히 스레드를 사용하고 있다는 점입니다 ... 구현을 숨기고 있으므로 50 명의 사람들이 50 개의 파일 (현재 메모리가 아닌)을 잘 요청하면 50 개의 스레드가 필요하지 않은 경우이 방법이 더 빠릅니다. ?

유일한 차이점은 내부적으로 관리되므로 Node.js 개발자는 스레드 세부 정보를 코딩 할 필요가 없지만 그 아래에서 여전히 스레드를 사용하여 IO (차단) 파일 요청을 처리하고 있다는 것입니다.

따라서 실제로 하나의 문제 (스레딩)를 취하고 그 문제가 여전히 존재하는 동안 숨기는 것이 아닙니다. 주로 여러 스레드, 컨텍스트 전환, 교착 상태 ... 등?

내가 아직 이해하지 못하는 세부 사항이 있어야합니다.


14
나는 그 주장이 다소 단순화되었다는 것에 동의한다. 노드의 성능 이점은 다음 두 가지로 요약됩니다. 1) 실제 스레드는 모두 상당히 낮은 수준으로 포함되므로 크기와 수에 제한이 있으므로 스레드 동기화가 단순화됩니다. 2) OS 레벨 "스위칭" select()은 스레드 컨텍스트 스왑보다 빠릅니다.
Pointy

답변:


140

실제로 여기에는 몇 가지 다른 것들이 있습니다. 그러나 스레드가 정말 어렵다는 밈으로 시작합니다. 만약 그것이 어렵다면, 스레드를 사용할 때 1) 버그로 인해 중단되고 2) 가능한 효율적으로 사용하지 않을 가능성이 높습니다. (2)는 당신이 요구하는 것입니다.

그가 제공 한 예제 중 하나에 대해 생각해보십시오. 요청이 들어오고 쿼리를 실행 한 다음 그 결과로 무언가를 수행하십시오. 표준 절차 방식으로 작성하면 코드는 다음과 같습니다.

result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );

요청으로 인해 위의 코드를 실행하는 새 스레드를 만들면 스레드가 거기에 앉아 query()실행 중일 때 아무것도 수행하지 않습니다 . Ryan에 따르면 Apache는 단일 스레드를 사용하여 원래 요청을 충족시키는 반면 nginx는 그렇지 않은 이유에 대해 nginx가 요구하는 성능을 능가합니다.

자, 만약 당신이 정말로 영리하다면, 당신은 쿼리를 실행하는 동안 환경이 벗어날 수있는 다른 방법으로 위의 코드를 표현할 것입니다 :

query( statement: "select smurfs from some_mushroom", callback: go_do_something_with_result() );

이것이 기본적으로 node.js 가하는 일입니다. 기본적으로 언어와 환경으로 인해 편리한 방식으로 클로저에 대한 요점을 장식하고 있습니다. 따라서 환경이 실행되는 시간과시기에 대해 영리한 방식으로 코드를 작성합니다. 그런 식으로 node.js는 비동기 I / O를 발명했다는 의미에서 새로운 것은 아니지만 (누구도 이와 같은 것을 주장하지는 않음) 표현 방식이 약간 다릅니다.

참고 : 환경이 실행되는 것에 대해 영리 할 수 ​​있다고 말할 때, 특히 의미하는 것은 일부 I / O를 시작하는 데 사용 된 스레드를 사용하여 다른 요청 또는 처리 할 수있는 계산을 처리 할 수 ​​있다는 것입니다 병렬로 또는 다른 병렬 I / O를 시작하십시오. (나는 특정 노드가 동일한 요청에 대해 더 많은 작업을 시작할만큼 정교하지는 않지만 아이디어를 얻습니다.)


6
좋아, 나는 IO가 돌아 오기를 기다리는 스레드 나 실행 스택이 없으므로 Ryan이 한 일을 효과적으로 찾을 수 있기 때문에 CPU를 최대한 활용할 수있는 것처럼 들리므로 이것이 어떻게 성능을 향상시킬 수 있는지 확실히 알 수 있습니다. 모든 격차를 줄이는 방법.
Ralph Caraveo

34
네, 제가 말하고 싶은 한 가지는 그가 틈새를 메울 방법을 찾지 못했다는 것입니다. 그것은 새로운 패턴이 아닙니다. 다른 점은 프로그래머가 Javascript를 사용하여 프로그래머가 이런 종류의 비동기에 훨씬 편리한 방식으로 프로그램을 표현할 수 있다는 점입니다. 아마도
귀찮은

16
또한 많은 I / O 작업에서 Node는 사용 가능한 커널 레벨 비동기 I / O API (epoll, kqueue, / dev / poll 등)를 사용합니다.
Paul

7
나는 아직도 그것을 완전히 이해하고 있는지 확실하지 않습니다. 웹 요청 내부에서 IO 작업이 요청을 처리하는 데 가장 많은 시간이 걸리는 것으로 간주하고 각 IO 작업마다 새 스레드가 생성되면 50 개의 요청이 매우 빠른 연속으로 생성됩니다. 아마도 50 개의 스레드가 병렬로 실행되고 IO 부분을 실행합니다. 표준 웹 서버와의 차이점은 전체 요청이 스레드에서 실행되는 반면 node.js에서는 IO 부분 만 수행하지만 대부분의 시간이 걸리고 스레드가 대기하는 부분입니다.
Florin Dumitrescu

13
@SystemParadox가 지적 해 주셔서 감사합니다. 실제로 최근에 주제에 대한 연구를했으며 실제로 커널 수준에서 올바르게 구현되면 비동기 I / O 작업을 수행하는 동안 스레드를 사용하지 않는 비동기 I / O가 있습니다. 대신 I / O 작업이 시작 되 자마자 호출 스레드가 해제되고 I / O 작업이 완료되고 스레드를 사용할 수있을 때 콜백이 실행됩니다. 따라서 I / O 작업에 대한 비동기 지원이 올바르게 구현 된 경우 node.js는 하나의 스레드 만 사용하여 50 개의 I / O 작업을 가진 50 개의 동시 요청을 병렬로 실행할 수 있습니다.
Florin Dumitrescu

32

노트! 이것은 오래된 대답입니다. 대략적인 개요에서는 여전히 사실이지만 지난 몇 년 동안 노드의 빠른 개발로 인해 일부 세부 사항이 변경되었을 수 있습니다.

다음과 같은 이유로 스레드를 사용하고 있습니다.

  1. open ()O_NONBLOCK 옵션은 파일에서 작동하지 않습니다 .
  2. 비 차단 IO를 제공하지 않는 타사 라이브러리가 있습니다.

비 차단 IO를 위조하려면 스레드가 필요합니다. 별도의 스레드에서 IO를 차단하십시오. 그것은 추악한 솔루션이며 많은 오버 헤드를 유발합니다.

하드웨어 수준에서는 더 나쁩니다.

  • DMA CPU는 비동기 IO 부담을 덜어.
  • 데이터는 IO 장치와 메모리간에 직접 전송됩니다.
  • 커널은이를 동기식 차단 시스템 호출로 래핑합니다.
  • Node.js는 차단 시스템 호출을 스레드로 래핑합니다.

이것은 단지 어리 석고 비효율적입니다. 그러나 적어도 작동합니다! Node.js는 이벤트 중심 비동기 아키텍처 뒤의 추악하고 번거로운 세부 사항을 숨기므로 즐길 수 있습니다.

어쩌면 누군가 파일에 O_NONBLOCK을 구현할 것입니까? ...

편집 : 나는 이것을 친구와 논의했으며 스레드의 대안은 select :를 사용하여 폴링한다고 말합니다 . 타임 아웃을 0으로 지정하고 반환 된 파일 디스크립터에서 IO를 수행하십시오 (지금은 차단되지 않는다고 보장됩니다).


Windows는 어떻습니까?
Pacerier

죄송합니다. 나는 libuv가 비동기 작업을 수행하기위한 플랫폼 중립 계층이라는 것을 알고 있습니다. Node의 시작 부분에는 libuv가 없었습니다. 그런 다음 libuv를 분리하기로 결정했으며 플랫폼 별 코드가 더 쉬워졌습니다. 다시 말해, Windows에는 Linux와는 완전히 다른 비동기 스토리가 있지만 libuv는 우리를 위해 열심히 일하기 때문에 중요하지 않습니다.
nalply

28

내가 여기서 "잘못된 일을하고있다"고 두려워한다면, 저를 삭제하고 사과드립니다. 특히, 나는 일부 사람들이 만든 깔끔한 작은 주석을 어떻게 만드는지 알지 못합니다. 그러나이 스레드에 대해 많은 우려 / 관찰이 있습니다.

1) 인기있는 답변 중 하나에서 의사 코드의 주석이 달린 요소

result = query( "select smurfs from some_mushroom" );
// twiddle fingers
go_do_something_with_result( result );

본질적으로 가짜입니다. 스레드가 컴퓨팅하는 경우 엄지 손가락을 돌리지 않고 필요한 작업을 수행하고 있습니다. 반면에 IO가 완료되기를 기다리는 경우 CPU 시간을 사용 하지 않는 경우 커널의 스레드 제어 인프라의 요점은 CPU가 유용한 작업을 찾는 것입니다. 여기서 제안한대로 "엄지 손가락을 돌리는"유일한 방법은 폴링 루프를 만드는 것입니다. 실제 웹 서버를 코딩 한 사람은 아무도 그렇게 할 수 없습니다.

2) "나사는 어렵다", 데이터 공유의 맥락에서만 의미가있다. 독립적 인 웹 요청을 처리 할 때와 같이 본질적으로 독립적 인 스레드가있는 경우 스레딩은 매우 간단합니다. 한 작업을 처리하는 방법의 선형 흐름을 코딩하고 여러 요청을 처리 할 것입니다. 효과적으로 독립적이 될 것입니다. 개인적으로, 나는 대부분의 프로그래머에게 클로저 / 콜백 메커니즘을 배우는 것이 단순히 위에서 아래로 쓰레드 버전을 코딩하는 것보다 더 복잡하다는 것을 모험 할 것이다. (그렇습니다. 스레드간에 통신해야한다면 인생이 정말 빨리 힘들어 지지만 클로저 / 콜백 메커니즘이 실제로 그것을 변경한다는 것을 확신하지 못합니다.이 접근법은 여전히 ​​스레드로 달성 할 수 있기 때문에 옵션을 제한합니다. 어쨌든

3) 지금까지 어느 특정 유형의 컨텍스트 전환이 다른 유형보다 다소 시간이 걸리는 지에 대한 실제 증거는 아무도 제시하지 못했습니다. 멀티 태스킹 커널을 만든 경험 (내장 컨트롤러의 소규모, "실제"OS만큼 멋진 것은 없음)은 이것이 사실이 아니라고 제안합니다.

4) 다른 웹 서버보다 Node가 얼마나 빠른지 보여주기 위해 현재까지 내가 본 모든 삽화에는 끔찍한 결함이 있지만, Node에 대해 확실히 수용 할 수있는 하나의 이점을 간접적으로 설명하는 방식으로 결함이 있습니다. 결코 중요하지 않습니다). 노드는 튜닝이 필요한 것처럼 보이지 않습니다 (실제로는 허가도 허용하지 않음). 스레드 모델이있는 경우 예상로드를 처리하기에 충분한 스레드를 작성해야합니다. 이 작업을 잘못하면 성능이 저하 될 수 있습니다. 스레드가 너무 적 으면 CPU가 유휴 상태이지만 더 많은 요청을 받아들이고 스레드를 너무 많이 만들면 커널 메모리가 낭비되고 Java 환경의 경우 기본 힙 메모리가 낭비됩니다 . 이제 Java의 경우 힙 낭비는 시스템 성능을 향상시키는 첫 번째 최선의 방법입니다. 효율적인 가비지 콜렉션 (현재 G1에 따라 변경 될 수 있지만 배심원이 적어도 2013 년 초 현재 그 시점에있는 것으로 보입니다)이 많은 여분의 힙을 갖는 데 달려 있기 때문입니다. 따라서 문제가 있습니다. 너무 적은 스레드로 조정하십시오. 유휴 CPU와 처리량이 부족하고 너무 많이 조정하여 다른 방식으로 다운됩니다.

5) 노드의 접근 방식이 "설계 상 더 빠르다"는 주장의 논리를 받아들이는 또 다른 방법이 있습니다. 대부분의 스레드 모델은 시간 분할 컨텍스트 스위치 모델을 사용하며,보다 적절한 (값 판단 경고) 및보다 효율적인 (값 판단이 아닌) 선점 모델 위에 계층화됩니다. 이것은 두 가지 이유로 발생합니다. 첫 번째로 대부분의 프로그래머는 우선 순위 선점을 이해하지 못하는 것 같습니다. 두 번째로 Windows 환경에서 스레딩을 배우면 타임 슬라이싱이 마음에 들지 않든간에 있습니다 (물론 첫 번째 요점을 강화합니다) 자바의 첫 번째 버전은 솔라리스 구현과 윈도우 타임 슬라이싱에 우선 순위를 부여했다. 대부분의 프로그래머들은 "스레딩이 솔라리스에서 작동하지 않는다"는 것을 이해하고 불평하지 않았기 때문에 그들은 모델을 모든 곳에서 타임 슬라이스로 변경했습니다). 어쨌든, 요점은 타임 슬라이싱이 추가적이며 잠재적으로 불필요한 컨텍스트 스위치를 생성한다는 것입니다. 모든 컨텍스트 전환에는 CPU 시간이 걸리며 해당 시간은 실제 작업에서 수행 할 수있는 작업에서 효과적으로 제거됩니다. 그러나 타임 스케일링으로 인해 컨텍스트 전환에 투자 한 시간은 꽤 외설적 인 일이 발생하지 않는 한 전체 시간의 아주 작은 비율을 넘어서는 안되며, 그럴 것으로 예상되는 이유가 없습니다. 간단한 웹 서버). 따라서 시간 분할과 관련된 과도한 컨텍스트 전환은 비효율적입니다. 그 시간은 실제 작업에서 수행 할 수있는 작업에서 효과적으로 제거됩니다. 그러나 타임 스케일링으로 인해 컨텍스트 전환에 투자 한 시간은 꽤 외설적 인 일이 발생하지 않는 한 전체 시간의 아주 작은 비율을 넘어서는 안되며, 그럴 것으로 예상되는 이유가 없습니다. 간단한 웹 서버). 따라서 시간 분할과 관련된 과도한 컨텍스트 전환은 비효율적입니다. 그 시간은 실제 작업에서 수행 할 수있는 작업에서 효과적으로 제거됩니다. 그러나 타임 스케일링으로 인해 컨텍스트 전환에 투자 한 시간은 꽤 외설적 인 일이 발생하지 않는 한 전체 시간의 아주 작은 비율을 넘어서는 안되며, 그럴 것으로 예상되는 이유가 없습니다. 간단한 웹 서버). 따라서 시간 분할과 관련된 과도한 컨텍스트 전환은 비효율적입니다.커널 스레드는 일반적으로 btw)이지만 그 차이는 처리량의 몇 퍼센트가 될 것입니다. 노드에 종종 암시되는 성능 요구에 암시되는 정수 요소의 종류는 아닙니다.

어쨌든, 그 모든 것에 대한 사과는 길고 잔인하지만, 나는 지금까지 토론이 아무것도 입증되지 않았으며, 나는이 상황 중 하나에서 누군가의 의견을 기뻐할 것이라고 느낍니다.

a) 왜 노드가 더 나은지에 대한 실제 설명 (위에서 설명한 두 가지 시나리오를 넘어서, 첫 번째 (가난한 조정)) 내가 지금까지 본 모든 테스트에 대한 실제 설명이라고 생각합니다. ], 실제로 그것에 대해 더 많이 생각할수록 방대한 수의 스택에 사용되는 메모리가 중요한지 더 궁금합니다. 현대 스레드의 기본 스택 크기는 상당히 큰 경향이 있지만 메모리는 클로저 기반 이벤트 시스템은 필요한 것입니다)

b) 선택한 스레드 서버에 실제로 공정한 기회를 제공하는 실제 벤치 마크. 적어도 그런 식으로, 나는 주장이 본질적으로 거짓이라고 믿지 말아야한다. 표시된 벤치 마크는 부당합니다.)

건배, 토비


2
스레드 문제 : RAM이 필요합니다. 사용량이 많은 서버는 최대 수천 개의 스레드를 실행할 수 있습니다. Node.js는 스레드를 피하므로 더 효율적입니다. 효율성은 코드를 더 빠르게 실행하는 것이 아닙니다. 코드가 스레드 또는 이벤트 루프에서 실행되는지는 중요하지 않습니다. CPU도 마찬가지입니다. 그러나 스레드를 없애면 RAM이 절약됩니다. 수천 개의 스택 대신 하나의 스택 만 있습니다. 컨텍스트 스위치도 저장합니다.
nalply 2019

3
그러나 노드는 스레드를 없애지 않습니다. 여전히 대부분의 웹 요청에 필요한 IO 작업에 내부적으로이를 사용합니다.
levi mar

1
또한 노드는 RAM에 콜백의 클로저를 저장하므로 어디에서이기는지 알 수 없습니다.
Oleksandr Papchenko

@levi 그러나 nodejs는“요청 당 하나의 스레드”를 사용하지 않습니다. 비동기 IO API 사용으로 인한 복잡성을 피하기 위해 IO 스레드 풀을 사용합니다 (POSIX open()를 비 블로킹으로 만들 수 없음). 이러한 방식으로 기존의 fork()/ pthread_create()on-request 모델이 스레드를 작성하고 제거해야하는 성능 저하를 막습니다 . 그리고 a)에서 언급했듯이 이것은 또한 스택 공간 문제를 암시합니다. 16 개의 IO 스레드만으로도 수천 건의 요청을 처리 할 수 ​​있습니다.
binki

"현대 스레드의 기본 스택 크기는 상당히 큰 경향이 있지만 클로저 기반 이벤트 시스템에 의해 할당 된 메모리는 필요한 것뿐입니다." 이들은 동일한 순서 여야한다는 인상을받습니다. 클로저는 저렴하지 않으며 런타임은 단일 스레드 응용 프로그램의 전체 콜 트리를 메모리에 보관해야하며 ( "에뮬레이션 스택"이라고 말하면) 트리의 리프가 관련 클로저로 해제 될 때 정리할 수 있습니다. "해결"됩니다. 여기에는 가비지 수집 할 수없고 정리시 성능에 영향을 줄 수있는 힙에 대한 많은 참조가 포함됩니다.
David Tonhofer

14

내가 이해하지 못하는 것은 Node.js가 여전히 스레드를 사용하고 있다는 것입니다.

Ryan은 차단하는 부분에 쓰레드를 사용합니다 (대부분의 node.js는 비 차단 IO를 사용합니다). 그러나 Ryan이 바라는 것은 모든 것을 차단하지 않는 것입니다. 에 슬라이드 63 (내부 설계) 는 라이언의 사용을 참조 libev 비 차단 (비동기 이벤트 알림을 추상화 라이브러리) eventloop을 . 이벤트 루프 node.js로 인해 컨텍스트 전환, 메모리 소비 등을 줄이는 스레드가 적습니다.


11

스레드는와 같은 비동기 기능이없는 함수를 처리하는 데만 사용됩니다 stat().

stat()함수는 항상 차단되므로 node.js는 메인 스레드 (이벤트 루프)를 차단하지 않고 실제 호출을 수행하기 위해 스레드를 사용해야합니다. 잠재적으로 이러한 종류의 함수를 호출 할 필요가없는 경우 스레드 풀의 스레드는 사용되지 않습니다.


7

node.js의 내부 작업에 대해서는 아무것도 모르지만 이벤트 루프를 사용하여 스레드 I / O 처리 성능을 능가하는 방법을 알 수 있습니다. 디스크 요청을 상상하고 staticFile.x를 지정하여 해당 파일에 대해 100 개의 요청을 만드십시오. 각 요청은 일반적으로 해당 파일을 가져 오는 스레드, 즉 100 개의 스레드를 차지합니다.

이제 첫 번째 요청이 게시자 객체가되는 하나의 스레드를 생성한다고 상상해보십시오. 99 개의 다른 요청은 모두 staticFile.x에 대한 게시자 객체가 있는지 먼저 확인합니다. 새로운 게시자 개체.

단일 스레드가 완료되면 staticFile.x를 100 개의 모든 리스너에 전달하고 자체를 삭제하므로 다음 요청은 새로운 스레드 및 게시자 객체를 만듭니다.

따라서 위의 예에서는 100 스레드 대 1 스레드이지만 100 디스크 조회 대신 1 디스크 조회이므로 이득은 상당히 경이 될 수 있습니다. 라이언은 똑똑한 사람입니다!

보는 또 다른 방법은 영화를 시작할 때 그의 예 중 하나입니다. 대신에:

pseudo code:
result = query('select * from ...');

다시 말하지만, 데이터베이스에 대한 100 개의 개별 쿼리와 ... :

pseudo code:
query('select * from ...', function(result){
    // do stuff with result
});

쿼리가 이미 진행 중이라면 다른 동일한 쿼리가 단순히 악대를 뛰어 넘기 때문에 단일 데이터베이스 왕복으로 100 개의 쿼리를 가질 수 있습니다.


3
데이터베이스는 다른 요청 (데이터베이스를 사용하거나 사용하지 않을 수 있음)을 유지하면서 응답을 기다리지 않고 무언가를 요구 한 다음 다시 전화를 걸도록 요청하는 것입니다. 응답을 추적하기가 매우 어려워서 서로 연결되어 있다고 생각하지 않습니다. 또한 하나의 연결에서 여러 개의 버퍼되지 않은 응답을 유지할 수있는 MySQL 인터페이스가 없다고 생각합니다 (??)
Tor Valamo

그것은 nodejs 별도의 모듈없이 DB의와 아무것도하지 않는, 이벤트 루프가 더 효율성을 제공 할 수있는 방법을 설명하기 위해 단지 추상적 인 예입니다)
BGerrissen

1
예, 내 의견은 단일 데이터베이스 왕복에서 100 개의 쿼리에 대한 것이 었습니다. : p
Tor Valamo

2
안녕 BGerrissen : 좋은 게시물. 따라서 쿼리가 실행될 때 다른 유사한 쿼리는 위의 staticFile.X 예제와 같이 "청취"합니다. 예를 들어, 100 명의 사용자가 동일한 쿼리를 검색하면 하나의 쿼리 만 실행되고 나머지 99 개는 첫 번째 쿼리를 수신합니까? 감사 !
차파

1
nodejs가 함수 호출이나 무언가를 자동으로 기억하는 것처럼 들립니다. 이제 JavaScript의 이벤트 루프 모델에서 공유 메모리 동기화에 대해 걱정할 필요가 없으므로 메모리에 안전하게 캐시하는 것이 더 쉽습니다. 그러나 이것이 nodejs가 마술처럼 당신을 위해 그것을하거나 이것이 성능 향상의 유형이라는 것을 의미하는 것은 아닙니다.
binki
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.