이 문서의 내용은 Python 목록(코드 포함)의 길이 조정 방법에 대한 것입니다. 특정 참조 값이 있으므로 도움이 필요한 친구에게 도움이 되기를 바랍니다.
파이썬의 리스트(list)는 매우 유연한 배열이고 길이를 마음대로 조절할 수 있습니다. 이러한 편리함 때문에 필요에 맞게 배열을 수정할 수밖에 없습니다. 삽입, 팝 등에 비해 추가 사용이 더 일반적입니다.
이렇게 쓰이는 것도 있어요:
>>> test = [] >>> test.append(1) >>> test.append({2}) >>> test.append([3]) >>> print test # 输出 [1, set([2]), [3]]
이렇게 쓰이는 것도 있어요:
test = [] for i in range(4): test.append(i) print test # 输出 [0, 1, 2, 3]
이렇게 사용하는 게 너무 행복하고 만족스럽습니다.
그러나 실제로 데이터 길이가 동적으로 수정될 수 있는 시나리오를 만날 때마다 우리는 즉시 대응해야 합니다. 즉, 메모리 관리 문제입니다.
운영 효율성과 편의성이 동시에 충족된다면 정말 좋은 소식이 될 것입니다.
그러나 하나님께서 당신을 위해 창문을 열어 주셨을 때 문도 닫으셨을 것입니다!
우리는 사전 할당에 대한 지식에 깊은 영향을 받았습니다. 또한 목록은 초기화 중에 특정 길이를 할당받는다고 생각합니다. 그렇지 않으면 매번 메모리를 적용하는 것이 훨씬 "낮습니다".
사실 목록은 정말 "낮습니다":
import sys test = [] test_1 = [1] print sys.getsizeof(test) print sys.getsizeof(test_1) - sys.getsizeof(test) # 输出 72 # 空列表内存大小,也是 list 对象的总大小 8 # 代表增加一个成员,list 增加的大小
우리의 추측으로는 목록이 정의된 후 지속적으로 메모리를 적용하지 않도록 데이터를 저장하기 위해 특정 크기의 풀이 사전 할당될 것입니다.
그러나 위의 실험에서는 멤버 목록의 길이가 빈 목록보다 8바이트만 길다는 것을 보여줍니다. 실제로 사전 할당된 풀이 있다면 사전 할당된 수만큼의 메모리 크기 내에 멤버를 추가하세요. 둘은 변함없이 유지되어야 합니다.
그러므로 이 목록에는 미리 할당된 메모리 풀이 없다고 추측할 수 있습니다. 여기에 진짜 망치가 필요합니다
PyObject * PyList_New(Py_ssize_t size) { PyListObject *op; size_t nbytes; if (size < 0) { PyErr_BadInternalCall(); return NULL; } /* Check for overflow without an actual overflow, * which can cause compiler to optimise out */ if ((size_t)size > PY_SIZE_MAX / sizeof(PyObject *)) return PyErr_NoMemory(); // list对象指针的缓存 if (numfree) { numfree--; op = free_list[numfree]; _Py_NewReference((PyObject *)op); } else { op = PyObject_GC_New(PyListObject, &PyList_Type); if (op == NULL) return NULL; } // list 成员的内存申请 nbytes = size * sizeof(PyObject *); if (size <= 0) op->ob_item = NULL; else { op->ob_item = (PyObject **) PyMem_MALLOC(nbytes); if (op->ob_item == NULL) { Py_DECREF(op); return PyErr_NoMemory(); } memset(op->ob_item, 0, nbytes); } Py_SIZE(op) = size; op->allocated = size; _PyObject_GC_TRACK(op); return (PyObject *) op; }
test = [1]을 실행할 때 실제로는 두 가지만 수행합니다.
멤버 수에 따라 해당 길이의 빈 목록을 구성합니다(위 코드)
A 그냥
어떤 아이들은 멤버를 채우는 단계에서 어떤 메커니즘이 작동하여 멤버를 더 크게 만들 수 있다고 생각할 수도 있습니다.
안타깝습니다. 초기화 방법이 PyList_SET_ITEM이므로 여기에는 트리거링 메커니즘이 없고 단지 배열 멤버의 간단한 할당일 뿐입니다.
#define PyList_SET_ITEM(op, i, v) (((PyListObject *)(op))->ob_item[i] = (v))
따라서 전체 목록의 초기화에는 실제로 사전 할당된 메모리 풀이 없습니다. , 요청에 따라 직접 적용하면 당근 하나하나가 정말 잔인합니다
가변 길이의 핵심
초기화 과정이 이렇다는 것은 이해가 되지만, 작동 중에도 여전히 이렇다면, 좀 불합리해요.
글 시작 부분에 추가를 사용하는 예에서 요소가 추가될 때마다 메모리가 적용된다면 목록의 수명이 의심스러울 정도로 비판을 받을 수 있으므로 여전히 자체 메모리 애플리케이션 루틴이 있습니다.
목록에서는 삽입, 팝 또는 추가 여부에 관계없이 list_resize를 만나게 됩니다. 이름에서 알 수 있듯이 이 함수는 목록 개체의 메모리 사용량을 조정하는 데 사용됩니다.
static int list_resize(PyListObject *self, Py_ssize_t newsize) { PyObject **items; size_t new_allocated; Py_ssize_t allocated = self->allocated; /* Bypass realloc() when a previous overallocation is large enough to accommodate the newsize. If the newsize falls lower than half the allocated size, then proceed with the realloc() to shrink the list. */ if (allocated >= newsize && newsize >= (allocated >> 1)) { assert(self->ob_item != NULL || newsize == 0); Py_SIZE(self) = newsize; return 0; } /* This over-allocates proportional to the list size, making room * for additional growth. The over-allocation is mild, but is * enough to give linear-time amortized behavior over a long * sequence of appends() in the presence of a poorly-performing * system realloc(). * The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ... */ # 确定新扩展之后的占坑数 new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6); /* check for integer overflow */ if (new_allocated > PY_SIZE_MAX - newsize) { PyErr_NoMemory(); return -1; } else { new_allocated += newsize; } if (newsize == 0) new_allocated = 0; # 申请内存 items = self->ob_item; if (new_allocated <= (PY_SIZE_MAX / sizeof(PyObject *))) PyMem_RESIZE(items, PyObject *, new_allocated); else items = NULL; if (items == NULL) { PyErr_NoMemory(); return -1; } self->ob_item = items; Py_SIZE(self) = newsize; self->allocated = new_allocated; return 0; }
위 코드에서는 newize와 new_allocation이라는 두 개의 명사가 자주 표시됩니다. 여기서는 newsize가 증가/감소된 수가 아니라 증가/감소한 후의 전체 구성원 수라는 점을 설명해야 합니다. 예:
a = [1, 2, 3] a.append(1)
위의 추가가 list_resize를 트리거하는 경우 newsize는 1이 아니라 3 + 1입니다. pop을 사용하여 목록 구성원을 줄이는 경우 감소된 총 수가 전달되기 때문입니다.
리스트의 구조 정의에는 길이에 대한 두 가지 정의, 즉 ob_size(실제 멤버 수)와 할당(총 멤버 수)이 있습니다
그 둘 사이의 관계는 다음과 같습니다.
0 <= ob_size <= allocated len(list) == ob_size
그래서 new_allocation이 좋다는 것을 이해했습니다. 새로운 총 구덩이 수입니다.
명사의 의미가 거의 이해되면 단서를 따라가면 list_resize 이후 목록의 크기가 어떻게 될지 알 수 있습니다.
이 방법은 실제로 위의 주석과 코드에서 매우 명확합니다. 다음은 간략한 요약입니다.
먼저 기준을 결정합니다: new_allocation = (newsize >> 3) + (newsize < 9 ? 3 : 6) ;
new_allocation + newsize가 PY_SIZE_MAX를 초과하는지 판단하세요. 초과하면 오류가 직접 보고됩니다.
마지막으로 새로운 총 피트 수를 결정합니다: new_allocation + newsize가 0이면 총 피트 수는 다음과 같습니다. 0 직접;
아래 시연:
#coding: utf8 import sys test = [] raw_size = sys.getsizeof(test) test.append(1) print "1 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size) test.append(1) print "2 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size) test.append(1) print "3 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size) test.append(1) print "4 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size) test.append(1) print "5 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size) test.append(1) print "6 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size)
# 输出结果 1 次 append 减去空列表的内存大小:32 2 次 append 减去空列表的内存大小:32 3 次 append 减去空列表的内存大小:32 4 次 append 减去空列表的内存大小:32 5 次 append 减去空列表的内存大小:64 6 次 append 减去空列表的内存大小:64
간단한 대체 방법으로 시작하여 단계별 계산:
그 중:
new_allocation = (newsize >> 3) + (newsize < 9 ? 3 : 6 ) + newsize (아래 newsize > 0 때문)
원본 할당 >= newsize 및 newsize >= 원본 할당 / 2인 경우 할당 변경 및 메모리 적용 없이 직접 반환됩니다
第 n 次 append | 列表原长度 | 新增成员数 | 原 allocated | newsize | new_allocated |
---|---|---|---|---|---|
1 | 0 | 1 | 0 | 0 + 1 = 1 | 3 + 1 = 4 |
2 | 1 | 1 | 4 | 1 + 1 = 2 | 无需改变 |
3 | 2 | 1 | 4 | 2 + 1 = 3 | 无需改变 |
4 | 3 | 1 | 4 | 3 + 1 = 4 | 无需改变 |
5 | 4 | 1 | 4 | 4 + 1 = 5 | 3 + 5 = 8 |
6 | 5 | 1 | 8 | 5 + 1 = 6 | 无需改变 |
通过上面的表格,应该比较清楚看到什么时候会触发改变 allocated,并且当触发时它们是如何计算的。为什么我们需要这样关注 allocated?理由很简单,因为这个值决定了整个 list 的动态内存的占用大小;
扩容是这样,缩容也是照猫画虎。反正都是算出新的 allocated, 然后由 PyMem_RESIZE 来处理。
综上所述,在一些明确列表成员或者简单处理再塞入列表的情况下,我们不应该再用下面的方式:
test = [] for i in range(4): test.append(i) print test
而是应该用更加 pythonic 和 更加高效的列表推导式:test = [i for i in range(4)]。
위 내용은 Python 목록 길이 조정 방법(코드 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!