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


94

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

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

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

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

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

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

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


2
우리에게 보여줄 수있는 코드의 예가 있습니까? (나는 확실히 여기 아니에요, 그래서뿐만 아니라 잠재적으로 적은 일반) 질문은 대답 쉬울 것이다
존 드보락

2
초당 수천 번 기능 실행을 중지하는 것은 어떻습니까? 이것이 정말로 이것에 접근하는 유일한 방법입니까? 이 질문은 XY 문제처럼 보입니다. X를 설명하고 있지만 실제로 찾고있는 것은 Y에 대한 해결책입니다.
Travis J

2
@TravisJ : 그는 초당 60 번만 실행하는데, 이는 매우 일반적인 애니메이션 속도입니다. 그는 더 적은 일을하라고 요구하지 않고 어떻게하면 더 효율적으로 가비지 수집을 수행 할 수 있습니다.
Bergi

1
@Bergi- "일부 함수는 초당 수천 번 호출 될 수 있습니다." 이는 밀리 초당 한 번입니다 (더 나쁠 수 있습니다!). 그것은 전혀 일반적이지 않습니다. 초당 60 회는 문제가되지 않습니다. 이 질문은 지나치게 모호하며 의견이나 추측 만 가능합니다.
Travis J

4
@TravisJ-게임 프레임 워크에서는 드물지 않습니다.
UpTheCreek 2013-08-21

답변:


127

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. 그래도 읽을 지옥 같은 것만 사용하는 복잡한 형식의 파서.


JSON.parsed 객체가 메시지 문자열보다 적은 (또는 동일한) 공간을 할당 한다고 생각하지 않습니까?
Bergi

@Bergi, 속성 이름에 별도의 할당이 필요한지 여부에 따라 다르지만 구문 분석 트리 대신 이벤트를 생성하는 구문 분석기는 불필요한 할당을 수행하지 않습니다.
Mike Samuel

환상적인 대답, 감사합니다! 현상금 만료에 대한 많은 사과-그 당시 여행 중이 었는데, 어떤 이유에서인지 내 휴대 전화의 Gmail 계정으로 SO에 로그인 할 수 없었습니다 .... : /
UpTheCreek

바운티로 나쁜 타이밍을 보충하기 위해 추가로 추가했습니다. (200은 내가 줄 수있는 최소값입니다.)-어떤 이유에서든 수여하기 전에 24 시간을 기다려야합니다. '기존 답변 보상'을 선택했습니다.) 내일 당신 것입니다 ...
UpTheCreek

@UpTheCreek, 걱정 마세요. 유용하다고 생각해서 다행입니다.
Mike Samuel

13

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

Chrome의 메모리 타임 라인

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

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

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

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


맞아요, 그게 톱니가 의미하는 것입니다. 나는 항상 어떤 종류의 톱니파 패턴이있을 것이라는 것을 알고 있지만, 내 앱에서는 톱니파 주파수와 '절벽'이 상당히 높다는 것이 우려됩니다. 흥미롭게도, GC 이벤트 내 타임 라인에 표시되지 않음 - '기록'창 (가운데 하나)에 표시되는 이벤트 만이 : request animation frame, animation frame fired,와 composite layers. 나는 GC Event당신처럼 보이지 않는 이유를 모르겠습니다 (최신 버전의 크롬과 카나리아에 있습니다).
UpTheCreek 2013-09-11

4
프로파일 러를 '레코드 힙 할당'과 함께 사용해 보았지만 지금까지 그다지 유용하지 않았습니다. 제대로 사용하는 방법을 몰라서 그런 것 같습니다. 같은 나에게 아무 의미 참고 문헌의 전체 것으로 보인다 @342342code relocation info.
UpTheCreek 2013-09-11

9

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

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

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

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


이. 또한 다른 함수 (IIFE가 아님)에서 언급 된 함수도 많은 메모리를 소모하고 놓치기 쉬운 일반적인 남용입니다.
Esailija

고마워 크리스! 안타깝게도 다운 타임이 없습니다 : /
UpTheCreek 2013-09-11

4

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

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

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


GC는 내 전역 범위 변수를 일관되게 가져옵니다.
VectorVortec
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.