기본 콘텐츠로 건너뛰기

스레드, 프로세스, 코어는 언제 많을수록 느려질까?

스레드, 프로세스, 코어는 언제 많을수록 느려질까?

빠른 답

  • 스레드 수를 늘리면 동시에 처리할 수 있는 작업은 늘 수 있지만, 컨텍스트 스위칭, 락 경합, 캐시 미스 비용도 함께 커질 수 있습니다.
  • 프로세스는 메모리 격리와 장애 분리에 유리하지만, 스레드보다 생성 비용과 메모리 사용량, 프로세스 간 통신 비용이 큽니다.
  • 코어가 많아도 프로그램이 일을 병렬로 나누지 못하거나 다른 자원에서 막히면 성능 향상은 제한됩니다.
  • 적정 개수는 CPU 사용률, run queue, 메모리, I/O 대기, p95·p99 응답 시간을 함께 보며 측정해야 합니다.

한눈에 비교

스레드, 프로세스, 코어는 모두 동시 처리와 관련 있지만 같은 층위의 개념은 아닙니다. 코어는 하드웨어 실행 자원이고, 프로세스는 운영체제가 격리해 관리하는 실행 단위이며, 스레드는 프로세스 안에서 작업을 나누는 실행 흐름입니다.

실행 자원
코어는 실제 명령어를 실행하는 CPU 자원이고, 스레드와 프로세스는 그 자원을 배정받는 실행 단위입니다.
격리 수준
프로세스는 독립된 메모리 공간을 갖고, 같은 프로세스 안의 스레드는 메모리를 공유합니다.
생성 비용
프로세스는 주소 공간과 자원을 새로 준비해야 하므로 대체로 무겁고, 스레드는 상대적으로 가볍지만 많아지면 스택 메모리와 스케줄링 비용이 누적됩니다.
통신 방식
스레드는 공유 메모리로 빠르게 데이터를 주고받을 수 있지만 동기화가 필요하고, 프로세스는 IPC를 통해 더 격리된 방식으로 통신합니다.
느려지는 지점
실행 가능한 작업이 CPU 코어보다 많아지거나, 락·메모리·DB·디스크·네트워크 같은 공유 자원에서 대기가 길어질 때 성능이 꺾입니다.
선택 기준
CPU 바운드 작업은 코어 수 근처에서 시작해 조정하고, I/O 바운드 작업은 외부 자원의 동시성 한계를 함께 보며 조정하는 편이 좋습니다.

시간 흐름으로 이해하기

요청 하나만 보면 스레드를 늘리는 방식이 단순한 해결책처럼 보입니다. 하지만 요청이 연속으로 들어오고, 대기열이 길어지고, 실행 가능한 작업이 CPU 코어보다 많아지는 순간부터 성능은 다른 양상을 보입니다.

첫 작업 도착
요청, 메시지, 파일 처리 작업이 들어오고 워커나 스레드가 하나를 가져갑니다.
동시 작업 증가
새 작업이 계속 들어오면 큐가 쌓이고 실행 가능한 스레드 수가 늘어납니다.
대기 발생
일부 작업은 I/O, 락, DB 커넥션, 타이머 때문에 멈춰 있고 다른 작업이 CPU를 사용합니다.
CPU 배정
실제로 동시에 실행되는 작업 수는 코어 수와 스케줄러 정책의 영향을 받습니다.
후속 지연
컨텍스트 스위칭과 자원 경합이 커지면 처리량보다 p95·p99 지연 시간이 먼저 나빠질 수 있습니다.

왜 많을수록 빨라 보일까

작업을 여러 개로 나누면 한 번에 더 많이 처리할 수 있다는 설명은 일부 상황에서 맞습니다. 서로 독립적인 계산 작업이 많고, 공유 자원 접근이 적고, 작업을 나누고 합치는 비용이 작다면 병렬화 효과가 큽니다. 이미지 변환, 대량 압축, 독립적인 배치 계산, 샤딩된 데이터 처리 같은 작업이 여기에 가깝습니다.

하지만 서버 요청 처리는 CPU만 쓰지 않습니다. 네트워크 응답을 기다리고, 데이터베이스 쿼리를 보내고, 로그를 쓰고, 큐에서 메시지를 가져오고, 같은 객체나 파일을 두고 경쟁하기도 합니다. 스레드 수를 늘리면 대기 중인 작업을 더 많이 품을 수 있지만, 동시에 더 많은 작업이 같은 자원을 향해 몰립니다.

