2017-01-31 7 views
-1

자바 스크립트에서 STEP-files 파서를 작성하려고합니다. 브라우저에서 주로 사용되지만 Node에서는 디버그를 위해 사용됩니다.자바 스크립트 메모리 누수 루프

아주 잘 진행되고 있으며 잠시 파싱 중입니다. 그러나 내가 수백만 라인 (약 200Mb 이상)의 파일로 정말 커질 때 그것은 질식하고 결국 충돌하고 자바 스크립트 힙이 메모리 부족에 대해 불평합니다!

파일은 다음과 같이 보일 :

... 
#10=ORGANIZATION('O0001','LKSoft','company'); 
#11=PRODUCT_DEFINITION_CONTEXT('part definition',#12,'manufacturing'); 
#12=APPLICATION_CONTEXT('mechanical design'); 
#13=APPLICATION_PROTOCOL_DEFINITION('','automotive_design',2003,#12); 
#14=PRODUCT_DEFINITION('0',$,#15,#11); 
#15=PRODUCT_DEFINITION_FORMATION('1',$,#16); 
#16=PRODUCT('A0001','Test Part 1','',(#18)); 
#17=PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#16)); 
#18=PRODUCT_CONTEXT('',#12,''); 
... 
#3197182=APPLIED_ORGANIZATION_ASSIGNMENT(#10,#20,(#16)); 
#3197183=ORGANIZATION_ROLE('id owner'); 

파일이 편지에 대한 편지를 구문 분석, 그래서 상당히 무딘 파서를 쓰고있어 조금 불규칙 :

const fs = require('fs'); 

class bigObject { 
    constructor(data) { 

    this.parse(data); 
    } 

    propertyLexer(row) { 

    let refNrRE = /[-0-9]/; 
    let floatNumberRE = /[.\-0-9E]/; 
    let charsRE = /[_a-zA-Z.]/; 
    let stringRE = /'((?:''|[^'])*)'/; 

    let lexedRow = []; 
    let current = 0; 
    let rowLen = row.length; 

    while (current < rowLen) { 
     let char = row[current]; 

     // I.E. #32123 
     if (char === '#') { 
     let property = ''; 

     while (refNrRE.test(row[current + 1]) && current < rowLen) { 
      current++; 
      property += row[current]; 
     } 

     lexedRow.push(parseInt(property)); 

     current++; 
     } 

     // Empty property 
     else if (char === '$') { 
     lexedRow.push(''); 

     current++; 
     } 

     // Skip to next property 
     else if (char === ',') { 
     current++; 
     } 

     // I.E. 'Comments, blabla (more comments)' 
     else if (char === "'") { 
     let property = stringRE.exec(row.substr(current)); 

     lexedRow.push(property[1]); 

     current += property[1].length + 2; 
     } 

     // I.E. .AREAUNIT. 
     else if (charsRE.test(char)) { 
     let property = ''; 

     while (charsRE.test(row[current]) && current < rowLen) { 
      property += row[current]; 

      current++; 
     } 

     lexedRow.push(property); 
     } 

     // I.E. -1000.00 
     else if (floatNumberRE.test(char)) { 
     let property = ''; 

     while (floatNumberRE.test(row[current]) && current < rowLen) { 
      property += row[current]; 

      current++; 
     } 

     lexedRow.push(property); 
     } 

     // Skip rest for now 
     else { 
     current++; 
     } 
    } 

    return lexedRow; 
    } 

    parse(data) { 
    if (typeof data !== "string") { 
     try { 
     data = data.toString(); 
     } 
     catch (e) { 
     throw `Indata not string or not able to convert to string: ${e}`; 
     } 
    } 

    let stepRowRE = /#\d+\s*=\s*[a-zA-Z0-9]+\s*\([^)]*(?:\)(?!;)[^)]*)*\);/g; 

    // Split single row into three capture groups 
    let singleRowWithGroupingRE = /^#(\d+)\s*=\s*([a-zA-Z0-9]+)\s*\(([^)]*(?:\)(?!;)[^)]*)*)\);/; 

    let stepRows = data.match(stepRowRE); 
    let rowIndex = stepRows.length - 1; 
    let rowsFromFile = {}; 
    let count = 0; 

    for (let i = 0; i <= rowIndex; i++) { 
     let matching = singleRowWithGroupingRE.exec(stepRows[i]); 

     rowsFromFile[matching[1]] = {c: matching[2], p: this.propertyLexer(matching[3].replace(/(\r\n|\n|\r)/gm, ''))}; 

     if (i % 200000 === 0) { 
     console.log(i + '::' + JSON.stringify(rowsFromFile[matching[1]])); 
     } 

     count++; 

    } 
    } 
} 

//// Start here //// 

fs.readFile('./ifc-files/A-40-V-00252.ifc', (err, data) => { 
    let newObject = new bigObject(data); 
}); 

나는이 오류 :

<--- Last few GCs ---> 

