Node.js 로 메모리 효율 이 높 은 프로그램 을 만 드 는 방법

11098 단어 Node능률.성능
머리말
소프트웨어 응용 프로그램 은 컴퓨터 의 메 인 메모리 에서 실 행 됩 니 다.우 리 는 랜 덤 액세스 메모리(RAM)라 고 부 릅 니 다.JavaScript,특히 Nodejs(서버 js)는 터미널 사용 자 를 위해 소형 에서 대형 소프트웨어 프로젝트 를 작성 할 수 있 도록 합 니 다.처리 프로그램의 메모 리 는 항상 까다 로 운 문제 입 니 다.잘못된 구현 은 주어진 서버 나 시스템 에서 실행 되 는 모든 다른 프로그램 을 막 을 수 있 기 때 문 입 니 다.C 와 C++프로 그 래머 는 확실히 메모리 관리 에 관심 을 가진다.코드 의 구석구석 에 숨겨 져 있 으 면 무 서운 메모리 누 출 이 발생 할 수 있 기 때문이다.하지만 JS 개발 자 에 게 당신 은 정말 이 문제 에 관심 을 가 진 적 이 있 습 니까?
JS 개발 자 들 은 보통 전용 고 용량 서버 에서 웹 서버 프로 그래 밍 을 하기 때문에 다 중 태 스 크 처리 지연 을 감지 하지 못 할 수도 있다.예 를 들 어 웹 서버 를 개발 하 는 상황 에서 도 데이터베이스 서버(MySQL),캐 시 서버(Redis)와 기타 필요 한 응용 프로그램 을 여러 개 실행 할 것 입 니 다.우 리 는 그것들 도 사용 가능 한 메 인 메모 리 를 소모 할 것 이라는 것 을 알 아야 한다.프로그램 을 마음대로 작성 하면 다른 프로 세 스 의 성능 을 떨 어 뜨리 고 메모리 가 분 배 를 완전히 거부 할 수도 있 습 니 다.본 논문 에서 우 리 는 하나의 문 제 를 해결 함으로써 NodeJS 의 흐름,버퍼,파이프 등 구 조 를 파악 하고 메모리 의 효과 적 인 응용 프로그램 을 어떻게 지원 하 는 지 알 고 있 습 니 다.
질문:큰 파일 복사
누 군가 NodeJS 로 파일 복사 프로그램 을 써 달 라 는 요청 을 받 으 면 다음 코드 를 빠르게 쓸 것 입 니 다.

const fs = require('fs');

let fileName = process.argv[2];
let destPath = process.argv[3];

fs.readFile(fileName, (err, data) => {
    if (err) throw err;

    fs.writeFile(destPath || 'output', data, (err) => {
        if (err) throw err;
    });
    
    console.log('New file has been created!');
});
이 코드 는 입력 한 파일 이름과 경로 에 따라 파일 을 읽 은 후에 대상 경 로 를 기록 하 는 것 은 작은 파일 에 문제 가 되 지 않 습 니 다.
지금 우리 에 게 큰 파일(4GB 이상)이 있다 고 가정 하면 이 프로그램 으로 백업 해 야 합 니 다.7.4G 에 달 하 는 초고 화질 4K 영 화 를 예 로 들 어 보 겠 습 니 다.저 는 상기 프로그램 코드 로 현재 디 렉 터 리 에서 다른 디 렉 터 리 로 복사 하 겠 습 니 다.

$ node basic_copy.js cartoonMovie.mkv ~/Documents/bigMovie.mkv
그리고 Ubuntu(Linux)시스템 에서 나 는 이 오 류 를 얻 었 다.
/home/shobarani/Workspace/basic_copy.js:7
    if (err) throw err;
             ^
RangeError: File size is greater than possible Buffer: 0x7fffffff bytes
    at FSReqWrap.readFileAfterStat [as oncomplete] (fs.js:453:11)
보시 다시 피 NodeJS 는 최대 2GB 의 데 이 터 를 버퍼 에 만 쓸 수 있 기 때문에 파일 을 읽 는 과정 에서 오류 가 발생 했 습 니 다.이 문 제 를 해결 하기 위해 서 는 I/O 밀집 작업 을 할 때(복사,처리,압축 등)메모리 의 상황 을 고려 하 는 것 이 좋 습 니 다.
NodeJS 의 Streams 와 Buffers.
상기 문 제 를 해결 하기 위해 서 우 리 는 큰 파일 을 많은 파일 블록 으로 자 르 는 방법 이 필요 하 며,동시에 데이터 구조 로 이 파일 블록 을 저장 해 야 한다.하나의 buffer 는 바 이 너 리 데 이 터 를 저장 하 는 구조 이다.다음은 파일 블록 을 읽 고 쓰 는 방법 이 필요 하 며,Streams 는 이 부분 을 제공 합 니 다.
버퍼(버퍼)
우 리 는 Buffer 대상 을 이용 하여 쉽게 buffer 를 만 들 수 있다.

