[javascript] Javascript에서 가비지 수집기 활동을 줄이기위한 모범 사례

초당 60 번 호출되는 메인 루프가있는 상당히 복잡한 자바 스크립트 앱이 있습니다. 많은 가비지 수집이 진행되고있는 것 같습니다 (Chrome 개발 도구의 메모리 타임 라인에서 ‘톱니’출력을 기반으로 함)-이는 종종 애플리케이션의 성능에 영향을 미칩니다.

따라서 가비지 수집기가 수행해야하는 작업량을 줄이기위한 모범 사례를 연구하려고합니다. (웹에서 찾을 수 있었던 대부분의 정보는 메모리 누수 방지에 관한 것입니다. 약간 다른 질문입니다. 메모리가 비워지고 있습니다. 단지 너무 많은 가비지 수집이 진행되고 있다는 것입니다.) 저는 가정하고 있습니다. 이것은 대부분 가능한 한 많은 객체를 재사용하는 것으로 귀결되지만 물론 악마는 세부 사항에 있습니다.

이 앱은 John Resig의 Simple JavaScript Inheritance 라인을 따라 ‘클래스’로 구성됩니다 .

한 가지 문제는 일부 함수가 초당 수천 번 호출 될 수 있다는 것입니다 (메인 루프의 각 반복 동안 수백 번 사용되기 때문에), 그리고 아마도 이러한 함수 (문자열, 배열 등)의 로컬 작업 변수가 호출 될 수 있습니다. 문제가 될 수 있습니다.

나는 더 크고 무거운 객체에 대한 객체 풀링을 알고 있지만 (우리는 이것을 어느 정도 사용합니다), 특히 타이트 루프에서 매우 많이 호출되는 함수와 관련하여 전반적으로 적용될 수있는 기술을 찾고 있습니다. .

가비지 수집기가 수행해야하는 작업량을 줄이기 위해 어떤 기술을 사용할 수 있습니까?

그리고 아마도 가비지 수집되는 개체를 식별하기 위해 어떤 기술을 사용할 수 있습니까? (그것은 매우 큰 코드베이스이므로 힙의 스냅 샷을 비교하는 것이 그다지 유익하지 않았습니다)



답변

GC 변동을 최소화하기 위해 수행해야하는 많은 작업은 대부분의 다른 시나리오에서 관용적 JS로 간주되는 것에 위배되므로 제가 제공하는 조언을 판단 할 때 컨텍스트를 염두에 두십시오.

할당은 여러 곳에서 현대 통역사에서 발생합니다.

  1. new리터럴 구문을 통해 또는을 통해 개체를 만들 때 [...]또는 {}.
  2. 문자열을 연결할 때.
  3. 함수 선언이 포함 된 범위를 입력 할 때.
  4. 예외를 트리거하는 작업을 수행 할 때.
  5. 함수 표현식을 평가할 때 : (function (...) { ... }).
  6. 당신이 강제 변환이 같은 오브젝트 것을 작업을 수행 할 때 Object(myNumber)또는Number.prototype.toString.call(42)
  7. 내부적으로 이러한 작업을 수행하는 내장 함수를 호출하면 Array.prototype.slice.
  8. arguments매개 변수 목록을 반영하여 사용할 때 .
  9. 문자열을 분할하거나 정규식과 일치하는 경우.

이를 피하고 가능한 한 개체를 풀링하고 재사용하십시오.

특히 다음과 같은 기회를 찾으십시오.

  1. 닫힌 상태에 대한 종속성이 없거나 거의없는 내부 함수를 더 높은 수명의 범위로 가져옵니다. ( 클로저 컴파일러 와 같은 일부 코드 축소자는 내부 함수를 인라인 할 수 있으며 GC 성능을 향상시킬 수 있습니다.)
  2. 구조화 된 데이터를 나타내거나 동적 주소 지정을 위해 문자열을 사용하지 마십시오. 특히 split각각 여러 개체 할당이 필요하기 때문에 또는 정규식 일치를 사용하여 반복적으로 구문 분석하지 마십시오 . 이는 조회 테이블 및 동적 DOM 노드 ID에 대한 키에서 자주 발생합니다. 예를 들어, lookupTable['foo-' + x]document.getElementById('foo-' + x)문자열 연결이 있으므로 모두 할당을 포함한다. 종종 다시 연결하는 대신 수명이 긴 개체에 키를 연결할 수 있습니다. 지원해야하는 브라우저에 따라 Map객체를 키로 직접 사용 하는 데 사용할 수 있습니다.
  3. 일반 코드 경로에서 예외를 포착하지 마십시오. 대신 try { op(x) } catch (e) { ... }수행 if (!opCouldFailOn(x)) { op(x); } else { ... }.
  4. 예를 들어 서버에 메시지를 전달하기 위해 문자열 생성을 피할 수없는 경우 JSON.stringify여러 개체를 할당하는 대신 내부 네이티브 버퍼를 사용하여 콘텐츠를 축적하는 내장형을 사용합니다.
  5. 빈도가 높은 이벤트에 콜백을 사용하지 말고 가능한 경우 메시지 콘텐츠에서 상태를 다시 생성하는 수명이 긴 함수 (1 참조)를 콜백으로 전달합니다.
  6. arguments호출 될 때 배열과 같은 객체를 만들어야하는 함수를 사용 하지 마십시오 .