동시성과 병렬성도 구분해서 봐야 합니다. 동시성은 여러 작업이 진행 중인 상태를 만드는 능력이고, 병렬성은 같은 시점에 여러 작업이 실제로 실행되는 능력입니다. 스레드가 100개 있어도 논리 CPU가 8개라면 CPU에서 동시에 실행되는 작업은 제한됩니다. 나머지는 실행 가능한 상태로 기다리거나 I/O, 락, 타이머 때문에 잠시 멈춰 있습니다.

스레드가 많을 때 생기는 비용

스레드는 같은 프로세스의 메모리를 공유하므로 데이터 전달이 빠르고 가볍게 보입니다. 웹 서버의 요청 처리 스레드, JVM의 스레드 풀, Node.js의 워커 스레드, Python의 워커 풀처럼 여러 런타임에서 흔히 쓰입니다. 다만 스레드는 공짜 자원이 아닙니다.

스레드가 많아지면 운영체제 스케줄러는 어떤 스레드를 실행할지 더 자주 결정해야 합니다. 실행 중이던 스레드의 레지스터와 상태를 저장하고 다른 스레드의 상태를 복원하는 컨텍스트 스위칭도 늘어납니다. 개별 전환 비용은 작아 보여도 초당 수천, 수만 번 반복되면 CPU 일부가 실제 업무가 아니라 전환 관리에 쓰입니다.

공유 메모리를 쓰는 구조에서는 락 경합도 중요합니다. 스레드가 같은 큐, 캐시, 맵, 파일, DB 커넥션 풀을 동시에 건드리면 어느 순간부터 병렬성이 아니라 대기 시간이 늘어납니다. 스레드를 늘렸는데 처리량은 조금 오르고 p95·p99 응답 시간이 크게 나빠진다면, CPU 부족보다 큐 대기나 락 경합을 먼저 확인해야 할 때가 많습니다.

프로세스가 많을 때 생기는 비용

프로세스는 주소 공간이 분리되어 있어 한 프로세스의 메모리 오류나 크래시가 다른 프로세스에 바로 번지지 않는다는 장점이 있습니다. 웹 서버의 워커 프로세스, 브라우저의 탭 분리, 배치 워커 분산처럼 안정성과 격리가 중요한 구조에서 프로세스 분리는 유용합니다.

대신 프로세스는 더 많은 메모리를 씁니다. 런타임, 라이브러리, 힙, 캐시, 파일 디스크립터 같은 자원이 프로세스별로 필요합니다. 같은 애플리케이션을 여러 프로세스로 띄우면 캐시가 중복되고, 프로세스마다 GC나 런타임 관리 비용도 발생할 수 있습니다.

프로세스 간 통신도 비용이 있습니다. 같은 프로세스의 스레드끼리는 공유 메모리를 통해 바로 데이터를 볼 수 있지만, 프로세스 사이에서는 파이프, 소켓, 메시지 큐, 공유 메모리 같은 IPC가 필요합니다. 안정성은 좋아질 수 있지만, 데이터를 직렬화하고 복사하고 동기화하는 비용을 함께 고려해야 합니다.

코어가 많아도 빨라지지 않는 경우

코어 수가 늘면 동시에 실행할 수 있는 작업의 상한이 올라갑니다. CPU 바운드 작업을 잘게 나눌 수 있다면 코어 증설이 처리량 개선으로 이어질 수 있습니다. 하지만 모든 프로그램이 코어를 자동으로 활용하지는 않습니다.

단일 스레드로 대부분의 일을 처리하는 프로그램은 코어가 많아도 한 코어만 바쁘게 쓸 수 있습니다. 전역 락 하나가 긴 시간 잡히는 구조도 비슷합니다. 여러 스레드가 있어도 중요한 구간이 하나씩만 실행된다면 추가 코어는 대기 시간이 줄어드는 데 크게 기여하지 못합니다.

하이퍼스레딩처럼 물리 코어 하나가 여러 논리 CPU로 보이는 경우도 해석에 주의가 필요합니다. 논리 CPU 수가 16개라고 해서 CPU 바운드 작업이 항상 물리 코어 16개처럼 선형으로 빨라지지는 않습니다. 논리 CPU는 실행 자원을 더 잘 활용하게 도와주지만, 같은 물리 코어의 실행 유닛과 캐시를 공유한다는 점은 남아 있습니다.

