Javascript에서 가비지 수집기 활동을 줄이기위한 모범 사례
초당 60 번 호출되는 메인 루프가있는 상당히 복잡한 자바 스크립트 앱이 있습니다. 많은 가비지 수집이 진행되고있는 것 같습니다 (Chrome 개발 도구의 메모리 타임 라인에서 '톱니'출력을 기반으로 함)-이것은 종종 애플리케이션의 성능에 영향을 미칩니다.
따라서 가비지 수집기가 수행해야하는 작업량을 줄이기위한 모범 사례를 연구하려고합니다. (웹에서 찾을 수 있었던 대부분의 정보는 메모리 누수 방지에 관한 것입니다. 약간 다른 질문입니다. 메모리가 비워지고 있습니다. 단지 너무 많은 가비지 수집이 진행되고 있다는 것입니다.) 저는 가정하고 있습니다. 이것은 대부분 가능한 한 많은 객체를 재사용하는 것으로 귀결되지만 물론 악마는 세부 사항에 있습니다.
이 앱은 John Resig의 Simple JavaScript Inheritance 라인을 따라 '클래스'로 구성됩니다 .
한 가지 문제는 일부 함수가 초당 수천 번 호출 될 수 있다는 것입니다 (메인 루프의 각 반복 동안 수백 번 사용되기 때문에), 그리고 아마도 이러한 함수 (문자열, 배열 등)의 로컬 작업 변수가 호출 될 수 있습니다. 문제가 될 수 있습니다.
나는 더 크고 무거운 객체에 대한 객체 풀링을 알고 있지만 (우리는 이것을 어느 정도 사용합니다), 특히 타이트 루프에서 매우 많이 호출되는 함수와 관련하여 전반적으로 적용될 수있는 기술을 찾고 있습니다. .
가비지 수집기가 수행해야하는 작업량을 줄이기 위해 어떤 기술을 사용할 수 있습니까?
그리고 아마도 가비지 수집되는 개체를 식별하기 위해 어떤 기술을 사용할 수 있습니까? (그것은 매우 큰 코드베이스이므로 힙의 스냅 샷을 비교하는 것이 그다지 유익하지 않았습니다)
GC 변동을 최소화하기 위해 수행해야하는 많은 작업은 대부분의 다른 시나리오에서 관용적 JS로 간주되는 것에 위배되므로 제가 제공하는 조언을 판단 할 때 컨텍스트를 염두에 두십시오.
할당은 여러 곳에서 현대 통역사에서 발생합니다.
new
리터럴 구문을 통해 또는을 통해 개체를 만들 때[...]
또는{}
.- 문자열을 연결할 때.
- 함수 선언이 포함 된 범위를 입력 할 때.
- 예외를 트리거하는 작업을 수행 할 때.
- 함수 표현식을 평가할 때 :
(function (...) { ... })
. - 당신이 강제 변환이 같은 오브젝트 것을 작업을 수행 할 때
Object(myNumber)
또는Number.prototype.toString.call(42)
- 내부적으로 이러한 작업을 수행하는 내장 함수를 호출하면
Array.prototype.slice
. arguments
매개 변수 목록을 반영하여 사용할 때 .- 문자열을 분할하거나 정규식과 일치하는 경우.
이를 피하고 가능한 한 개체를 풀링하고 재사용하십시오.
특히 다음과 같은 기회를 찾으십시오.
- 닫힌 상태에 대한 종속성이 없거나 거의없는 내부 함수를 더 높은 수명의 범위로 가져옵니다. ( 클로저 컴파일러 와 같은 일부 코드 축소자는 내부 함수를 인라인 할 수 있으며 GC 성능을 향상시킬 수 있습니다.)
- 구조화 된 데이터를 나타내거나 동적 주소 지정을 위해 문자열을 사용하지 마십시오. 특히
split
각각 여러 개체 할당이 필요하기 때문에 또는 정규식 일치를 사용하여 반복적으로 구문 분석하지 마십시오 . 이는 조회 테이블 및 동적 DOM 노드 ID에 대한 키에서 자주 발생합니다. 예를 들어,lookupTable['foo-' + x]
및document.getElementById('foo-' + x)
문자열 연결이 있으므로 모두 할당을 포함한다. 종종 다시 연결하는 대신 수명이 긴 개체에 키를 연결할 수 있습니다. 지원해야하는 브라우저에 따라Map
객체를 키로 직접 사용 하는 데 사용할 수 있습니다. - 일반 코드 경로에서 예외를 포착하지 마십시오. 대신
try { op(x) } catch (e) { ... }
수행if (!opCouldFailOn(x)) { op(x); } else { ... }
. - 예를 들어 서버에 메시지를 전달하기 위해 문자열 생성을 피할 수없는 경우
JSON.stringify
여러 객체를 할당하는 대신 내부 네이티브 버퍼를 사용하여 콘텐츠를 축적하는 내장형을 사용합니다. - 빈도가 높은 이벤트에 콜백을 사용하지 말고 가능한 경우 메시지 콘텐츠에서 상태를 다시 생성하는 수명이 긴 함수 (1 참조)를 콜백으로 전달합니다.
arguments
호출 될 때 배열과 같은 객체를 만들어야하는 함수를 사용 하지 마십시오 .
JSON.stringify
나가는 네트워크 메시지를 만드는 데 사용 하는 것이 좋습니다 . 입력 메시지를 사용하여 구문 분석하는 JSON.parse
것은 분명히 할당과 큰 메시지에 대한 많은 것을 포함합니다. 들어오는 메시지를 기본 배열로 나타낼 수 있다면 많은 할당을 저장할 수 있습니다. 할당하지 않는 파서를 빌드 할 수있는 유일한 다른 내장 기능은 String.prototype.charCodeAt
. 그래도 읽을 지옥 같은 것만 사용하는 복잡한 형식의 파서.
Chrome 개발자 도구에는 메모리 할당을 추적하는 데 매우 유용한 기능이 있습니다. 메모리 타임 라인이라고합니다. 이 문서에서는 몇 가지 세부 사항을 설명합니다. 나는 이것이 당신이 "톱니"에 대해 말하는 것이라고 생각합니까? 이는 대부분의 GC 런타임에서 정상적인 동작입니다. 할당은 수집을 트리거하는 사용량 임계 값에 도달 할 때까지 진행됩니다. 일반적으로 서로 다른 임계 값에 서로 다른 종류의 컬렉션이 있습니다.
Garbage collections are included in the event list associated with the trace along with their duration. On my rather old notebook, ephemeral collections are occurring at about 4Mb and take 30ms. This is 2 of your 60Hz loop iterations. If this is an animation, 30ms collections are probably causing stutter. You should start here to see what's going on in your environment: where the collection threshold is and how long your collections are taking. This gives you a reference point to assess optimizations. But you probably won't do better than to decrease the frequency of the stutter by slowing the allocation rate, lengthening the interval between collections.
The next step is to use the Profiles | Record Heap Allocations feature to generate a catalog of allocations by record type. This will quickly show which object types are consuming the most memory during the trace period, which is equivalent to allocation rate. Focus on these in descending order of rate.
The techniques are not rocket science. Avoid boxed objects when you can do with an unboxed one. Use global variables to hold and reuse single boxed objects rather than allocating fresh ones in each iteration. Pool common object types in free lists rather than abandoning them. Cache string concatenation results that are likely reusable in future iterations. Avoid allocation just to return function results by setting variables in an enclosing scope instead. You will have to consider each object type in its own context to find the best strategy. If you need help with specifics, post an edit describing details of the challenge you're looking at.
I advise against perverting your normal coding style throughout an application in a shotgun attempt to produce less garbage. This is for the same reason you should not optimize for speed prematurely. Most of your effort plus much of the added complexity and obscurity of code will be meaningless.
As a general principle you'd want to cache as much as possible and do as little creating and destroying for each run of your loop.
The first thing that pops in my head is to reduce the use of anonymous functions (if you have any) inside your main loop. Also it'd be easy to fall into the trap of creating and destroying objects that are passed into other functions. I'm by no means a javascript expert, but I would imagine that this:
var options = {var1: value1, var2: value2, ChangingVariable: value3};
function loopfunc()
{
//do something
}
while(true)
{
$.each(listofthings, loopfunc);
options.ChangingVariable = newvalue;
someOtherFunction(options);
}
would run much faster than this:
while(true)
{
$.each(listofthings, function(){
//do something on the list
});
someOtherFunction({
var1: value1,
var2: value2,
ChangingVariable: newvalue
});
}
Is there ever any downtime for your program? Maybe you need it to run smoothly for a second or two (e.g. for an animation) and then it has more time to process? If this is the case I could see taking objects that would normally be garbage collected throughout the animation and keeping a reference to them in some global object. Then when the animation ends you can clear all the references and let the garbage collector do it's work.
Sorry if this is all a bit trivial compared to what you've already tried and thought of.
I'd make one or few objects in the global scope
(where I'm sure garbage collector is not allowed to touch them), then I'd try to refactor my solution to use those objects to get the job done, instead of using local variables.
Of course it couldn't be done everywhere in the code, but generally that's my way to avoid garbage collector.
P.S. It might make that specific part of code a little bit less maintainable.
'Programing' 카테고리의 다른 글
emacs, 특정 창 분할 해제 (0) | 2020.09.05 |
---|---|
콘텐츠와 함께 저장된 Chrome 네트워크 디버거 har 파일을보고 재생하려면 어떻게합니까? (0) | 2020.09.05 |
PHP에서 언어 구조와 "내장"함수의 차이점은 무엇입니까? (0) | 2020.09.05 |
테스트를 위해 ExpressJS 인스턴스를 프로그래밍 방식으로 종료하려면 어떻게합니까? (0) | 2020.09.05 |
파이썬이 함수 정의를 인쇄 할 수 있습니까? (0) | 2020.09.04 |