Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

Ruby의 실용적인 가비지 컬렉션 튜닝

아래 게시물은 2017년 Nate Berkopec의 'GC.stat를 통한 Ruby GC 이해' 기사를 기반으로 한 것으로 확인되었습니다. 이 기사의 일부가 표절된 것으로 보이며, 이는 원저자가 언급할 때까지 우리가 알지 못했던 것입니다. 우리는 게시하기 전에 모든 기사를 표절 도구를 통해 실행했지만 이를 선택하지 않았습니다. 이 실수에 대해 Nate와 독자들에게 큰 사과를 드립니다.

앱 성능을 완벽하게 제어하려면 Ruby에서 가비지 수집이 작동하는 방식을 이해하는 것이 중요합니다.

이 게시물에서는 Ruby에서 가비지 수집을 구현하고 사용자 지정하는 방법에 대해 자세히 알아보겠습니다.

가자!

Ruby 가비지 수집기 모듈

Ruby Garbage Collector 모듈은 Ruby의 표시 및 스윕 가비지 수집 메커니즘에 대한 인터페이스입니다.

필요할 때 백그라운드에서 자동으로 실행되는 동안 GC 모듈을 사용하면 필요할 때마다 수동으로 GC를 호출하고 가비지 수집 주기가 실행되는 방식에 대한 통찰력을 얻을 수 있습니다. 모듈은 중간 성능으로 변경할 수 있는 몇 가지 매개변수를 제공합니다.

이 모듈에서 가장 일반적으로 사용되는 방법은 다음과 같습니다.

  • 시작/가비지_수집 :이 방법은 가비지 수집 주기를 수동으로 시작합니다.
  • 활성화/비활성화 :이 메서드는 자동 가비지 수집 주기를 활성화하거나 비활성화합니다. 작업이 성공했는지 여부를 나타내는 부울 값을 반환합니다.
  • 통계 :이 메소드는 GC 모듈의 성능을 설명하는 키와 값의 목록을 제공합니다. 다음 섹션에서 이러한 측정항목을 자세히 살펴보겠습니다.

Ruby Garbage Collector 매개변수 이해

Ruby의 GC가 내부적으로 어떻게 작동하는지 이해하기 위해 GC 모듈의 메트릭을 살펴보겠습니다. 새로 부팅된 irb에서 다음 명령을 실행하십시오.

puts GC.stat

화면에 다음과 같은 숫자가 나타납니다.

{
    :count=>12,
    :heap_allocated_pages=>49,
    :heap_sorted_length=>49,
    :heap_allocatable_pages=>0,
    :heap_available_slots=>19975,
    :heap_live_slots=>19099,
    :heap_free_slots=>876,
    :heap_final_slots=>0,
    :heap_marked_slots=>16659,
    :heap_eden_pages=>49,
    :heap_tomb_pages=>0,
    :total_allocated_pages=>49,
    :total_freed_pages=>0,
    :total_allocated_objects=>66358,
    :total_freed_objects=>47259,
    :malloc_increase_bytes=>16216,
    :malloc_increase_bytes_limit=>16777216,
    :minor_gc_count=>10,
    :major_gc_count=>2,
    :remembered_wb_unprotected_objects=>191,
    :remembered_wb_unprotected_objects_limit=>312,
    :old_objects=>16024,
    :old_objects_limit=>23556,
    :oldmalloc_increase_bytes=>158824,
    :oldmalloc_increase_bytes_limit=>16777216
}

이것은 런타임에서 가비지 수집이 어떻게 발생했는지에 대한 모든 정보를 보유합니다. 각각의 수치를 자세히 살펴보겠습니다.

Ruby Garbage Collector의 카운트

다음 키에 대해 설명하는 것으로 시작하겠습니다.

{
    :count=>12,
    #…
    :minor_gc_count=>10,
    :major_gc_count=>2,
}

이것은 GC 수이며 매우 간단한 정보를 전달합니다. minor_gc_countmajor_gc_count 각 유형의 가비지 수집 실행 횟수입니다.

Ruby에는 두 가지 유형의 가비지 컬렉션이 있습니다.

마이너 GC는 새로운 개체, 즉 3회 이하의 가비지 수집 주기에서 살아남은 개체만 가비지 수집을 시도하는 가비지 수집 시도를 나타냅니다.