CPU 바운드와 I/O 바운드의 선택 기준

CPU 바운드 작업은 CPU가 계산하느라 바쁜 작업입니다. 암호화, 영상 인코딩, 대량 JSON 파싱, 이미지 처리, 복잡한 알고리즘 계산 등이 여기에 들어갑니다. 이런 작업에서는 실행 가능한 스레드나 프로세스가 코어 수보다 지나치게 많아지면 CPU를 나눠 쓰는 비용이 커집니다. 처음에는 논리 CPU 수 근처에서 시작해 처리량과 지연 시간을 보며 조정하는 방식이 이해하기 쉽습니다.

I/O 바운드 작업은 CPU보다 외부 응답을 기다리는 시간이 긴 작업입니다. HTTP API 호출, DB 쿼리, 파일 읽기, 메시지 큐 대기 같은 작업이 대표적입니다. 어떤 스레드가 I/O를 기다리는 동안 다른 스레드가 CPU를 사용할 수 있으므로 코어 수보다 많은 스레드가 도움이 될 수 있습니다. 다만 DB 커넥션 풀, 외부 API rate limit, 디스크 대역폭, 네트워크 지연 같은 다음 병목을 함께 봐야 합니다.

판단 흐름은 다음처럼 잡을 수 있습니다.

  • CPU 사용률이 높고 run queue가 길다면 스레드나 프로세스를 더 늘려도 느려질 가능성이 큽니다.
  • CPU 사용률은 낮은데 응답이 느리다면 I/O 대기, 락 대기, 커넥션 풀, 외부 서비스 지연을 확인해야 합니다.
  • 메모리 사용량이 계속 늘어난다면 프로세스 수, 스레드 스택, 큐에 쌓인 작업, 캐시 중복을 같이 봐야 합니다.
  • 평균 응답 시간은 괜찮아도 p95·p99가 나빠진다면 큐 대기와 경합이 이미 커졌을 수 있습니다.

워커 수와 스레드 풀 크기 잡기

처음부터 큰 값을 넣기보다 작은 기준값에서 부하를 걸어보고 병목이 어디로 이동하는지 보는 편이 안전합니다. 아래 설정은 웹 서버나 배치 워커에서 흔히 볼 수 있는 구조를 단순화한 예입니다. 숫자 자체보다 workers, threads.max, queueSize, connectionPool.maxSize가 서로 어떤 영향을 주는지 보는 것이 중요합니다.

server:
  workers: 4
  threads:
    min: 8
    max: 32
  queueSize: 1000

database:
  connectionPool:
    maxSize: 20
    connectionTimeoutMs: 3000

workers는 프로세스 또는 독립 워커 단위의 병렬 처리 개수를 뜻할 수 있습니다. threads.max는 한 워커 안에서 동시에 처리할 작업 수를 제한합니다. database.connectionPool.maxSize가 20인데 요청 처리 스레드를 200개로 늘리면 많은 스레드는 결국 DB 커넥션 앞에서 기다립니다. 겉으로는 스레드가 늘었지만 실제 병목은 DB 연결 풀에서 만들어지는 셈입니다.

CPU 바운드 작업이라면 워커 수를 논리 CPU 수 근처에서 시작하고, I/O 바운드 작업이라면 외부 자원의 제한을 넘지 않는 선에서 스레드 수를 더 높여볼 수 있습니다. 큐 크기는 장애를 오래 숨길 만큼 크게 잡기보다, 지연 시간이 커지는 시점을 관찰할 수 있게 잡는 편이 운영에 도움이 됩니다.

Node.js 워커 스레드로 비교하기

JavaScript 예시라고 해서 항상 브라우저 프런트엔드 주제는 아닙니다. 아래 코드는 Node.js 런타임에서 CPU 작업을 worker_threads로 분리해 실행하는 예입니다. 워커 수를 환경 변수로 바꿀 수 있게 두면 같은 코드로 여러 설정을 비교할 수 있습니다.

const { Worker } = require("node:worker_threads");
const os = require("node:os");

const workerCount = Number(process.env.WORKERS || os.availableParallelism());

for (let i = 0; i < workerCount; i += 1) {
  const worker = new Worker("./cpu-worker.js", {
    workerData: { workerId: i }
  });

  worker.on("message", (message) => {
    console.log(`[worker:${i}] ${message}`);
  });

  worker.on("error", (error) => {
    console.error(`[worker:${i}]`, error);
  });
}