[11348:000002D4A6E72260] 81407 ms: Mark-sweep 1403.2 (1458.8) -> 
1403.2 (1458.8) MB, 2428.1/0.0 ms allocation failure GC in old space requested [11348:000002D4A6E72260] 83836 ms: Mark-sweep 
1403.2 (1458.8) -> 1403.2 (1428.8) MB, 2429.0/0.0 ms last resort gc [11348:000002D4A6E72260] 86282 ms: Mark-sweep 1403.2 (1428.8) -> 
1403.1 (1428.8) MB, 2446.3/0.0 ms last resort gc 


<--- JS stacktrace ---> 

==== JS stack trace ========================================= 

Security context: 00000384656C0D51 <JS Object> 
    1: parse [C:\Users\user\Projects\parser\index.js:~95] [pc=000000525FB71B18](this=000001EE5F96DE19 <a bigObject with map 0000036221B1B7A9>,data=0000034357F04201 <Very long string[190322237]>) 
    2: new bigObject [C:\Users\user\Projects\parser\index.js:8] [pc=000000525FB48737](this=000001EE5F96DE19 <a bigObject with map 0000036221B1B7... 

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 

나는이 날을위한 이유를 지금 찾으려고 노력했지만 아무 것도 볼 수 없다. 마치 메모리 누수 또는 무한 루프처럼 보입니다.

내 컴퓨터는 16Gb 메모리를 가지고 있으며 여러 번 200mb 파일을 쉽게 처리 할 수 ​​있어야합니다!

내 문제를 해결할 수있는 사람이 있습니까? 감사!

편집 : Firefox 또는 Edge (!)를 사용하는 경우 모든 작업이 정상적으로 작동하며 --max_old_space_size=4096 플래그를 사용하여 Chrome/노드 (V8)의 사용 가능한 메모리를 늘리는 경우에도 문제가 없습니다. 하지만 일반 사용자가이 작업을 수행 할 가능성은 낮습니다 ... 그래서 여전히 더 많은 메모리를 효율적으로 사용해야합니다. 하지만 나는 아무런 단서가 없습니다.

EDIT2 : 문제를 일으키는 전체 파일을 읽는 것이 JSON.stringify가 아니거나 사실이 아닙니다. 현재 수행중인 것보다 더 큰 파일을 읽으려고하면이 문제가 발생합니다. 하지만 지금은 기억력이 너무 강해서 더 많은 것이 있습니다.

+2

필요하지 않은 경우 왜 전체 파일을 메모리로 읽어들입니까? 한 번에 한 줄씩 버퍼링하고 파싱하십시오. 파일 끝까지 반복하십시오. – mscdex

+0

정확히. 그리고 시스템이 파일을로드 할 수 있기 때문에 수백만 줄에 복잡한 정규식을 빠르게 수행하고 있습니다 _.생각할 필요가있는 것은 200MB 뿐이며, 파서를 유지하는 데 필요한 200MB * 라인 수 * 처리 능력과 메모리입니다. http://stackoverflow.com/a/32599033/1214800을 참조하십시오. – brandonscript

+0

나는 이것에 대해서도 생각해 봤지만, 문제의 파일 크기에는 필요하지 않아야한다. 지금은 파서의 논리에 더 관심이 많습니다. 다음 단계에서 버퍼에 대해 설명하겠습니다. – mottosson

답변

0

복잡해지기 전에 응용 프로그램이 충돌합니다. line 95에서의 충돌은 data.toString()을 호출 할 때 발생합니다.

분명히 Node.js는 200MB 문자열을 좋아하지 않습니다. 이것은 특히 놀라운 것이 아닙니다. 200MB는 String 구현에 대해 많은 질문을합니다.

입력 파일이 줄 바꿈 문자로 구성되어 있기 때문에 mscdex의 제안이 올바른 방법이라고 생각합니다. readline을 사용하고 파일을 한 줄씩 읽은 다음 각 줄을 구문 분석합니다.

code sample은 원하는대로하는 것으로 보입니다.

줄 단위 방식에는 won't block the event loop이라는 이점이 추가되었습니다. 다른 이벤트를 끼워 넣을 기회없이 엄청난 작업을하는 대신, 각 라인 이벤트 사이에 애플리케이션을 yield으로 쉽게 구성 할 수 있습니다. readline가 자동으로이 작업을 수행하지만, 그렇지 않을 수도 있습니다.

일부 관련 SO 질문 : this one, that one.

+0

예 파일이 매우 크면 95 행에서 충돌하지만 현재는 괜찮습니다. 문자열화할 때보 다 메모리에 문제가 생길 정도로 큰 파일을 관리합니다. 아마 메모리 사용으로 인해 충돌의 현재 문제를 해결했을 때 나중에 일종의 라인 별 리더를 추가 할 것입니다 ... – mottosson

+0

게시 한 오류는 95 행의 문제를 참조했습니다. 더 이상 문제가되지 않습니다, 새로운 SO 질문을 여는 것이 적절할 것입니다. –

+0

아니 그게 문제가 아니었다. 나는이 질문을보다 명확하게 편집했다. – mottosson