반면에 주요 GC는 가비지 수집 주기가 세 번 이상 지속된 개체를 포함하여 모든 개체를 가비지 수집하려고 시도하는 가비지 수집 시도입니다. count minor_gc_count의 합계입니다. 및 major_gc_count .

GC 수를 추적하면 몇 가지 이유로 도움이 될 수 있습니다. 특정 작업이나 프로세스가 항상 GC를 트리거하는지 여부와 이를 트리거하는 횟수를 파악할 수 있습니다. 다중 스레드 응용 프로그램과 같은 경우에는 100% 정확하지 않을 수 있지만 메모리가 어디에서 흘러내리는지 파악하는 좋은 출발점입니다.

힙 번호:슬롯 및 페이지

다음으로 힙 번호라고도 하는 이러한 키에 대해 알아보겠습니다. :

{
    # page numbers
    :heap_allocated_pages=>49,
    :heap_sorted_length=>49,
    :heap_allocatable_pages=>0,
 
    # slots
    :heap_available_slots=>19975,
    :heap_live_slots=>19099,
    :heap_free_slots=>876,
    :heap_final_slots=>0,
    :heap_marked_slots=>16659,
 
    # Eden and Tomb pages
    :heap_eden_pages=>49,
    :heap_tomb_pages=>0,
}

여기서 말하는 힙은 C 데이터 구조입니다. 여기에는 현재 살아있는 모든 Ruby 객체에 대한 참조가 포함됩니다. 힙 페이지 메모리 슬롯으로 구성되며 각 슬롯에는 하나의 라이브 Ruby 개체에 대한 정보만 포함됩니다.

  • heap_allocated_pages 현재 할당된 힙 페이지의 수입니다. 이 페이지는 완전히 비어 있거나 완전히 채워지거나 부분적으로 채워질 수 있습니다.
  • heap_sorted_length 힙이 메모리에서 차지하는 실제 크기이며 heap_allocated_pages와 다릅니다. , 길이는 길이이므로 카운트가 아니라 합친 힙 페이지 수 . 처음에 10개의 페이지를 할당한 다음 세트 중간에서 하나를 해제한 경우 heap_allocated_pages 9이지만 heap_sorted_length 여전히 10입니다.
  • 마지막으로 heap_allocatable_pages 필요할 때 사용할 수 있는 현재 Ruby가 소유하고 있는 힙의 수입니다.

이제 슬롯으로 이동합니다.

  • heap_available_slots 힙 페이지에서 사용 가능한 총 슬롯 수입니다.
  • heap_live_slots 메모리에 있는 라이브 개체의 수입니다.
  • heap_free_slots 비어 있는 할당된 힙 페이지의 슬롯입니다.
  • heap_final_slots 개체에 종료자가 있는 슬롯의 수입니다. 그들에게 붙어 있습니다. 종료자는 객체가 해제될 때 실행되는 Procs이며 OOPS의 소멸자와 유사합니다.
  • heap_marked_slots 오래된 개체(즉, 3개 이상의 GC 주기 동안 존재한 개체) 및 쓰기 방지 보호되지 않은 개체의 수입니다.

그런 다음 tomb_pages가 있습니다. 및 eden_pages .

tomb_pages 라이브 개체가 포함되지 않은 페이지 수입니다. 이 페이지는 결국 Ruby에 의해 운영 체제로 다시 릴리스됩니다.

반면에 eden_pages 운영 체제로 다시 릴리스할 수 없도록 최소한 하나의 라이브 개체를 포함하는 페이지의 수입니다.

heap_free_slots 측정항목 모니터링 고려 애플리케이션에서 메모리 팽창 문제에 직면한 경우

많은 수의 여유 슬롯(250,000개 이상)은 일반적으로 한 번에 많은 개체를 할당한 다음 해제하는 소수의 컨트롤러 작업이 있음을 나타냅니다. 이렇게 하면 실행 중인 Ruby 프로세스의 크기가 영구적으로 커질 수 있습니다.

누적 숫자

{
    :total_allocated_pages=>49,
    :total_freed_pages=>0,
    :total_allocated_objects=>66358,
    :total_freed_objects=>47259,
}