4코어 또는 8논리 CPU 서버에서 WORKERS=4, WORKERS=8, WORKERS=16처럼 값을 바꿔 실행해보면 처리량과 지연 시간이 어떻게 변하는지 볼 수 있습니다. CPU 작업이라면 어느 지점부터 워커를 늘려도 처리량이 거의 늘지 않거나 오히려 떨어질 수 있습니다.

$ WORKERS=4 node app.js
workers=4   throughput=820 req/s  p95=42ms   cpu=88%

$ WORKERS=8 node app.js
workers=8   throughput=910 req/s  p95=65ms   cpu=96%

$ WORKERS=16 node app.js
workers=16  throughput=870 req/s  p95=140ms  cpu=99%

이 출력에서는 워커 8개일 때 처리량이 가장 높고, 16개에서는 p95 지연 시간이 크게 나빠졌습니다. CPU가 이미 거의 가득 찬 상태에서 실행 가능한 워커가 더 늘어나 컨텍스트 스위칭과 대기 시간이 커졌다고 볼 수 있습니다. 평균 처리량만 보면 놓칠 수 있는 변화라서 지연 시간 지표를 함께 보는 것이 중요합니다.

운영 명령 출력으로 병목 확인하기

운영 중인 서버에서는 코드만 보고 스레드 수를 결정하기 어렵습니다. 서버의 논리 CPU 수, 실행 가능한 작업의 밀림, 특정 프로세스의 스레드 수, I/O 대기 비율을 같이 확인해야 합니다.

$ lscpu
CPU(s):                  8
On-line CPU(s) list:     0-7
Thread(s) per core:      2
Core(s) per socket:      4
Socket(s):               1
Model name:              Intel(R) Xeon(R) CPU

$ ps -o pid,ppid,nlwp,%cpu,%mem,cmd -p 12345
  PID  PPID NLWP %CPU %MEM CMD
12345     1   96  385 18.2 java -jar api-server.jar

$ pidstat -t -p 12345 1
12:00:01      UID      TGID       TID    %usr %system  %CPU   CPU  Command
12:00:02     1000     12345         -  380.0    18.0 398.0     3  java
12:00:02     1000         -     12351   72.0     3.0  75.0     1  |__java
12:00:02     1000         -     12352   68.0     4.0  72.0     2  |__java
12:00:02     1000         -     12353   69.0     3.0  72.0     5  |__java

lscpu 출력에서 논리 CPU는 8개이고, 물리 코어는 4개입니다. 이 서버에서 NLWP가 96이라고 해서 바로 문제라고 단정할 수는 없습니다. I/O 대기가 많은 서버라면 가능한 구성입니다. 다만 CPU 사용률이 높고 응답 시간이 나쁘다면 스레드가 CPU를 두고 과하게 경쟁하는지 확인할 필요가 있습니다.

pidstat에서 프로세스 전체 CPU가 398%라면 대략 CPU 4개 분량을 쓰고 있다고 해석할 수 있습니다. 이 값은 단독으로 결론을 내기보다 응답 시간, 큐 길이, GC 로그, DB 대기 시간과 함께 봐야 합니다. 스레드 몇 개가 CPU를 오래 점유하는지, 많은 스레드가 조금씩 CPU를 쓰며 흔들리는지도 중요한 단서가 됩니다.

top에서 load average와 CPU 상태를 함께 보면 현재 병목의 방향을 더 빨리 잡을 수 있습니다.

top - 12:10:15 up 10 days,  2:31,  1 user,  load average: 18.42, 17.90, 16.75
Tasks: 245 total,  12 running, 233 sleeping,   0 stopped,   0 zombie
%Cpu(s): 92.1 us,  5.8 sy,  0.0 ni,  1.2 id,  0.4 wa,  0.0 hi,  0.5 si,  0.0 st
MiB Mem :  16000.0 total,    420.0 free,  14200.0 used,   1380.0 buff/cache

%Cpu(s): 12.0 us,  3.0 sy,  0.0 ni, 58.0 id, 26.5 wa,  0.0 hi,  0.5 si,  0.0 st

