Ruby 내부를 간단히 둘러보고 싶으신가요?
그럼 당신은 대접을받을 것입니다.
때문에 …
Ruby 객체가 메모리에 배치되는 방식과 내부 데이터 구조를 조작하여 멋진 작업을 수행하는 방법을 함께 탐구할 것입니다.
안전벨트를 매고 Ruby 인터프리터의 세계로 여행을 떠날 준비를 하세요!
배열의 메모리 레이아웃
배열을 생성할 때 Ruby는 약간의 시스템 메모리와 약간의 메타데이터로 이를 백업해야 합니다.
메타데이터 포함 :
- 배열 크기(항목 수)
- 어레이 용량
- 수업
- 객체 상태(고정 여부)
- 메모리에서 데이터가 저장된 위치에 대한 포인터
기본 Ruby 인터프리터(MRI)는 C로 작성되어 있으므로 개체가 없습니다.
하지만 다른 것이 있습니다. 구조체 .
C의 구조체는 관련 데이터를 함께 저장하는 데 도움이 되며, 이는 Array
와 같은 것을 나타내기 위해 MRI의 소스 코드에서 많이 사용됩니다. , String
의 및 기타 종류의 개체입니다.
이러한 구조체 중 하나를 보고 개체의 메모리 레이아웃을 유추할 수 있습니다.
Array
의 구조체를 살펴보겠습니다. , RArray
라고 함 :
struct RArray { struct RBasic basic; union { struct { long len; union { long capa; VALUE shared; } aux; const VALUE *ptr; } heap; const VALUE ary[RARRAY_EMBED_LEN_MAX]; } as; };
C에 익숙하지 않다면 이것이 다소 위협적으로 보일 수 있다는 것을 알고 있지만 걱정하지 마십시오! 나는 이것을 소화하기 쉬운 부분으로 분해하는 데 도움을 줄 것입니다 🙂
우리가 가지고 있는 첫 번째 것은 이 RBasic
입니다. 구조체이기도 한 것:
struct RBasic { VALUE flags; VALUE klass; }
이것은 대부분의 Ruby 객체가 가지고 있으며 이 객체에 대한 클래스와 이 객체가 고정되었는지 여부(및 '오염된' 속성과 같은 기타 사항)를 알려주는 일부 바이너리 플래그와 같은 몇 가지를 포함합니다.
즉 :
RBasic
개체에 대한 일반 메타데이터를 포함합니다.
그 후에 배열의 길이를 포함하는 또 다른 구조체가 있습니다(len
).
통합 표현식은 aux
capa
일 수 있습니다. (용량용) 또는 shared
. 이것은 대부분 최적화에 관한 것이며 Pat Shaughnessy의 이 훌륭한 게시물에서 더 자세히 설명합니다. 메모리 할당 측면에서 컴파일러는 유니온 내에서 가장 큰 유형을 사용합니다.
그런 다음 ptr
이 있습니다. , 실제 Array
가 있는 메모리 주소를 포함합니다. 데이터가 저장됩니다.
다음은 이것이 어떻게 보이는지 그림입니다(모든 흰색/회색 상자는 32비트 시스템에서 4바이트 ):
ObjectSpace 모듈을 사용하여 개체의 메모리 크기를 볼 수 있습니다.
require 'objspace' ObjectSpace.memsize_of([]) # 20
이제 즐거운 시간을 보낼 준비가 되었습니다!
Fiddle:재미있는 실험
RBasic은 32비트 시스템에서 정확히 8바이트이고 64비트 시스템에서 16바이트입니다. 이것을 알면 Fiddle 모듈을 사용하여 개체의 원시 메모리 바이트에 액세스하고 재미있는 실험을 위해 변경할 수 있습니다.
예를 들어 :
단일 비트를 토글하여 고정 상태를 변경할 수 있습니다.
이것은 본질적으로 freeze 방법이 하는 일이지만 어떻게 unfreeze 방법이 없는지 확인하십시오.
재미로 구현해 봅시다!
먼저 Fiddle
모듈(Ruby 표준 라이브러리의 일부) 및 고정 문자열을 만듭니다.
require 'fiddle' str = 'water'.freeze str.frozen? # true
다음:
우리는 문자열에 대한 메모리 주소가 필요합니다. 이것은 다음과 같이 얻을 수 있습니다.
memory_address = str.object_id * 2
마지막으로:
Ruby가 객체가 고정되었는지 확인하기 위해 검사하는 정확한 비트를 뒤집습니다. 또한 frozen?
을 호출하여 이것이 작동하는지 확인합니다. 방법.
Fiddle::Pointer.new(memory_address)[1] ^= 8 str.frozen? # false
인덱스 [1]
플래그의 두 번째 바이트를 나타냅니다. 값(총 4바이트로 구성됨).
그런 다음 ^=
를 사용합니다. 이것은 해당 비트를 뒤집는 "XOR"(배타적 OR) 연산자입니다.
플래그 내부의 비트가 다르기 때문에 이렇게 하는 것입니다. 의미가 다르며 관련 없는 것을 변경하고 싶지 않습니다.
내 루비 트릭 게시물을 읽었다면 전에 이것을 보았을 수도 있지만 이제 어떻게 작동하는지 알 것입니다 🙂
시도할 수 있는 또 다른 방법은 배열의 길이를 변경하고 배열을 인쇄하는 것입니다.
어레이가 어떻게 짧아지는지 알 수 있습니다!
클래스를 변경하여 Array
를 만들 수도 있습니다. String
이라고 생각하세요. …
결론
Ruby가 내부적으로 어떻게 작동하는지에 대해 조금 배웠습니다. Ruby 객체의 메모리 배치 방법 및 Fiddle
사용 방법 가지고 놀 수 있는 모듈입니다.
Fiddle
을 사용하면 안 됩니다. 실제 앱에서 이와 같지만 실험해 보는 것은 재미있습니다.
이 게시물을 공유하는 것을 잊지 마세요. 더 많은 분들이 보실 수 있도록 🙂