let buffer = new Buffer(10); # 10   buffer    
console.log(buffer); # prints <Buffer 00 00 00 00 00 00 00 00 00 00>
새 버 전의 NodeJS(>8)에서 도 이렇게 쓸 수 있다.

let buffer = new Buffer.alloc(10);
console.log(buffer); # prints <Buffer 00 00 00 00 00 00 00 00 00 00>
만약 우리 가 배열 이나 다른 데이터 세트 와 같은 데 이 터 를 가지 고 있다 면,우 리 는 그것들 을 위해 buffer 를 만 들 수 있 습 니 다.

let name = 'Node JS DEV';
let buffer = Buffer.from(name);
console.log(buffer) # prints <Buffer 4e 6f 64 65 20 4a 53 20 44 45 5>
Buffers 는 buffer.toString()과 buffer.toJSON()과 같은 중요 한 방법 이 있어 서 저 장 된 데이터 에 깊이 들 어 갈 수 있 습 니 다.
우 리 는 코드 를 최적화 하기 위해 서 원본 buffer 를 직접 만 들 지 않 을 것 이다.NodeJS 와 V8 엔진 은 streams 와 네트워크 socket 을 처리 할 때 내부 버퍼(대기 열)를 만 드 는 과정 에서 이 를 실현 했다.
Streams(흐름)
쉽게 말 하면 흐름 은 NodeJS 대상 의 임의의 문 과 같다.컴퓨터 네트워크 에서 입 구 는 입력 동작 이 고 출구 는 출력 동작 이다.우 리 는 이어서 이 용어 들 을 계속 사용 할 것 이다.
흐름 의 유형 은 모두 네 가지 가 있다.
읽 을 수 있 는 흐름(데 이 터 를 읽 는 데 사용)
  • 스 트림 을 쓸 수 있 습 니 다
  • 4.567917.쌍 공 류(읽 기와 쓰기 에 사용 가능)
  • 변환 흐름(데 이 터 를 처리 하 는 사용자 정의 이중 작업 흐름,예 를 들 어 압축,검사 데이터 등)
  • 아래 의 이 말 은 왜 우리 가 흐름 을 사용 해 야 하 는 지 명확 하 게 설명 할 수 있다.
    Stream API(특히 stream.pipe()방법)의 중요 한 목 표 는 데이터 버퍼 를 받 아들 일 수 있 는 수준 으로 제한 하 는 것 입 니 다.그러면 서로 다른 속도 의 소스 와 목표 가 사용 가능 한 메모 리 를 막 지 않 습 니 다.
    우 리 는 시스템 을 무 너 뜨리 지 않 고 임 무 를 완성 할 방법 이 필요 하 다.이것 도 우리 가 문장 첫머리 에 이미 언급 한 것 이다.

    위의 설명도 에서 우 리 는 두 가지 유형의 흐름 이 있 는데 그것 이 바로 읽 을 수 있 는 흐름 과 쓸 수 있 는 흐름 이다.pipe()방법 은 매우 기본 적 인 방법 으로 읽 을 수 있 는 흐름 과 쓸 수 있 는 흐름 을 연결 하 는 데 사용 된다.만약 당신 이 위의 설명도 도 를 모 르 더 라 도 괜 찮 습 니 다.우리 의 예 를 본 후에 당신 은 설명도 로 돌아 갈 수 있 습 니 다.그 때 는 모든 것 이 당연 해 보일 것 입 니 다.파 이 프 는 사람들의 주목 을 끄 는 메커니즘 이다.다음은 우리 가 두 가지 예 로 그것 을 설명 한다.
    해법 1(간단하게 스 트림 으로 파일 복사)
    앞의 큰 파일 복사 문 제 를 해결 하기 위해 해법 을 설계 합 시다.우선 우 리 는 두 개의 흐름 을 만 든 후에 다음 몇 가지 절 차 를 실행 해 야 한다.
    1.읽 을 수 있 는 스 트림 의 데이터 블록 을 감청 합 니 다.
    2.데이터 블록 을 쓰기 가능 한 흐름 에 쓴다.
    3.파일 복사 진행 추적
    우 리 는 이 코드 를 streams 라 고 명명 했다.copy_basic.js
    
    /*
        A file copy with streams and events - Author: Naren Arya
    */
    
    const stream = require('stream');
    const fs = require('fs');
    
    let fileName = process.argv[2];
    let destPath = process.argv[3];
    
    const readabale = fs.createReadStream(fileName);
    const writeable = fs.createWriteStream(destPath || "output");
    
    fs.stat(fileName, (err, stats) => {
        this.fileSize = stats.size;
        this.counter = 1;
        this.fileArray = fileName.split('.');
        
        try {
            this.duplicate = destPath + "/" + this.fileArray[0] + '_Copy.' + this.fileArray[1];
        } catch(e) {
            console.exception('File name is invalid! please pass the proper one');
        }
        
        process.stdout.write(`File: ${this.duplicate} is being created:`);
        
        readabale.on('data', (chunk)=> {
            let percentageCopied = ((chunk.length * this.counter) / this.fileSize) * 100;
            process.stdout.clearLine();  // clear current text
            process.stdout.cursorTo(0);
            process.stdout.write(`${Math.round(percentageCopied)}%`);
            writeable.write(chunk);
            this.counter += 1;
        });
        
        readabale.on('end', (e) => {
            process.stdout.clearLine();  // clear current text
            process.stdout.cursorTo(0);
            process.stdout.write("Successfully finished the operation");
            return;
        });
        
        readabale.on('error', (e) => {
            console.log("Some error occured: ", e);
        });
        
        writeable.on('finish', () => {
            console.log("Successfully created the file copy!");
        });
        
    });
    이 프로그램 에서 저 희 는 사용자 가 들 어 오 는 두 개의 파일 경로(원본 파일 과 대상 파일)를 받 은 다음 에 두 개의 흐름 을 만 들 었 습 니 다.데이터 블록 을 읽 을 수 있 는 흐름 에서 쓸 수 있 는 흐름 으로 옮 기 는 데 사 용 됩 니 다.그 다음 에 우 리 는 파일 복사 진 도 를 추적 하고 콘 솔 로 출력 하 는 변 수 를 정의 했다.이와 동시에 우 리 는 몇 가지 사건 을 구독 했다.
    data:데이터 블록 을 읽 을 때 터치 합 니 다.
    end:데이터 블록 이 읽 을 수 있 는 흐름 에 의 해 읽 혔 을 때 터치 합 니 다.
    error:데이터 블록 을 읽 는 중 오류 가 발생 했 을 때 발생 합 니 다.
    이 프로그램 을 실행 하면 큰 파일(7.4 G)의 복사 작업 을 성공 적 으로 수행 할 수 있 습 니 다.
    
    $ time node streams_copy_basic.js cartoonMovie.mkv ~/Documents/4kdemo.mkv
    그러나 작업 관리 자 를 통 해 프로그램 이 실행 중인 메모리 상 태 를 관찰 할 때 문제 가 있 습 니 다.

    4.6GB?우리 프로그램 이 실 행 될 때 소모 되 는 메모 리 는 말 이 안 되 고 다른 프로그램 이 걸 려 죽 을 수도 있 습 니 다.
    무슨 일이 있 었 죠?
    만약 네가 위의 그림 속 의 읽 기와 쓰기 율 을 자세히 관찰한다 면,너 는 약간의 실 마 리 를 발견 할 수 있 을 것 이다.
    Disk Read: 53.4 MiB/s
    Disk Write: 14.8 MiB/s
    생산자 가 더 빠 른 속도 로 생산 하고 있 고 소비자 들 이 이 속 도 를 따라 가지 못 하고 있다 는 뜻 이다.컴퓨터 는 읽 은 데이터 블록 을 저장 하기 위해 남 은 데 이 터 를 기계 의 RAM 에 저장 합 니 다.RAM 이 정점 을 찍 은 이유 다.
    상술 한 코드 는 내 기계 에서 3 분 16 초 동안 운행 되 었 다.
    17.16s user 25.06s system 21% cpu 3:16.61 total
    해법 2(흐름 과 자동 배 압 기반 파일 복사)
    상술 한 문 제 를 극복 하기 위해 서,우 리 는 프로그램 을 수정 하여 디스크 의 읽 기와 쓰기 속 도 를 자동 으로 조정 할 수 있다.이 기 제 는 바로 배 압 이다.우 리 는 너무 많이 할 필요 가 없다.읽 기 가능 한 흐름 을 쓰기 가능 한 흐름 으로 가 져 오 면 된다.NodeJS 는 배 압 작업 을 맡 을 것 이다.
    이 프로그램 을 streams 라 고 명명 합 시다.copy_efficient.js
    
    /*
        A file copy with streams and piping - Author: Naren Arya
    */
    
    const stream = require('stream');
    const fs = require('fs');
    
    let fileName = process.argv[2];
    let destPath = process.argv[3];
    
    const readabale = fs.createReadStream(fileName);
    const writeable = fs.createWriteStream(destPath || "output");
    
    fs.stat(fileName, (err, stats) => {
        this.fileSize = stats.size;
        this.counter = 1;
        this.fileArray = fileName.split('.');
        
        try {
            this.duplicate = destPath + "/" + this.fileArray[0] + '_Copy.' + this.fileArray[1];
        } catch(e) {
            console.exception('File name is invalid! please pass the proper one');
        }
        
        process.stdout.write(`File: ${this.duplicate} is being created:`);
        
        readabale.on('data', (chunk) => {
            let percentageCopied = ((chunk.length * this.counter) / this.fileSize) * 100;
            process.stdout.clearLine();  // clear current text
            process.stdout.cursorTo(0);
            process.stdout.write(`${Math.round(percentageCopied)}%`);
            this.counter += 1;
        });
        
        readabale.pipe(writeable); // Auto pilot ON!
        
        // In case if we have an interruption while copying
        writeable.on('unpipe', (e) => {
            process.stdout.write("Copy has failed!");
        });
        
    });
    이 예 에서 우 리 는 이전의 데이터 블록 기록 작업 을 코드 로 바 꾸 었 다.
    
    readabale.pipe(writeable); // Auto pilot ON!
    이곳 의 pipe 는 모든 마법 이 발생 하 는 원인 이다.그것 은 메모리(RAM)를 막 지 않 을 정도 로 디스크 읽 기와 쓰기 속 도 를 제어 했다.
    운행 해 봐.
    
    $ time node streams_copy_efficient.js cartoonMovie.mkv ~/Documents/4kdemo.mkv
    우 리 는 같은 큰 파일(7.4GB)을 복사 해서 메모리 이 용 률 을 살 펴 보 았 다.

    깜짝 이 야!현재 Node 프로그램 은 61.9 MiB 의 메모리 만 차지 하고 있 습 니 다.읽 기와 쓰기 속 도 를 관찰 했다 면:
    Disk Read: 35.5 MiB/s
    Disk Write: 35.5 MiB/s
    임의로 주어진 시간 내 에 배 압 이 존재 하기 때문에 읽 기와 쓰기 속도 가 일치 할 수 있다.더욱 놀 라 운 것 은 이 최 적 화 된 프로그램 코드 가 이전 보다 13 초 빨 라 졌 다 는 점 이다.
    12.13s user 28.50s system 22% cpu 3:03.35 total
    노드 JS 흐름 과 배관 으로 메모리 부하 가 98.68%줄 었 고 실행 시간 도 줄 었 다.이것 이 바로 파이프 가 강 한 존재 인 이유 다.
    61.9 MiB 는 읽 기 가능 한 흐름 으로 만 든 버퍼 크기 입 니 다.버퍼 블록 에 사용자 정의 크기 를 할당 하기 위해 읽 을 수 있 는 스 트림 의 read 방법 도 사용 할 수 있 습 니 다.
    
    const readabale = fs.createReadStream(fileName);
    readable.read(no_of_bytes_size);
    로 컬 파일 의 복 제 를 제외 하고 이 기술 은 많은 I/O 작업 을 최적화 하 는 데 도 사용 할 수 있다.
    카 프 카 에서 데이터베이스 로 의 데이터 흐름 처리
  • 파일 시스템 에서 온 데이터 흐름 을 처리 하고 동적 으로 압축 하여 디스크 에 기록 합 니 다
  • 더..............................................................
    결론.
    내 가 이 글 을 쓴 동 기 는 NodeJS 가 좋 은 API 를 제공 하 더 라 도 우 리 는 주의 하지 않 고 성능 이 매우 나 쁜 코드 를 쓸 수 있다 는 것 을 설명 하기 위해 서 이다.만약 우리 가 그 내 장 된 도구 에 더 많은 관심 을 가 질 수 있다 면 우 리 는 프로그램의 운행 방식 을 더욱 최적화 시 킬 수 있 을 것 이다.
    이상 은 Node.js 로 메모리 효율 이 높 은 프로그램 을 만 드 는 방법 에 대한 자세 한 내용 입 니 다.Node.js 에 관 한 자 료 는 다른 관련 글 을 주목 하 십시오!

    좋은 웹페이지 즐겨찾기