이 숫자는 본질적으로 프로세스의 전체 수명 동안 누적되거나 추가됩니다. GC에 의해 재설정되지 않으며 기술적으로 다운될 수 없습니다. 이 네 가지 숫자는 모두 자명합니다.

가비지 수집 임계값

이 수치를 이해하려면 먼저 GC가 트리거되는 시점을 이해해야 합니다.

{
    :malloc_increase_bytes=>16216,
    :malloc_increase_bytes_limit=>16777216,
    :remembered_wb_unprotected_objects=>191,
    :remembered_wb_unprotected_objects_limit=>312,
    :old_objects=>16024,
    :old_objects_limit=>23556,
    :oldmalloc_increase_bytes=>158824,
    :oldmalloc_increase_bytes_limit=>16777216
}

GC 실행이 고정된 간격으로 발생한다는 일반적인 가정과 달리 GC 실행은 Ruby에서 메모리 공간이 부족해지기 시작할 때 트리거됩니다. Ruby에 free_slots가 부족하면 사소한 GC가 발생합니다. .

free_slots에서 Ruby가 여전히 부족한 경우 마이너 GC 실행 후 — 또는 oldmalloc, malloc, 이전 개체 수 또는 shady 임계값 /write-barrier-unprotected count 초과됨 — 주요 GC 실행이 트리거됩니다. gc.stat의 위 부분은 이러한 임계값의 값을 보여줍니다.

malloc_increase_bytes 외부에 할당된 메모리 양을 나타냅니다. 우리는 지금까지 이야기했습니다. 객체의 크기가 메모리 슬롯의 표준 크기(예:40바이트)를 초과하는 경우 — Ruby malloc 해당 개체를 위한 다른 어딘가에 공간이 있습니다. 총 추가 할당 공간이 malloc_increase_bytes_limit를 초과하는 경우 , 주요 GC가 트리거됩니다.

oldmalloc_increase_bytes 오래된 개체에 대한 유사한 임계값입니다. old_objects 오래된 것으로 표시된 개체 슬롯의 수입니다. 숫자가 old_objects_limit를 초과하는 경우 , 주요 GC가 트리거됩니다.

remembered_wb_unprotected_objects 쓰기 장벽으로 보호되지 않는 개체의 총 개수입니다. 기억된 세트의 일부입니다. .

쓰기 장벽은 Ruby 런타임과 객체 간의 인터페이스로, 인터프리터가 객체가 생성되는 즉시 객체에 대한 참조를 추적할 수 있도록 합니다.

C-extensions는 쓰기 장벽을 사용하지 않고 개체에 대한 새로운 참조를 만들 수 있으며, 이 경우 개체는 shady으로 표시됩니다. 또는 쓰기 장벽이 보호되지 않음 . 기억된 세트는 단순히 오래된 목록입니다. new에 대한 참조가 하나 이상 있는 개체 개체.

Ruby 가비지 컬렉션 성능 사용자 지정

이제 Ruby GC가 애플리케이션의 메모리를 관리하는 방법을 이해했으므로 GC 동작을 사용자 정의하는 데 사용할 수 있는 옵션을 살펴볼 차례입니다.

다음은 Ruby GC의 성능을 조정하고 애플리케이션의 성능을 개선하는 데 사용할 수 있는 환경 변수입니다.

RUBY_GC_HEAP_INIT_SLOTS
RUBY_GC_HEAP_FREE_SLOTS
RUBY_GC_HEAP_GROWTH_FACTOR
RUBY_GC_HEAP_GROWTH_MAX_SLOTS
RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR
and other variables

