[EXT-041][실무] 작업 스레드 수를 측정으로 정하기
페이지 정보

본문
[이번 수업]
작업자 풀은 크게 잡는다고 항상 빨라지지 않습니다. Node.js가 알려 주는 병렬 처리량을 출발점으로 삼고, 같은 계산을 여러 Worker 수로 실행해 시간을 비교합니다.
[선수지식]
EXT-039의 Worker와 EXT-040의 작업자 풀을 이해해야 합니다. 현재 지원 Node.js를 준비하세요.
[학습목표]
1. 실행 환경이 권장하는 병렬 처리량을 확인한다.
2. 같은 총 작업량을 여러 Worker 수로 나누어 측정한다.
3. 반복 측정으로 풀 크기를 고른다.
[핵심개념]
availableParallelism()은 프로그램이 쓸 병렬 처리량의 추정치를 1 이상의 정수로 반환합니다. 공식 문서는 이 목적에 os.cpus().length를 쓰지 말라고 안내합니다. 다만 이 값이 곧 최적 Worker 수는 아닙니다. 스레드 생성, 메시지 전달, 메모리 경쟁에도 비용이 듭니다. performance.now()는 프로세스 시작을 기준으로 한 고해상도 밀리초 시각입니다. 같은 입력과 환경에서 여러 번 측정하고 처리량뿐 아니라 응답 지연과 오류도 함께 보세요.
[따라하기]
아래 내용을 size-check.mjs로 저장하세요. 총 반복 횟수는 같고 Worker 수만 바뀝니다.
```js
import { availableParallelism } from 'node:os';
import { performance } from 'node:perf_hooks';
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
if (!isMainThread) {
let checksum = 0;
for (let i = 0; i < workerData; i++)
checksum = (checksum + i) % 1_000_003;
parentPort.postMessage(checksum);
} else {
const available = availableParallelism();
const candidates = [1, Math.min(2, available), Math.min(4, available)]
.filter((value, index, list) => list.indexOf(value) === index);
async function runOne(iterations) {
const worker = new Worker(new URL(import.meta.url), { workerData: iterations });
try {
return await new Promise((resolve, reject) => {
worker.once('message', resolve);
worker.once('error', reject);
});
} finally {
await worker.terminate();
}
}
console.log(`available=${available}`);
for (const size of candidates) {
const each = Math.ceil(20_000_000 / size);
const started = performance.now();
await Promise.all(Array.from({ length: size }, () => runOne(each)));
const elapsed = Math.round(performance.now() - started);
console.log(`workers=${size} elapsedMs=${elapsed}`);
}
}
```
Windows·macOS·Linux 모두 `node size-check.mjs`를 실행합니다. 값은 기기마다 다르지만 available은 1 이상이고 각 후보의 시간이 표시됩니다.
```text
available=8
workers=1 elapsedMs=220
workers=2 elapsedMs=135
workers=4 elapsedMs=110
```
[흔한 실수]
한 번 가장 빠른 값을 정답으로 확정하거나 서로 다른 총 작업량을 비교하지 마세요. 절전 상태와 다른 프로그램의 부하도 결과를 흔듭니다. Worker 시작 비용 포함 여부도 기록하세요.
[보안 주의]
입력 크기와 Worker 상한을 고정해 자원 고갈을 막습니다. 운영 서버에서 갑자기 부하 시험을 하지 말고 본인 소유의 로컬·격리 환경에서 시작하세요. 로그에는 원문이나 비밀값 대신 작업 ID, 시간, 성공 여부만 남깁니다.
[직접 해볼 과제]
스크립트를 다섯 번 실행해 후보별 중앙값을 기록하세요. 반복 횟수를 절반으로 줄여 시작 비용의 영향을 비교하고, 실제 CPU 작업으로 바꿔 처리량과 지연 시간을 함께 표로 만드세요.
[확인문제]
1. os.cpus().length보다 availableParallelism()이 알맞은 이유는 무엇일까요?
2. Worker 수를 늘려도 실행 시간이 줄지 않을 수 있는 이유는 무엇일까요?
3. 풀 크기를 공정하게 비교하려면 어떤 조건을 같게 해야 할까요?
[다음 학습]
다음에는 작업의 시작·종료·실패를 AsyncResource로 연결해 비동기 진단 정보를 올바르게 남깁니다.
[공식 참고 자료]
Node.js os.availableParallelism()
https://nodejs.org/api/os.html#osavailableparallelism
Node.js performance.now()
https://nodejs.org/api/perf_hooks.html#performancenow
Node.js Worker threads
https://nodejs.org/api/worker_threads.html
작업자 풀은 크게 잡는다고 항상 빨라지지 않습니다. Node.js가 알려 주는 병렬 처리량을 출발점으로 삼고, 같은 계산을 여러 Worker 수로 실행해 시간을 비교합니다.
[선수지식]
EXT-039의 Worker와 EXT-040의 작업자 풀을 이해해야 합니다. 현재 지원 Node.js를 준비하세요.
[학습목표]
1. 실행 환경이 권장하는 병렬 처리량을 확인한다.
2. 같은 총 작업량을 여러 Worker 수로 나누어 측정한다.
3. 반복 측정으로 풀 크기를 고른다.
[핵심개념]
availableParallelism()은 프로그램이 쓸 병렬 처리량의 추정치를 1 이상의 정수로 반환합니다. 공식 문서는 이 목적에 os.cpus().length를 쓰지 말라고 안내합니다. 다만 이 값이 곧 최적 Worker 수는 아닙니다. 스레드 생성, 메시지 전달, 메모리 경쟁에도 비용이 듭니다. performance.now()는 프로세스 시작을 기준으로 한 고해상도 밀리초 시각입니다. 같은 입력과 환경에서 여러 번 측정하고 처리량뿐 아니라 응답 지연과 오류도 함께 보세요.
[따라하기]
아래 내용을 size-check.mjs로 저장하세요. 총 반복 횟수는 같고 Worker 수만 바뀝니다.
```js
import { availableParallelism } from 'node:os';
import { performance } from 'node:perf_hooks';
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
if (!isMainThread) {
let checksum = 0;
for (let i = 0; i < workerData; i++)
checksum = (checksum + i) % 1_000_003;
parentPort.postMessage(checksum);
} else {
const available = availableParallelism();
const candidates = [1, Math.min(2, available), Math.min(4, available)]
.filter((value, index, list) => list.indexOf(value) === index);
async function runOne(iterations) {
const worker = new Worker(new URL(import.meta.url), { workerData: iterations });
try {
return await new Promise((resolve, reject) => {
worker.once('message', resolve);
worker.once('error', reject);
});
} finally {
await worker.terminate();
}
}
console.log(`available=${available}`);
for (const size of candidates) {
const each = Math.ceil(20_000_000 / size);
const started = performance.now();
await Promise.all(Array.from({ length: size }, () => runOne(each)));
const elapsed = Math.round(performance.now() - started);
console.log(`workers=${size} elapsedMs=${elapsed}`);
}
}
```
Windows·macOS·Linux 모두 `node size-check.mjs`를 실행합니다. 값은 기기마다 다르지만 available은 1 이상이고 각 후보의 시간이 표시됩니다.
```text
available=8
workers=1 elapsedMs=220
workers=2 elapsedMs=135
workers=4 elapsedMs=110
```
[흔한 실수]
한 번 가장 빠른 값을 정답으로 확정하거나 서로 다른 총 작업량을 비교하지 마세요. 절전 상태와 다른 프로그램의 부하도 결과를 흔듭니다. Worker 시작 비용 포함 여부도 기록하세요.
[보안 주의]
입력 크기와 Worker 상한을 고정해 자원 고갈을 막습니다. 운영 서버에서 갑자기 부하 시험을 하지 말고 본인 소유의 로컬·격리 환경에서 시작하세요. 로그에는 원문이나 비밀값 대신 작업 ID, 시간, 성공 여부만 남깁니다.
[직접 해볼 과제]
스크립트를 다섯 번 실행해 후보별 중앙값을 기록하세요. 반복 횟수를 절반으로 줄여 시작 비용의 영향을 비교하고, 실제 CPU 작업으로 바꿔 처리량과 지연 시간을 함께 표로 만드세요.
[확인문제]
1. os.cpus().length보다 availableParallelism()이 알맞은 이유는 무엇일까요?
2. Worker 수를 늘려도 실행 시간이 줄지 않을 수 있는 이유는 무엇일까요?
3. 풀 크기를 공정하게 비교하려면 어떤 조건을 같게 해야 할까요?
[다음 학습]
다음에는 작업의 시작·종료·실패를 AsyncResource로 연결해 비동기 진단 정보를 올바르게 남깁니다.
[공식 참고 자료]
Node.js os.availableParallelism()
https://nodejs.org/api/os.html#osavailableparallelism
Node.js performance.now()
https://nodejs.org/api/perf_hooks.html#performancenow
Node.js Worker threads
https://nodejs.org/api/worker_threads.html
- 이전글[EXT-042][실무] 작업 결과를 원래 요청과 연결하기 26.09.09
- 다음글[EXT-040][실무] 작업 스레드 수와 대기열 제한하기 26.09.09
댓글목록
등록된 댓글이 없습니다.
