비 블로킹 I / O가 멀티 스레드 차단 I / O보다 실제로 빠릅니까? 어떻게?
I / O 차단 및 비 차단 I / O에 대한 기술적 인 세부 사항을 웹에서 검색했으며 비 차단 I / O가 I / O 차단보다 빠르다는 몇 사람이 발견했습니다. 예를 들어이 문서에서 .
차단 I / O를 사용하는 경우 물론 현재 차단 된 스레드는 다른 작업을 수행 할 수 없습니다. 차단되어 있기 때문입니다. 그러나 스레드가 차단되기 시작하면 OS는 다른 스레드로 전환하고 차단 된 스레드에 대해 수행 할 작업이있을 때까지 다시 전환 할 수 없습니다. 따라서 시스템에 CPU가 필요하고 차단되지 않은 다른 스레드가있는 한 이벤트 기반 비 차단 방식과 비교하여 더 이상 CPU 유휴 시간이 없어야합니까?
CPU가 유휴 시간을 줄이는 것 외에도 주어진 시간 프레임에서 컴퓨터가 수행 할 수있는 작업 수를 늘리는 옵션이 하나 더 있습니다. 스레드 전환으로 인한 오버 헤드를 줄입니다. 그러나 어떻게 이것을 할 수 있습니까? 그리고 오버 헤드가 측정 가능한 효과를 보여줄만큼 충분히 큰가? 작동 방식을 설명하는 방법에 대한 아이디어는 다음과 같습니다.
- 파일의 내용을로드하기 위해 응용 프로그램은이 작업을 이벤트 기반 i / o 프레임 워크에 위임하고 파일 이름과 함께 콜백 함수를 전달합니다.
- 이벤트 프레임 워크는 운영 체제에 위임합니다. 운영 체제는 하드 디스크의 DMA 컨트롤러를 프로그램하여 파일을 메모리에 직접 작성합니다.
- 이벤트 프레임 워크를 사용하면 추가 코드를 실행할 수 있습니다.
- 디스크 간 복사가 완료되면 DMA 컨트롤러가 인터럽트를 일으 킵니다.
- 운영 체제의 인터럽트 핸들러는 파일이 메모리에 완전히로드되고 있음을 이벤트 기반 I / O 프레임 워크에 알립니다. 어떻게합니까? 신호 사용 ??
- 현재 이벤트 I / O 프레임 워크 내에서 실행되는 코드가 완료됩니다.
- 이벤트 기반 i / o 프레임 워크는 큐를 확인하고 5 단계에서 운영 체제 메시지를보고 1 단계에서 얻은 콜백을 실행합니다.
그게 어떻게 작동합니까? 그렇지 않은 경우 어떻게 작동합니까? 이는 이벤트 시스템이 스택을 명시 적으로 만질 필요없이 작동 할 수 있음을 의미합니다 (예 : 스택을 백업하고 스레드를 전환하는 동안 다른 스레드의 스택을 메모리에 복사해야하는 실제 스케줄러)? 이것이 실제로 얼마나 많은 시간을 절약합니까? 그것에 더 있습니까?
비 블로킹 또는 비동기 I / O의 가장 큰 장점은 스레드가 병렬 작업을 계속할 수 있다는 것입니다. 물론 추가 스레드를 사용하여이를 달성 할 수도 있습니다. 전체적인 (시스템) 성능을 높이기 위해 언급 한 것처럼 여러 스레드가 아닌 비동기 I / O를 사용하는 것이 좋습니다 (스레드 전환 감소).
병렬로 연결된 1000 개의 클라이언트를 처리 할 수있는 네트워크 서버 프로그램의 가능한 구현을 살펴 보겠습니다.
- 연결 당 하나의 스레드 (I / O를 차단할 수 있지만 비 차단 I / O 일 수도 있음).
각 스레드에는 메모리 리소스 (커널 메모리도 필요)가 필요합니다. 이는 단점입니다. 그리고 모든 추가 스레드는 스케줄러에 더 많은 작업을 의미합니다. - 모든 연결을위한 하나의 스레드.
스레드가 적기 때문에 시스템에서로드가 걸립니다. 그러나 한 프로세서를 100 %로 구동하고 다른 모든 프로세서를 유휴 상태로 만들 수 있기 때문에 컴퓨터의 전체 성능을 사용하지 못하게됩니다. - 각 스레드가 일부 연결을 처리하는 몇 개의 스레드
스레드가 적기 때문에 시스템에서로드가 걸립니다. 그리고 사용 가능한 모든 프로세서를 사용할 수 있습니다. Windows에서는이 방법이 Thread Pool API에서 지원됩니다 .
물론 더 많은 스레드를 갖는 것은 그 자체로는 문제가되지 않습니다. 아시다시피, 상당히 많은 수의 연결 / 스레드를 선택했습니다. 십여 개의 스레드에 대해서만 이야기한다면 가능한 세 가지 구현 사이에 어떤 차이가 있는지 의심 스럽습니다 (이것은 Raymond Chen이 MSDN 블로그 게시물 에서 제안한 것입니다 .Windows는 프로세스 당 스레드 수가 2000 개입니까? ).
버퍼되지 않은 파일 I / O를 사용하는 Windows 에서 쓰기는 페이지 크기의 배수 인 크기 여야합니다. 테스트하지는 않았지만 버퍼링 된 동기 및 비동기 쓰기의 쓰기 성능에 긍정적 인 영향을 줄 수있는 것처럼 들립니다.
설명하는 1 ~ 7 단계는 작동 방식에 대한 좋은 아이디어를 제공합니다. Windows의 운영 체제 (비동기 I / O의 완료에 대해 알려 것 WriteFile
와 OVERLAPPED
이벤트 또는 콜백을 사용하여 구조). 콜백 함수는 예를 들어 코드가 WaitForMultipleObjectsEx
로 bAlertable
설정된 경우에만 호출 됩니다 true
.
웹에서 더 읽을 거리 :
- MSDN 의 사용자 인터페이스 에 여러 스레드가 있으며 스레드 생성 비용도 곧 처리
- 스레드 및 스레드 풀 섹션에 따르면 "스레드는 비교적 작성 및 사용이 쉽지만 운영 체제는 상당한 시간과 기타 리소스를 할당하여이를 관리합니다."라고 말합니다.
- MSDN의 CreateThread 문서 는 "하지만 프로세서 당 하나의 스레드를 만들고 응용 프로그램이 컨텍스트 정보를 유지 관리하는 요청 큐를 구축하면 응용 프로그램의 성능이 향상됩니다."
- 이전 기사 너무 많은 스레드가 성능을 저하시키는 이유 및 해결 방법
I / O에는 하드 드라이브에서 데이터 읽기 및 쓰기, 네트워크 리소스 액세스, 웹 서비스 호출 또는 데이터베이스에서 데이터 검색과 같은 여러 종류의 작업이 포함됩니다. 플랫폼 및 작업 종류에 따라 비동기 I / O는 일반적으로 작업 수행을 위해 하드웨어 또는 저수준 시스템 지원을 활용합니다. 이는 CPU에 가능한 한 적은 영향을 미치면서 수행됨을 의미합니다.
애플리케이션 레벨에서 비동기 I / O는 스레드가 I / O 조작이 완료 될 때까지 대기하지 않도록합니다. 비동기 I / O 작업이 시작 되 자마자 시작된 스레드를 해제하고 콜백이 등록됩니다. 작업이 완료되면 콜백은 사용 가능한 첫 번째 스레드에서 실행 대기합니다.
I / O 작업이 동기식으로 실행되면 작업이 완료 될 때까지 실행중인 스레드에서 아무 것도 수행하지 않습니다. 런타임은 I / O 작업이 완료되는시기를 알지 못하므로 대기중인 스레드에 CPU 시간을 주기적으로 제공합니다. CPU 시간은 실제 CPU 바운드 작업을 수행하는 다른 스레드에서 사용할 수있는 CPU 시간입니다.
따라서 @ user1629468에서 언급했듯이 비동기 I / O는 더 나은 성능을 제공하지 않고 확장 성을 향상시킵니다. 이것은 웹 응용 프로그램의 경우처럼 사용 가능한 스레드 수가 제한적인 컨텍스트에서 실행될 때 분명합니다. 웹 응용 프로그램은 일반적으로 각 요청에 스레드를 할당하는 스레드 풀을 사용합니다. 장시간 실행되는 I / O 작업에서 요청이 차단되면 웹 풀이 고갈되고 웹 응용 프로그램이 멈추거나 응답이 느려지는 위험이 있습니다.
내가 주목 한 것은 비동기 I / O가 매우 빠른 I / O 작업을 처리 할 때 최상의 옵션이 아니라는 것입니다. 이 경우 I / O 작업이 완료되기를 기다리는 동안 스레드를 사용하지 않는 이점은 중요하지 않으며 작업이 한 스레드에서 시작되고 다른 스레드에서 완료된다는 사실은 전체 실행에 오버 헤드를 추가합니다.
당신은 내가 최근에 멀티 스레딩 비동기 I의 주제 / O 대에 만든 더 자세한 연구를 읽을 수 있습니다 여기를 .
AIO를 사용하는 주된 이유는 확장 성 때문입니다. 몇 가지 스레드의 맥락에서 볼 때 이점은 분명하지 않습니다. 그러나 시스템이 1000 개의 스레드로 확장되면 AIO는 훨씬 더 나은 성능을 제공합니다. 경고는 AIO 라이브러리가 더 이상 병목 현상을 일으키지 않아야한다는 것입니다.
To presume a speed improvement due to any form of multi-computing you must presume either that multiple CPU-based tasks are being executed concurrently upon multiple computing resources (generally processor cores) or else that not all of the tasks rely upon the concurrent usage of the same resource -- that is, some tasks may depend on one system subcomponent (disk storage, say) while some tasks depend on another (receiving communication from a peripheral device) and still others may require usage of processor cores.
The first scenario is often referred to as "parallel" programming. The second scenario is often referred to as "concurrent" or "asynchronous" programming, although "concurrent" is sometimes also used to refer to the case of merely allowing an operating system to interleave execution of multiple tasks, regardless of whether such execution must take place serially or if multiple resources can be used to achieve parallel execution. In this latter case, "concurrent" generally refers to the way that execution is written in the program, rather than from the perspective of the actual simultaneity of task execution.
It's very easy to speak about all of this with tacit assumptions. For example, some are quick to make a claim such as "Asynchronous I/O will be faster than multi-threaded I/O." This claim is dubious for several reasons. First, it could be the case that some given asynchronous I/O framework is implemented precisely with multi-threading, in which case they are one in the same and it doesn't make sense to say one concept "is faster than" the other.
Second, even in the case when there is a single-threaded implementation of an asynchronous framework (such as a single-threaded event loop) you must still make an assumption about what that loop is doing. For example, one silly thing you can do with a single-threaded event loop is request for it to asynchronously complete two different purely CPU-bound tasks. If you did this on a machine with only an idealized single processor core (ignoring modern hardware optimizations) then performing this task "asynchronously" wouldn't really perform any differently than performing it with two independently managed threads, or with just one lone process -- the difference might come down to thread context switching or operating system schedule optimizations, but if both tasks are going to the CPU it would be similar in either case.
It is useful to imagine a lot of the unusual or stupid corner cases you might run into.
"Asynchronous" does not have to be concurrent, for example just as above: you "asynchronously" execute two CPU-bound tasks on a machine with exactly one processor core.
Multi-threaded execution doesn't have to be concurrent: you spawn two threads on a machine with a single processor core, or ask two threads to acquire any other kind of scarce resource (imagine, say, a network database that can only establish one connection at a time). The threads' execution might be interleaved however the operating system scheduler sees fit, but their total runtime cannot be reduced (and will be increased from the thread context switching) on a single core (or more generally, if you spawn more threads than there are cores to run them, or have more threads asking for a resource than what the resource can sustain). This same thing goes for multi-processing as well.
So neither asynchronous I/O nor multi-threading have to offer any performance gain in terms of run time. They can even slow things down.
If you define a specific use case, however, like a specific program that both makes a network call to retrieve data from a network-connected resource like a remote database and also does some local CPU-bound computation, then you can start to reason about the performance differences between the two methods given a particular assumption about hardware.
The questions to ask: How many computational steps do I need to perform and how many independent systems of resources are there to perform them? Are there subsets of the computational steps that require usage of independent system subcomponents and can benefit from doing so concurrently? How many processor cores do I have and what is the overhead for using multiple processors or threads to complete tasks on separate cores?
If your tasks largely rely on independent subsystems, then an asynchronous solution might be good. If the number of threads needed to handle it would be large, such that context switching became non-trivial for the operating system, then a single-threaded asynchronous solution might be better.
Whenever the tasks are bound by the same resource (e.g. multiple needs to concurrently access the same network or local resource), then multi-threading will probably introduce unsatisfactory overhead, and while single-threaded asynchrony may introduce less overhead, in such a resource-limited situation it too cannot produce a speed-up. In such a case, the only option (if you want a speed-up) is to make multiple copies of that resource available (e.g. multiple processor cores if the scarce resource is CPU; a better database that supports more concurrent connections if the scarce resource is a connection-limited database, etc.).
Another way to put it is: allowing the operating system to interleave the usage of a single resource for two tasks cannot be faster than merely letting one task use the resource while the other waits, then letting the second task finish serially. Further, the scheduler cost of interleaving means in any real situation it actually creates a slowdown. It doesn't matter if the interleaved usage occurs of the CPU, a network resource, a memory resource, a peripheral device, or any other system resource.
One possible implementation of non-blocking I/O is exactly what you said, with a pool of background threads that do blocking I/O and notify the thread of the originator of the I/O via some callback mechanism. In fact, this is how the AIO module in glibc works. Here are some vague details about the implementation.
While this is a good solution that is quite portable (as long as you have threads), the OS is typically able to service non-blocking I/O more efficiently. This Wikipedia article lists possible implementations besides the thread pool.
I am currently in the process of implementing async io on an embedded platform using protothreads. Non blocking io makes the difference between running at 16000fps and 160fps. The biggest benefit of non blocking io is that you can structure your code to do other things while hardware does its thing. Even initialization of devices can be done in parallel.
Martin
In Node, multiple threads are being launched, but it's a layer down in the C++ run-time.
"So Yes NodeJS is single threaded, but this is a half truth, actually it is event-driven and single-threaded with background workers. The main event loop is single-threaded but most of the I/O works run on separate threads, because the I/O APIs in Node.js are asynchronous/non-blocking by design, in order to accommodate the event loop. "
"Node.js is non-blocking which means that all functions ( callbacks ) are delegated to the event loop and they are ( or can be ) executed by different threads. That is handled by Node.js run-time."
https://itnext.io/multi-threading-and-multi-process-in-node-js-ffa5bb5cde98
The "Node is faster because it's non-blocking..." explanation is a bit of marketing and this is a great question. It's efficient and scaleable, but not exactly single threaded.
The improvement as far as I know is that Asynchronous I/O uses ( I'm talking about MS System, just to clarify ) the so called I/O completion ports. By using the Asynchronous call the framework leverage such architecture automatically, and this is supposed to be much more efficient that standard threading mechanism. As a personal experience I can say that you would sensibly feel your application more reactive if you prefer AsyncCalls instead of blocking threads.
Let me give you a counterexample that asynchronous I/O does not work. I am writing a proxy similar to below-using boost::asio. https://github.com/ArashPartow/proxy/blob/master/tcpproxy_server.cpp
However, the scenario of my case is, incoming (from clients side) messages are fast while outgoing (to server side) is slow for one session, to keep up with the incoming speed or to maximize the total proxy throughput, we have to use multiple sessions under one connection.
Thus this async I/O framework does not work anymore. We do need a thread pool to send to the server by assigning each thread a session.
'IT박스' 카테고리의 다른 글
쉘에서 파일 크기 (바이트)를 얻는 휴대용 방법? (0) | 2020.08.03 |
---|---|
콘솔 대신 Visual Studio에서 콘솔 응용 프로그램의 출력 (0) | 2020.08.02 |
SASS.js가 있습니까? (0) | 2020.08.02 |
별도의 개발자 및 제품 Firebase 환경 (0) | 2020.08.02 |
“알고리즘 디자인 매뉴얼”에 대한 솔루션은 어디에서 찾을 수 있습니까? (0) | 2020.08.02 |