JSON.stringify나가는 네트워크 메시지를 만드는 데 사용 하는 것이 좋습니다 . 입력 메시지를 사용하여 구문 분석하는 JSON.parse것은 분명히 할당과 큰 메시지에 대한 많은 것을 포함합니다. 수신 메시지를 기본 배열로 나타낼 수 있다면 많은 할당을 저장할 수 있습니다. 할당하지 않는 파서를 빌드 할 수있는 유일한 다른 내장 기능은 String.prototype.charCodeAt. 그래도 읽을 지옥 같은 것만 사용하는 복잡한 형식의 파서.


답변

크롬 개발자 도구는 메모리 할당을 추적하는 아주 좋은 기능이 있습니다. 메모리 타임 라인이라고합니다. 이 문서에서는 몇 가지 세부 사항을 설명합니다. 나는 이것이 당신이 “톱니”에 대해 말하는 것이라고 생각합니까? 이는 대부분의 GC 런타임에서 정상적인 동작입니다. 할당은 수집을 트리거하는 사용량 임계 값에 도달 할 때까지 진행됩니다. 일반적으로 서로 다른 임계 값에 서로 다른 종류의 컬렉션이 있습니다.

Chrome의 메모리 타임 라인

가비지 콜렉션은 지속 기간과 함께 추적과 연관된 이벤트 목록에 포함됩니다. 내 다소 오래된 노트북에서 임시 컬렉션은 약 4Mb에서 발생하며 30ms가 걸립니다. 이것은 60Hz 루프 반복 중 2 회입니다. 애니메이션 인 경우 30ms 컬렉션이 끊김을 유발할 수 있습니다. 여기에서 시작하여 환경에서 무슨 일이 일어나고 있는지 확인해야합니다. 수집 임계 값이 어디에 있고 수집에 걸리는 시간입니다. 이를 통해 최적화를 평가할 수있는 기준점이됩니다. 그러나 할당 속도를 늦추고 수집 간격을 늘려서 끊김의 빈도를 줄이는 것보다 더 나은 방법은 없을 것입니다.

다음 단계는 프로필 | 레코드 유형별 할당 카탈로그를 생성하는 레코드 힙 할당 기능. 이렇게하면 추적 기간 동안 가장 많은 메모리를 사용하는 개체 유형이 빠르게 표시되며 이는 할당 속도와 동일합니다. 속도의 내림차순으로 이들에 초점을 맞 춥니 다.

이 기술은 로켓 과학이 아닙니다. 상자가없는 개체로 할 수있는 경우 상자에있는 개체를 피하십시오. 전역 변수를 사용하여 각 반복에서 새로운 개체를 할당하는 대신 단일 박스형 개체를 보유하고 재사용합니다. 일반 개체 유형을 버리지 않고 사용 가능한 목록에 풀링합니다. 향후 반복에서 재사용 할 수있는 캐시 문자열 연결 결과. 대신 둘러싸는 범위에 변수를 설정하여 함수 결과를 반환하기위한 할당을 피하십시오. 최상의 전략을 찾으려면 고유 한 컨텍스트에서 각 개체 유형을 고려해야합니다. 세부 사항에 대한 도움이 필요하면보고있는 챌린지의 세부 사항을 설명하는 편집을 게시하십시오.

쓰레기를 줄이려는 샷건 시도에서 응용 프로그램 전체에 걸쳐 정상적인 코딩 스타일을 왜곡하지 않는 것이 좋습니다. 이것은 속도를 너무 일찍 최적화하지 말아야하는 것과 같은 이유입니다. 대부분의 노력과 추가 된 복잡성 및 코드의 모호함은 의미가 없습니다.


답변

일반적으로 가능한 한 많이 캐시하고 루프가 실행될 때마다 생성 및 삭제를 최소화하는 것이 좋습니다.

내 머릿속에서 가장 먼저 떠오르는 것은 메인 루프 내에서 익명 함수 (있는 경우)의 사용을 줄이는 것입니다. 또한 다른 함수로 전달되는 객체를 만들고 파괴하는 함정에 빠지기 쉽습니다. 나는 결코 자바 스크립트 전문가는 아니지만 다음과 같이 상상할 것입니다.

var options = {var1: value1, var2: value2, ChangingVariable: value3};
function loopfunc()
{
    //do something
}

while(true)
{
    $.each(listofthings, loopfunc);

    options.ChangingVariable = newvalue;
    someOtherFunction(options);
}

이보다 훨씬 빠르게 실행됩니다.

while(true)
{
    $.each(listofthings, function(){
        //do something on the list
    });

    someOtherFunction({
        var1: value1,
        var2: value2,
        ChangingVariable: newvalue
    });
}

프로그램에 다운 타임이 있습니까? 1 ~ 2 초 (예 : 애니메이션)를 부드럽게 실행해야하고 처리하는 데 더 많은 시간이 필요합니까? 이 경우 애니메이션 전체에서 일반적으로 가비지 수집되는 개체를 가져와 일부 전역 개체에 참조를 유지하는 것을 볼 수 있습니다. 그런 다음 애니메이션이 끝나면 모든 참조를 지우고 가비지 수집기가 작동하도록 할 수 있습니다.

이미 시도하고 생각한 것에 비해 이것이 모두 약간 사소한 경우 죄송합니다.


답변

나는 global scope(가비지 수집기가 그것들을 만질 수 없다고 확신하는 곳 에서) 하나 또는 몇 개의 객체를 만든 다음, 지역 변수를 사용하는 대신 해당 객체를 사용하여 작업을 완료하도록 솔루션을 리팩터링하려고합니다. .

물론 코드의 모든 곳에서 수행 할 수는 없지만 일반적으로 가비지 수집기를 피하는 방법입니다.

추신 : 그것은 코드의 특정 부분을 유지 관리하기 어렵게 만들 수 있습니다.


답변