Syscall(시스템 호출)은 커널과 사용자 공간 간의 격리로 인해 일부 성능 저하를 초래합니다. 따라서 시스템 호출을 줄이는 것이 좋은 생각인 것 같습니다.
그래서 내 생각은 시스템 호출을 하나로 묶을 수 있다는 것입니다. 그래서 아이디어는 시스템 호출과 매개변수를 메모리의 간단한 데이터 구조에 배치하는 것입니다. 그런 다음 이 데이터 구조를 제공하는 새로운 시스템 호출을 도입할 수 있습니다. 그런 다음 커널은 모든 기능을 병렬로 트리거하고 하나(또는 모든) 시스템 호출이 완료되면 스레드를 재개할 수 있습니다.
나는 이 접근 방식이 동시 프로그래밍(비동기 I/O)을 위한 좋은 기반이 될 것이며 모든 시스템 호출에서 동시성을 허용하고 전체 컨텍스트 전환을 줄임으로써 기존 선택/폴링/에폴링 솔루션을 개선할 것이라고 생각합니다.
왜 이것이 이루어지지 않았습니까?
답변1
이는 이미 존재합니다. Linux에서는 구현이 다음과 같습니다.io_uring, 커널 버전 5.1(2019년 5월)부터 사용 가능: 작업은 대기열(또는 더 정확하게는 링)에 배치되고 시스템 호출 없이 처리되며 해당 결과는 다른 대기열로 전송됩니다.
답변2
일반적인 개념이 완료되었으며 존재합니다. Stephen Kitt의 답변에서 알 수 있듯이 가장 가까운 예는 Linux의 io_uring이지만 그러한 인터페이스의 유일한 예는 아닙니다. Windows, Solaris, AmigaOS 및 소수의 기타 운영 체제에는 모두 io_uring과 유사한 방식으로 작동하는 유사한 IO 지향 완료 대기열 메커니즘이 있습니다(Linux는 실제로 약간 늦습니다).
또한 실제로 UNIX 계열 시스템에는 제안한 대로 작동하지 않지만 피해야 하는 시스템 호출이 많이 있습니다.많은잠재적인 컨텍스트 전환은 일반적으로 사용자 공간에서 수행되는 특정 작업을 커널로 푸시하여 구현됩니다. 시스템 sendfile()
호출은 아마도 이러한 유형의 시스템 호출의 가장 좋은 예일 것입니다. 이는 매우 일반적인 작업(한 파일 설명자에서 다른 파일 설명자로 대량의 데이터 복사)을 수행하고 이를 커널 모드로 푸시하여 완전히 루프 및 많은 컨텍스트 전환을 방지합니다. 이를 수행하려면 사용자 공간(및 추가 버퍼)이 필요합니다.
그러나 여기서 이해해야 할 중요한 점은 이것이 의미가 있으려면 관련 작업 집합과 관련된 모든 것을 이와 같이 대량으로 설정하는 비용이 "일반적인" 방식으로 수행하는 비용보다 낮아야 한다는 것입니다. . io_uring을 사용하는 것은 처리 중인 경우에만 의미가 있습니다.위치IO(예: VM용 블록 저장 장치를 시뮬레이션할 때)(QEMU는 이를 위해 이를 사용하도록 지원하며 빠른 호스트 하드웨어에서도 성능 차이는 작습니다)미친) 또는 초당 수천 개의 파일을 읽습니다(제가 일하는 회사에서는 최근 이러한 작업 부하에 io_uring을 사용할 가능성에 대해 내부적으로 논의하기 시작했습니다). 마찬가지로 sendfile()
사용자 공간을 통해 데이터를 복사하기 위해 여러 번의 읽기/쓰기 반복이 필요한 경우에만 의미가 있습니다(일반적으로 읽기/쓰기 반복을 실행하는 대신 사용자 공간에서 버퍼 공간을 감당할 수 없는 기능임에도 불구하고). 빠른) 반복).
게다가 시스템 호출은 실제로 배치 컨텍스트에서 의미가 있어야 합니다. 처리가 호출 순서를 유지하는 경우 IO는 일반적으로 여기서 의미가 있지만 많은 것들이 의미가 없습니다. 예를 들어, 이러한 유형의 인터페이스 exec()
(fork와 exec가 결합된) 를 사용해 보십시오.아마도, 그러나 일반 실행자는 아님). 마찬가지로 특정 유형의 시스템 호출은 개별적으로 처리될 때만 유용합니다. 운영 프로세스의 신호 마스킹은 좋은 예입니다. 초기 설정 외에도 거의 항상 코드의 중요한 부분을 보호하기 위해 이 작업을 수행하며 일반적으로 이러한 목적을 위해 시기적절하고 예측 가능한 처리가 필요합니다.
답변3
이러한 기능은 꽤 오랫동안 사용되어 왔습니다.
1997년 솔라리스 2.6이를 수행하기 위해 커널 비동기 IO 시스템 호출을 추가했습니다.kaio()
.
액세스하는 한 가지 방법은 다음을 통해입니다.`lio_listio() 함수:
lio_listio
- 목록 지향 I/O
요약
cc [ flag... ] file... -lrt [ library... ] #include <aio.h> int lio_listio(int mode, struct aiocb *restrict const list[], int nent, struct sigevent *restrict sig);
설명하다
이
lio_listio()
함수를 사용하면 호출 프로세스, LWP 또는 스레드가 단일 함수 호출에서 I/O 요청 목록을 시작할 수 있습니다.
Illumos libc
소스 코드는 오픈 소스이며 원래 Solaris 구현에서 파생되었으며 다음 위치 lio_listio()
에서 찾을 수 있습니다.https://github.com/illumos/illumos-gate/blob/470204d3561e07978b63600336e8d47cc75387fa/usr/src/lib/libc/port/aio/posix_aio.c#L121
이러한 기능이 일반적이지 않은 이유 중 하나는 전체 소프트웨어 및 하드웨어 시스템이 이를 활용하도록 설계되지 않는 한 실제로 성능을 크게 향상시키지 않는다는 것입니다.
스토리지는 올바르게 정렬된 블록을 제공하도록 구성되어야 하며, 파일 시스템은 스토리지 시스템에서 제공한 블록과 올바르게 정렬되도록 구축되어야 하며, 전체 소프트웨어 스택은아니요IO 문제 - 모두 IO가 올바르게 정렬되어야 합니다.
회전식 디스크를 사용하면 동일한 디스크에 대한 일괄 IO 작업이 서로 쉽게 간섭할 수 있으며 헤드가 검색하는 데 더 많은 시간을 소비하기 때문에 실제로 모든 작업 속도가 느려질 수 있습니다.
내 경험에 따르면 레이어 중 하나만 잘못된 작업을 수행하면 일괄 처리 시스템 호출의 성능 이점이 오버헤드로 인해 손실됩니다. 최악의 시스템 호출 오버헤드에 비해 IO가 느리기 때문이다.
대량 IO 시스템 호출을 통해 제공되는 성능 향상을 활용하기 위해 결합된 하드웨어/소프트웨어 시스템을 만들고 유지 관리하는 데 드는 비용은 상당합니다.
내가 본 최고의 수치는 많은 IO 호출을 단일 시스템 호출로 일괄 처리하면 성능이 약 25-30% 향상될 수 있다는 것입니다.
이는 24시간 내내 수백 기가바이트의 데이터를 처리하는 경우 중요합니다.
고양이 비디오 시청 지연 시간을 8ms에서 6ms로 줄이기 위해 이와 같은 전체 시스템을 구축하고 유지 관리합니까? 그다지 많지는 않습니다.