본문 바로가기

app/python

Python integer objects implementation

해당 글은 http://www.laurentluce.com/posts/python-integer-objects-implementation/ 의 글을 번역한 것입니다. 

해당 글의 소스는 python2를 기준으로 작성되었으며 python버전에 따라 다를 수 있습니다. 

 

이 글은 파이썬의 integer가  내부적으로 어떻게 관리 되는지 설명합니다. 

 

파이썬의 Integer 오브젝트는 PyIntObject 구조로 표현됩니다. 그 값은 long 타입으로 구현되어 있습니다. 

(Python3에서는 PyLongObject 객체로 integer는 long으로 대체되었습니다.)

 

/* python2  */

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

 

 

새로운 정수 객체가 필요할 때마다 새로운 정수 객체가 할당되는 것을 피하기 위해 파이썬은 사용되지 않은 정수 객체 블록을 미리 할당합니다.

 

PyIntObjects는 Python에서 정수 객체를 할당하는 데 사용됩니다. 이 구조가 초기화되면 새로운 정수 값이 파이썬 스크립트의 객체에 할당되며 정수 객체를 사용할 수 있습니다. 이 구조를 "PyIntBlock"이라고하며 다음과 같이 정의됩니다.

 

struct _intblock {
    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;

 

 

Integer 객체 블록이 파이썬에 할당 될 때, 객체에는 아직 할당된 값이 없습니다. 우리는 그것들을 free integer objects 라고 부릅니다. 프로그램에서 새 정수 값을 사용하면 다음 사용 가능한 객체에 값이 할당됩니다. 빈 정수 객체 값이 설정되어 있으면 새로운 메모리 할당이 필요하지 않으므로 속도가 빠릅니다.

 

블록 내부의 integer objects는 ob_type이라는 내부 포인터를 사용하여 서로 다시 연결됩니다. 

integer block에는 64비트 시스템에서 약 40개의 PyIntObject 객체이며, 1K 바이트의 블록에 들어갈 수 있는 정수 개체의 수가 포함됩니다. block 내부의 모든 정수 객체가 사용 되면 새로운 블록에 새로운 정수 객체 목록이 할당됩니다.

단일 링크리스트는 할당된 integer block을 추적하는 데 사용되며 block_list 라고 부릅니다.

 

 

해당 구조는 작은 정수를 참조하고 공유하므로 액세스가 빠릅니다. 정수 객체에 대한 262개의 포인터 배열을 가집니다. 정수 범위는 -5에서 256까지입니다. Python은 해당 범위의 정수를 사용면 많은 시간 절약할 수 있습니다. 

 

#define NSMALLPOSINTS           257
#define NSMALLNEGINTS           5

static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

static PyObject *
get_small_int(sdigit ival)
{
    PyObject *v;
    assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
    v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
    Py_INCREF(v);

#ifdef COUNT_ALLOCS
    if (ival >= 0)
        quick_int_allocs++;
    else
        quick_neg_int_allocs++;
#endif
    return v;

}

 

 

정수 -5를 나타내는 integer object는 작은 정수 배열 내부의 오프셋 0, -4를 나타내는 integer object는 오프셋 1에 있습니다.

Python 스크립트에서 정수를 정의하면 어떻게 될까요?

 

>>> a=1
>>> a
1

 

첫 번째 행을 실행하면 PyInt_FromLong 함수가 호출되고 해당 논리는 다음과 같습니다.

 

if integer value in range -5,256:
    return the integer object pointed by the small integers array at the offset (value + 5).
else:
    if no free integer object available:
        allocate new block of integer objects
    set value of the next free integer object in the current block of integers.
    return integer object

 

예를 들어 integer 1은 small ints 배열의 오프셋 1 + 5 = 6을 가리 킵니다. 이 integer object에 대한 포인터가 반환되고 변수 "a"는 해당 integer object를 가리킵니다.

 

 

다른 예를 봅시다

 

>>> a=300
>>> a
300

 

300은 small_ints 배열의 범위에 있지 않으므로 integer object 값은 300으로 할당됩니다. 

 

 

 

python 2.6 소스 코드에서 intobject.c 파일을 살펴보면 덧셈, 곱셈, 변환 등의 연산을 처리하는 긴 함수 목록을 볼 수 있습니다. 비교 함수는 다음과 같습니다.

(python 3.x 버전은 longobject.c 를 보시면 됩니다. )

 

/* python 2.x */

static int
int_compare(PyIntObject *v, PyIntObject *w)
{
    register long i = v->ob_ival;
    register long j = w->ob_ival;
    return (i < j) ? -1 : (i > j) ? 1 : 0;
}


/* python 3.x */

static int
long_compare(PyLongObject *a, PyLongObject *b)
{
    Py_ssize_t sign;

    if (Py_SIZE(a) != Py_SIZE(b)) {
        sign = Py_SIZE(a) - Py_SIZE(b);
    }
    else {
        Py_ssize_t i = Py_ABS(Py_SIZE(a));
        while (--i >= 0 && a->ob_digit[i] == b->ob_digit[i]);
        if (i < 0)
            sign = 0;
        else {
            sign = (sdigit)a->ob_digit[i] - (sdigit)b->ob_digit[i];
            if (Py_SIZE(a) < 0)
                sign = -sign;
        }
    }
    return sign < 0 ? -1 : sign > 0 ? 1 : 0;
}

 

Integer object의 값은 long 유형 인 ob_ival 속성에 저장됩니다. 액세스를 최적화하기 위해 각 값이 레지스터에 배치되고 이 두 레지스터간에 비교가 수행됩니다. v가 가리키는 integer object가 w가 가리키는 integer object보다 작은 경우 -1이 반환됩니다. 반대의 경우 1이 리턴되고 동일하면 0이 리턴됩니다.