첫 번째 상태처럼 논리 CPU가 8개인 서버에서 load average가 18 근처로 계속 유지되고 CPU idle이 낮다면 실행 가능한 작업이 밀리고 있을 가능성이 큽니다. 이 상태에서 워커를 더 늘리면 처리량보다 지연 시간이 더 나빠질 수 있습니다.

두 번째 CPU 라인처럼 idle이 높고 I/O wait가 높다면 CPU 자체보다 디스크나 네트워크 I/O 대기가 병목일 수 있습니다. 스레드를 늘려 일부 처리량을 높일 수는 있지만, 느린 쿼리, 스토리지 지연, 외부 API 응답 지연이 원인이라면 대기 중인 요청 수만 커질 수 있습니다.

숫자를 조정할 때 남겨야 할 증거

스레드, 프로세스, 코어 수를 조정할 때는 변경 전후의 증거가 남아야 합니다. 단순히 “스레드를 늘렸더니 빨라졌다”보다 어떤 부하에서 처리량, 지연 시간, CPU, 메모리, I/O 대기가 어떻게 움직였는지를 같이 기록해야 다음 조정이 쉬워집니다.

기본적으로는 다음 항목을 같이 남기는 편이 좋습니다.

  • 설정값: 워커 수, 스레드 풀 크기, 큐 크기, DB 커넥션 풀 크기
  • 처리량: 초당 요청 수, 초당 작업 수, 배치 처리 시간
  • 지연 시간: 평균보다 p95, p99, timeout 수
  • CPU 상태: user, system, idle, iowait, load average, run queue
  • 메모리 상태: RSS, 힙 사용량, 스왑 발생 여부, 캐시 중복
  • 외부 자원: DB 대기 시간, 커넥션 풀 고갈, 외부 API 제한, 디스크 대기

스레드 풀을 키우기 전에는 DB 커넥션 풀과 외부 API 동시 호출 제한을 같이 확인해야 합니다. 프로세스 수를 늘릴 때는 프로세스당 RSS와 캐시 중복을 봐야 합니다. 코어 수를 늘린 뒤에도 단일 스레드 CPU 병목이나 전역 락이 남아 있으면 추가 코어는 충분히 쓰이지 못합니다.

결국 “많을수록 좋은가”라는 질문은 “현재 병목이 어디에 있는가”로 바꿔 보는 편이 더 정확합니다. CPU가 병목이면 실행 단위를 줄이거나 작업을 더 잘 나누는 방향이 필요하고, I/O가 병목이면 대기 원인을 줄이거나 외부 자원의 동시성 제한을 조정해야 합니다. 스레드, 프로세스, 코어는 성능을 높이는 수단이 될 수 있지만, 병목을 옮기거나 감추는 수단이 되기도 합니다.

원문 참고

https://www.maeil-mail.kr/question/84

댓글

이 블로그의 인기 게시물

아이콘 폰트 (icomoon 사용법)

 장난감 프로젝트를 만들다 보면, 아이콘이 필요한 경우가 있다. 간단하게 아이콘을 인터넷에서 검색하여, 이미지로 넣어두고 이미지 태그를 이용하여, 사용하는 경우가 일반적이였지만...  요즘에는 대부분 폰트를 이용하여 아이콘을 노출 한다. 나 같은 경우에도 기본적으로  https://material.io/resources/icons 를 참고하여 아이콘 폰트를 이용할 수 있도록 처리하고, 추가적으로 필요한 아이콘이고, 일상적으로 사용 되지 않는 아이콘의 경우에는  https://icomoon.io 에서 제작하여, 아이콘 폰트로 이용 하곤 한다.  그래서 이번에는 아이콘  https://icomoon.io 의 사용법을 간단히 공유하고자 한다.   들어가자 마자 위의 icoMoonApp버튼을 누르면 아래와 같은 화면이 나타난다.  icomoon에서 무료로 제공하는 아이콘들이 보이면 위에 파란색으로 표시 되어있는 집 모양 세가지를 선택한 후, 아래의 빨간색으로 표시되어있는 Generate Font를 눌러보자.  그리고 나서 바로 다운로드를 요청해보자. icomoon.zip이 다운로드가 될텐데, 압축을 해제해 보면, 아래의 폴더 및 파일들이 있다. 아래에서 중요한 것은 font 폴더와 style.css이다. demo-files fonts demo.html Read Me.txt selection.json style.css <!doctype html > <html> <head> <link rel ="stylesheet" href ="style.css" ></head> </head> <body> <span class ="icon-home" ></span> <span class ="icon-home2" ></span> <span class ="icon-hom...