여기서 중요한 매개변수에 대해 하나씩 이야기해 보겠습니다.

  • RUBY_GC_HEAP_INIT_SLOTS :Ruby 힙의 초기 슬롯 수를 정의하며 기본적으로 10000으로 설정됩니다. 앱이 처음에 대부분의 개체를 할당한다고 확신하는 경우 이 매개변수를 변경할 수 있습니다.
  • RUBY_GC_HEAP_FREE_SLOTS :GC 주기 직후에 사용 가능해야 하는 최소 여유 슬롯 수를 제어합니다. 기본값은 4096입니다. 이 값은 첫 번째 힙 증가 동안 런타임에 한 번만 사용됩니다.
  • RUBY_GC_HEAP_GROWTH_FACTOR :Ruby 인터프리터에 사용 가능한 힙이 증가하는 요인입니다. 기본값은 1.8입니다. Ruby는 이미 힙 성장에 공격적이기 때문에 이것을 변경하는 것은 거의 의미가 없습니다. 최신 인터프리터에서 힙이 주문형으로 할당되기 때문에 줄이면 큰 차이가 없습니다.
  • RUBY_GC_HEAP_GROWTH_MAX_SLOTS :Ruby가 한 번에 힙 공간에 추가할 수 있는 최대 슬롯 수입니다. 기본값은 0이며 숫자에 제한이 없음을 나타냅니다. 앱이 수명 동안 수백만 개의 객체를 할당해야 하는 경우 이 매개변수에 제한을 두는 것이 좋습니다. 그러나 앱의 GC 시간에 미치는 영향은 매우 낮습니다.
  • RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR 메모리에 있는 이전 개체의 총 수 =이 수 x 마지막 GC 주기 이후 메모리에 있는 이전 개체 수보다 많을 때 인터프리터가 주요 GC 주기를 수행하도록 합니다. 많은 개체가 이전 세대로 들어간 후 사용되지 않을 것이라고 생각되면 이 수를 늘릴 수 있습니다. 그러나 이것은 거의 필요하지 않습니다.
  • RUBY_GC_MALLOC_LIMIT 새로운 세대에 대한 malloc 호출의 최소 한계입니다. 기본값은 16MB입니다. RUBY_GC_MALLOC_LIMIT_MAX 동일한 malloc 호출에 대한 최대 제한입니다. 기본값은 32MB입니다. 애플리케이션이 평균보다 높은 메모리를 사용하는 경우 이 두 제한을 늘릴 수 있습니다. 그러나 이것들을 너무 많이 올리지 않도록 주의하십시오. 그렇지 않으면 최대 메모리 소비가 증가할 수 있습니다. 항상 이러한 제한을 점진적으로 늘리십시오(예:4MB 또는 8MB).
  • RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR 새로운 세대에 대한 malloc 한계의 성장 인자입니다. 기본값은 1.4입니다. 앱이 한 번에 전체가 아닌 청크로 메모리를 할당하는 경우 이 수를 늘리는 것이 좋습니다.
  • 마찬가지로 RUBY_GC_OLDMALLOC_LIMITRUBY_GC_OLDMALLOC_LIMIT_MAX 구세대에 대한 최소 및 최대 malloc 제한입니다. 이 매개변수의 기본값은 16MB와 128MB입니다.
  • RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR 이 한계의 성장 인자입니다. 기본값은 1.2입니다. 최대 효과를 위해 새로운 세대 제한으로 변경하는 것을 고려할 수 있습니다.

Ruby에서 가비지 컬렉션 미세 조정

우리는 애플리케이션의 전반적인 성능을 향상시키는 데 도움이 되도록 GC 모듈을 사용자 지정하는 몇 가지 일반적이고 간단한 방법에 대해 논의했습니다. 그러나 이러한 조정이 모든 경우에 작동하지 않을 수 있습니다. 사용자 정의할 항목을 결정하기 전에 앱의 메모리 사용 패턴을 파악해야 합니다.

반대로 이러한 매개변수의 최상의 값을 찾는 자동화된 테스트를 실행하는 것을 고려할 수 있습니다. TuneMyGC와 같은 도구는 환경 변수에 대한 최상의 값 집합을 찾을 때 매우 간단합니다.

애플리케이션이 이상하게 작동하는 경우 GC 매개변수를 확실히 살펴보십시오. 여기에서 약간만 변경하면 앱의 메모리 소비를 줄이고 메모리 팽창을 방지할 수 있습니다.

이 기사가 Ruby Garbage Collection 모듈을 사용자 정의할 때 주의해야 할 사항에 대한 좋은 아이디어를 제공했기를 바랍니다. 소개에 대한 자세한 내용은 가비지 컬렉션 소개 파트 I 및 파트 II를 확인하세요.

즐거운 코딩하세요!