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

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

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

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

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

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

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

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



답변

실제로 여기에는 몇 가지 다른 것들이 있습니다. 그러나 스레드가 정말 어렵다는 밈으로 시작합니다. 만약 그것이 어렵다면, 스레드를 사용할 때 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를 시작하십시오. (나는 특정 노드가 동일한 요청에 대해 더 많은 작업을 시작할만큼 정교하지는 않지만 아이디어를 얻습니다.)


답변

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

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

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

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

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

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

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

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

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


답변

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

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

건배, 토비


답변

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

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


답변

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

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


답변

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 개의 쿼리를 가질 수 있습니다.


답변