Chart js와 amchart 비교

Chart js 특징은 위의 그림으로 대체 할 수 있을 듯 하다. 오픈 소스이고, 기본으로 제공하는 차트 종류가 8가지 Canv a s를 이용해서 차트를 그리고, 반응형을 지원한다. amchart amchart는 기본적으로 유료이며, 기본으로 제공하는 차트 종류가 기본적인 차트 + 주식 처럼 보이는 차트 + 지도에 관련된 차트(?) 까지 하면, 기본 제공 하는 종류가 20개 내외 이려나, 일일이 세기에는 양이 좀 많아 보인다. 렌더링은 svg를 통하여 그려지고, 당연 반응형도 지원이 된다. 그러면, 이 둘중에 어떤것이 내 프로젝트에 적합 하냐는 것이 문제이다. 일단, 주식 처럼 보이는 차트나 지도에 관련된 차트(?)가 필요하면, amchart를 선택해야 되는 것은 맞다. 그건 당연한 것이니 빼고 얘기 해보자! 여러 종류의 차트가 필요하다면, 일단은 amchart를 염두해 두는 것이 좋다. 돈 낸 만큼은 하는 듯 하다. 하지만, 기본적인 막대 그래프, 도넛 차트 등, 아주 기본적인 차트들인데, Chart js도 amchart도 그러한 차트가 없을 때가 문제가 된다. 그렇다면, 조금이라도 커스텀이 용이한 것을 찾는 것이 좋을 것이다.  일단 amchart에서 custom이라고 검색 하였을 때, 검색 결과가 61가지가 나온다. 차트의 종류도 많고, 각 차트마다 들어가는 속성이 매우 많기 때문에, 웬만한 내용들은 속성 값을 어떻게 주느냐에 따라서 변경이 가능 하게 된다. 커스텀의 예를 들면, 기본적으로 도넛 파이의 형태를 띄면서, 화살표로 목표를 표시해주는 차트가 필요하다고 생각 해보자. 이것은 amchart로 만든 그래프이고 이것은 chart js로 만든 그래프이다. 모양이 살짝 다르긴 하지만, 완벽하게 똑같이 구현 할 수도 있다. amchart로 만든 그래프의 경우, 저것은 도넛그래프가 아닌 guage 그래프이다. 원래 게이지 그래프는 이와 같...

javascript 압축 파일 다운로드

이번에는 전 게시글의 응용판? 이라고 해야하나....? 어쨋든! 우리는 각각의 파일들을 다운로드 해보았다. 그런데 생각보다 귀찮음?을 느꼇을 것이다. 파일을 각각 다운 받아야 한다는 현실때문에! 그래 파일 두개야 뭐 그렇다 치지... 하지만, 개발자도 사용자도 게으름뱅이이다. 자 결국, 우리가 해야 하는 것은 파일을 한 번에 둘다! 다운 받는 것이다. 물론, 클릭 한번에 여러개의 함수를 엮어서 다운받게 하면 되지만! 크롬에서 자주 봤듯이, 여러개의 파일을 다운로드를 시도하면 <- 여러개의 파일을 다운로드 합니다. 허용 합니까? 하고 물어보는 것을 볼 수 있다. 게다가 다운로드 한 파일들을 찾기도 귀찮다는 것. 자 해결책을 제시해보자면, https://github.com/Stuk/jszip 클라이언트 단에서 파일을 zip파일로 압축을 할 수가 있다! 필요한 작업은 아래와 같다. 0. 데이터 준비 1. BLOB(binary large object)를 만든다. 2. Blob을 URL.createObjectURL을 사용하여, 해당 binary의 주소를 생성. 3. 다운로드가 필요한 파일들을 Zip 객체에 셋팅! 4. a태그를 이용하여, 해당 url 셋팅 하고, 다운로드. 전 게시물과 별로 달라진게 없네... 자 그럼 샘플! 샘플을 보자! http://embed.plnkr.co/NMprnRxqYG0fkHa2J55D/ var util = {} function fixBinary(bin) { //binary to arrayBuffer var length = bin.length var buf = new ArrayBuffer(length) var arr = new Uint8Array(buf) for (var i = 0; i < length; i++) { arr[i] = bin.charCodeAt(i) } return buf } window.onload = function() { ...