๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ

๐Ÿค”๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋Š” ์–ด๋””์— ์‚ฌ์šฉ๋˜๋Š” ๊ฑธ๊นŒ

Chanho Lee
๋‹ค์–‘ํ•œ ๋ถ„์•ผ๋ฅผ ๊ณต๋ถ€ํ•˜๊ณ  ์žˆ๋Š” ํ•™์ƒ

Introductionโ€‹

์ž๋ฃŒ๊ตฌ์กฐ์™€ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ณต๋ถ€ํ•˜๋‹ค ๋ณด๋ฉด ํ•œ ๋ฒˆ์ฏค์€ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ(Linked List)๋ฅผ ๋ฐฐ์šด๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ง€๊ธˆ๊นŒ์ง€ ๋ง‰์ƒ ๊ฐœ๋ฐœ์„ ํ•˜๊ฑฐ๋‚˜ ์‹ค์ œ ๋ฌธ์ œ๋ฅผ ํ’€ ๋•Œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์„œ ์‚ฌ์šฉํ•œ ๊ฒฝํ—˜์€ ๊ฑฐ์˜ ์—†๋‹ค. ๋‚˜ ์—ญ์‹œ ์˜ˆ์ „์— ๊ธฐ๋ณธ ๊ฐœ๋…์„ ๊ณต๋ถ€ํ•œ ๋’ค๋กœ๋Š” ์‹ค์งˆ์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๊ฐ€ ๊ฑฐ์˜ ์—†์—ˆ๊ณ , ์ตœ๊ทผ ์ฝ”๋”ฉ ํ…Œ์ŠคํŠธ ์ค€๋น„๋ฅผ ํ•˜๋ฉด์„œ๋„ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฌธ์ œ๋Š” ๊ฑฐ์˜ ๋งŒ๋‚˜์ง€ ๋ชปํ–ˆ๋‹ค. ์žˆ์–ด๋„ ๋Œ€๋ถ€๋ถ„์€ ํŒŒ์ด์ฌ์˜ deque๋กœ ํ•ด๊ฒฐํ–ˆ๊ณ , โ€œํŒŒ์ด์ฌ์—์„œ๋Š” ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋น„ํšจ์œจ์ ์ด๋‹คโ€๋ผ๋Š” ์ •๋„์˜ ๋‹จํŽธ์ ์ธ ์ง€์‹๋งŒ ๊ฐ–๊ณ  ๋„˜์–ด๊ฐ€๊ณ  ์žˆ์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ด๋ฒˆ์— ๋‹ค์‹œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ณต๋ถ€ํ•˜๋ฉด์„œ, "์™œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ž˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฑธ๊นŒ?โ€๋ผ๋Š” ๊ทผ๋ณธ์ ์ธ ์งˆ๋ฌธ์ด ์ƒ๊ฒผ๋‹ค. ๋‹จ์ˆœํžˆ โ€œํŒŒ์ด์ฌ์—์„œ ๋น„ํšจ์œจ์ ์ด๋‹ˆ๊นŒโ€๋ผ๊ณ  ์น˜๋ถ€ํ•˜๊ธฐ์—” ๋ญ”๊ฐ€ ๋ถ€์กฑํ–ˆ๊ณ , ์‹ค์ œ๋กœ ์–ด๋–ค ์ด์œ ๋“ค์ด ์กด์žฌํ•˜๋Š”์ง€ ์ •๋ฆฌํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋‹ค. ์ด๋ฒˆ ๊ธ€์€ ๊ทธ ๊ณผ์ •์—์„œ ์กฐ์‚ฌํ•˜๊ณ  ์ƒ๊ฐํ•œ ๋‚ด์šฉ์„ ์ •๋ฆฌํ•œ ๊ฒƒ์ด๋‹ค.

ํŒŒ์ด์ฌ์—์„œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ž˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์€ ์ด์œ โ€‹

์ฃผ์˜

์ ˆ๋Œ€ ์ œ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ์ผ€์ด์Šค๋ฅผ ๋ณด์ง€ ๋ชปํ•ด์„œ '์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ด์œ ' -> '์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™์€ ์ด์œ '๋กœ ๊ณ ์นœ ๊ฒŒ ์•„๋‹™๋‹ˆ๋‹ค.

์ •๋ณด

ํ•œ๊ธ€ ์ฃผ์„์„ ์ œ์™ธํ•œ ์˜๋ฌธ ์ฃผ์„์€ ์›๋ž˜ ์ฝ”๋“œ์— ์žˆ๋Š” ์ฃผ์„์ž…๋‹ˆ๋‹ค.

๋™์  ๋ฐฐ์—ด (Dynamic Array)โ€‹

ํŒŒ์ด์ฌ์—์„œ๋Š” ์‚ฌ์‹ค ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ(Linked List)๋ฅผ ์ž˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋ฐ ๊ทธ ์ด์œ ๋Š” ํŒŒ์ด์ฌ์˜ ๋‚ด์žฅย list๊ฐ€ ์‚ฌ์‹ค์ƒ ๊ฐ€๋ณ€ ๊ธธ์ด ๋ฐฐ์—ด(variableโ€‘length array)๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์–ด ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์žฅ์ ์˜ ์ƒ๋‹น ๋ถ€๋ถ„์„ ์ด๋ฏธ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. CPythonย FAQ์—์„œ๋Š” ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋‹ค๋ฅธ ๊ฐ์ฒด์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ๋ฅผ ๋‹ด๋Š” ์—ฐ์†๋œ ๋ฐฐ์—ด๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ๊ณ , ๋ฆฌ์ŠคํŠธ ํ—ค๋”๊ฐ€ ์ด ๋ฐฐ์—ด๊ณผ ๋ฐฐ์—ด ๊ธธ์ด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ํฌ์ธํ„ฐ๋ฅผ ํฌํ•จํ•œ๋‹ค๊ณ  ์„ค๋ช…ํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ๊ตฌ์กฐ ๋•๋ถ„์— ์ธ๋ฑ์Šค๋กœ ์š”์†Œ์— ์ ‘๊ทผํ•˜๋Š” a[i] ์—ฐ์‚ฐ์€ ๋ฆฌ์ŠคํŠธ ํฌ๊ธฐ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ํ•ญ์ƒย O(1)์ด๋‹ค.

๋ฐฐ์—ด

  • ์ธ๋ฑ์Šค ์ ‘๊ทผ O(1)O(1)
  • ์—ฐ์†๋œ ๋ฉ”๋ชจ๋ฆฌ
  • ์‚ฝ์ž… ๋ฐ ์‚ญ์ œ๊ฐ€ ์ค‘๊ฐ„์—์„œ ์ง„ํ–‰ํ•  ๊ฒฝ์šฐ O(n)O(n)
  • ๋์— ์ถ”๊ฐ€๋Š” amortized O(1)O(1)

๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ

  • ์ธ๋ฑ์Šค ์ ‘๊ทผ O(n)O(n)
  • ๋ฉ”๋ชจ๋ฆฌ ํฉ์–ด์ ธ ์žˆ์Œ
  • ์‚ฝ์ž… ๋ฐ ์‚ญ์ œ๊ฐ€ ๋…ธ๋“œ๋ฅผ ์•Œ๊ณ  ์žˆ๋‹ค๋Š” ๊ฐ€์ • ํ•˜์— O(1)O(1)
  • ์‚ฝ์ž…ํ•  ์œ„์น˜๋ฅผ ์ฐพ์„ ๋•Œ O(n)O(n)

์‹ค์ œ๋กœ C์˜ array์™€ ๊ฑฐ์˜ ์œ ์‚ฌํ•˜๋ฉฐ, ๊ตฌ์กฐ์ ์œผ๋กœ C++์˜ std::vector์™€ ๋น„์Šทํ•˜๋‹ค.

์•„๋ž˜๋Š” cpython/Include/cpython/listobject.h ์ฝ”๋“œ์ด๋‹ค. ์—ฌ๊ธฐ์„œ ๋ด์•ผํ•  ๊ฑด PyObject **ob_item์ด๋‹ค. ์ด ๋ฐฐ์—ด์— ํŒŒ์ด์ฌ ๊ฐ์ฒด์˜ ํฌ์ธํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ๊ตฌ์กฐ๋กœ ๋˜์–ด ์žˆ๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, vector<int>์ฒ˜๋Ÿผ ์—ฐ์†์ ์œผ๋กœ ์ €์žฅ๋˜๋Š” ๋ฐฐ์—ด์ด๋‹ค.

typedef struct {
PyObject_VAR_HEAD
/* Vector of pointers to list elements. list[0] is ob_item[0], etc. */
PyObject **ob_item;

/* ob_item contains space for 'allocated' elements. The number
* currently in use is ob_size.
* Invariants:
* 0 <= ob_size <= allocated
* len(list) == ob_size
* ob_item == NULL implies ob_size == allocated == 0
* list.sort() temporarily sets allocated to -1 to detect mutations.
*
* Items must normally not be NULL, except during construction when
* the list is not yet visible outside the function that builds it.
*/
Py_ssize_t allocated;
} PyListObject;

์—ฌ๊ธฐ์„œ ob_item ๋ฐฐ์—ด์€ C์˜ malloc์„ ์“ด๋‹ค. append()ย ์—ฐ์‚ฐ ์‹œ ๋ฐฐ์—ด์ด ๊ฝ‰ ์ฐจ๋ฉด ํฌ๊ธฐ๋ฅผ ๋Š˜๋ฆฌ๊ธฐ ์œ„ํ•ด ์žฌํ• ๋‹น์ด ๋ฐœ์ƒํ•˜๋Š”๋ฐ, CPython์€ ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ์ถ”๊ฐ€ ์ž‘์—…์„ ํšจ์œจ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ์—ฌ์œ  ๊ณต๊ฐ„์„ ๋ฏธ๋ฆฌ ํ™•์žฅํ•ด ๋‘๋Š” ์ „๋žต์„ ์‚ฌ์šฉํ•œ๋‹ค.

  • cpython/Objects/lisobject.c
...
items = PyMem_New(PyObject*, size);
...
  • cpython/include/pymem.h
#define PyMem_New(type, n) \
( ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? NULL : \
( (type *) PyMem_Malloc((n) * sizeof(type)) ) )
  • cpython/Objects/obmalloc.c
void *
PyMem_Malloc(size_t size)
{
/* see PyMem_RawMalloc() */
if (size > (size_t)PY_SSIZE_T_MAX)
return NULL;
OBJECT_STAT_INC_COND(allocations512, size < 512);
OBJECT_STAT_INC_COND(allocations4k, size >= 512 && size < 4094);
OBJECT_STAT_INC_COND(allocations_big, size >= 4094);
OBJECT_STAT_INC(allocations);
return _PyMem.malloc(_PyMem.ctx, size);
}

์บ์‹œ ์ง€์—ญ์„ฑ(Cache Locality)โ€‹

๋œฌ๊ธˆ์—†์ด ์บ์‹œ ์ง€์—ญ์„ฑ์€ ๋™์  ๋ฐฐ์—ด์— ์ด์–ด ์„ค๋ช…ํ•˜๊ธฐ ์ข‹๋‹ค๊ณ  ์ƒ๊ฐํ•˜์—ฌ ๋„ฃ๊ฒŒ ๋๋‹ค.

์ด ๋ฌธ์ œ๋Š” ํŒŒ์ด์ฌ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ C++์—์„œ๋„ ๋™์ผํ•˜๋‹ค. ์ด๋ก ์ ์œผ๋กœ๋Š” ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ์˜ ์‚ฝ์ž… ๋ฐ ์‚ญ์ œ๊ฐ€ O(1)O(1)์ด์ง€๋งŒ, ์‹ค์ œ ์„ฑ๋Šฅ์€ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ ํŒจํ„ด์— ํฌ๊ฒŒ ์˜ํ–ฅ์„ ๋ฐ›๋Š”๋‹ค.

CPU๋Š” ๋ฉ”์ธ ๋ฉ”๋ชจ๋ฆฌ(RAM)์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ๋งค์šฐ ๋А๋ฆฐ๋ฐ, ๊ทธ๋ž˜์„œ CPU ๋‚ด๋ถ€์— ์บ์‹œ(Cache)๋ผ๋Š” ์ž‘์ง€๋งŒ ๋งค์šฐ ๋น ๋ฅธ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ํ˜„๋Œ€ CPU๋Š” L1, L2, L3 ์บ์‹œ ๊ฐ™์€ ๋ฉ”๋ชจ๋ฆฌ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ง€๋Š”๋ฐ, ๋Œ€๋žต์ ์œผ๋กœ L1 ์บ์‹œ ์ ‘๊ทผ์€ ์•ฝ 1-2ns, ๋ฉ”์ธ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ์€ ์•ฝ 60-100ns๊ฐ€ ๊ฑธ๋ฆฐ๋‹ค. CPU ์•„ํ‚คํ…์ฒ˜๋‚˜ ์„ธ๋Œ€์— ๋”ฐ๋ผ ๋‹ค๋ฅด์ง€๋งŒ, ์บ์‹œ์™€ ๋ฉ”์ธ ๋ฉ”๋ชจ๋ฆฌ ๊ฐ„์—๋Š” ์ˆ˜์‹ญ ๋ฐฐ์—์„œ 100๋ฐฐ ๊ฐ€๊นŒ์šด ์†๋„ ์ฐจ์ด๊ฐ€ ๋‚œ๋‹ค.

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฑด CPU๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ X๋ฅผ ์ฝ์œผ๋ฉด ์ฃผ๋ณ€ ๋ฉ”๋ชจ๋ฆฌ(X+1, X+2, ...)๋„ ํ•จ๊ป˜ ์บ์‹œ๋กœ ๊ฐ€์ ธ์˜จ๋‹ค๋Š” ์ ์ด๋‹ค. ๋ฐฐ์—ด์ฒ˜๋Ÿผ ์—ฐ์†๋œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์Œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ด๋ฏธ ์บ์‹œ์— ์žˆ์–ด์„œ ๋น ๋ฅด๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๊ฑธ ์บ์‹œ ํžˆํŠธ(cache hit)๋ผ๊ณ  ํ•œ๋‹ค. ๋ฐ˜๋ฉด ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ์ฒ˜๋Ÿผ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํฉ์–ด์ ธ ์žˆ์œผ๋ฉด ๋งค๋ฒˆ ๋ฉ”์ธ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ๊ฐ€์ ธ์™€์•ผ ํ•ด์„œ ๋А๋ฆฌ๋‹ค. ์ด๊ฑธ ์บ์‹œ ๋ฏธ์Šค(cache miss)๋ผ๊ณ  ํ•œ๋‹ค.

๋ฐฐ์—ด์˜ ๊ฒฝ์šฐ๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค. ํฌ์ธํ„ฐ๋“ค์ด ์—ฐ์†๋œ ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋˜์–ด ์žˆ์–ด์„œ CPU๊ฐ€ ํ•œ ๋ฒˆ์— ์—ฌ๋Ÿฌ ์š”์†Œ๋ฅผ ์บ์‹œ๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค(prefetching). ์ˆœ์ฐจ ์ ‘๊ทผ ์‹œ ์บ์‹œ ํžˆํŠธ์œจ์ด ๋งค์šฐ ๋†’๋‹ค.

๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ: 1000   1008   1016   1024
[ptr0] [ptr1] [ptr2] [ptr3] <- ์—ฐ์†๋œ ๋ฉ”๋ชจ๋ฆฌ

๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋Š” ๋‹ค๋ฅด๋‹ค. ๊ฐ ๋…ธ๋“œ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ๊ณณ๊ณณ์— ํฉ์–ด์ ธ ์žˆ์–ด์„œ ๋‹ค์Œ ๋…ธ๋“œ์— ์ ‘๊ทผํ•  ๋•Œ๋งˆ๋‹ค ์บ์‹œ ๋ฏธ์Šค๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค. ํฌ์ธํ„ฐ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋Š” ๊ณผ์ •์—์„œ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ ์ง€์—ฐ์ด ๋ฐœ์ƒํ•œ๋‹ค.

๋ฉ”๋ชจ๋ฆฌ ์ฃผ์†Œ: 1000        5432        2048        7890
Node(1) --> Node(2) --> Node(3) --> Node(4)

์ •๋ฆฌํ•˜๋ฉด ๋ฐฐ์—ด์€ ์ˆœ์ฐจ ์ ‘๊ทผ ์‹œ ๋Œ€๋ถ€๋ถ„ ์บ์‹œ ํžˆํŠธ(~1ns)๊ฐ€ ๋ฐœ์ƒํ•˜์ง€๋งŒ, ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋Š” ๋…ธ๋“œ๋งˆ๋‹ค ์บ์‹œ ๋ฏธ์Šค(~100ns)๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค. ์ฆ‰, ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋Š” ์ด๋ก ์ ์œผ๋กœ O(1)O(1) ์—ฐ์‚ฐ์ด์–ด๋„ ์‹ค์ œ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ ์‹œ๊ฐ„ ๋•Œ๋ฌธ์— ๋ฐฐ์—ด๋ณด๋‹ค ํ›จ์”ฌ ๋А๋ฆด ์ˆ˜ ์žˆ๋‹ค. ์ด๊ฒƒ์ด ํŒŒ์ด์ฌ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ C++์—์„œ๋„ std::vector๋ฅผ std::list๋ณด๋‹ค ์„ ํ˜ธํ•˜๋Š” ์ด์œ ๋‹ค.

PyObject์™€ ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋ฒ„ํ—ค๋“œโ€‹

์œ„์—์„œ๋„ ๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ํŒŒ์ด์ฌ์—์„œ ๋ชจ๋“  ๊ฐ’์€ PyObject ๊ตฌ์กฐ์ฒด๋กœ ํ‘œํ˜„๋œ๋‹ค. ์•„๋ž˜๋Š” cpython/Include/object.h์˜ PyObject ๊ตฌ์กฐ์ฒด ์ •์˜๋‹ค.

// PyObject ๊ธฐ๋ณธ ๊ตฌ์กฐ์ฒด
struct _object {
_Py_ANONYMOUS union {
#if SIZEOF_VOID_P > 4 // 64 bit ์‹œ์Šคํ…œ
PY_INT64_T ob_refcnt_full; /* This field is needed for efficient initialization with Clang on ARM */ // 8 ๋ฐ”์ดํŠธ
struct {
# if PY_BIG_ENDIAN
uint16_t ob_flags; // 2 ๋ฐ”์ดํŠธ
uint16_t ob_overflow; // 2 ๋ฐ”์ดํŠธ
uint32_t ob_refcnt; // 4 ๋ฐ”์ดํŠธ, Reference count(์ฐธ์กฐ ์นด์šดํ„ฐ)
# else
uint32_t ob_refcnt;
uint16_t ob_overflow;
uint16_t ob_flags;
# endif
}; // union์˜ ํฌ๊ธฐ๋Š” ๊ฐ€์žฅ ํฐ ๋ฉค๋ฒ„ ํฌ๊ธฐ๋‹ค -> 8 ๋ฐ”์ดํŠธ
#else
Py_ssize_t ob_refcnt;
#endif
_Py_ALIGNED_DEF(_PyObject_MIN_ALIGNMENT, char) _aligner;
};
PyTypeObject *ob_type; // 8 ๋ฐ”์ดํŠธ Type pointer(ํƒ€์ž… ์ •๋ณด ํฌ์ธํ„ฐ)
};

ํŒŒ์ด์ฌ์˜ ๋ชจ๋“  ๊ฐ์ฒด๋Š” PyObject ๊ตฌ์กฐ์ฒด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฉฐ, ์ตœ์†Œํ•œ ๋‹ค์Œ ์ •๋ณด๋ฅผ ํฌํ•จํ•œ๋‹ค.

  • Reference count (์ฐธ์กฐ ์นด์šดํ„ฐ): 8๋ฐ”์ดํŠธ (union ์ „์ฒด ํฌ๊ธฐ)
  • Type pointer (ํƒ€์ž… ์ •๋ณด): 8๋ฐ”์ดํŠธ (64bit ์‹œ์Šคํ…œ)
  • ์ด 16๋ฐ”์ดํŠธ

C++์˜ Node vs ํŒŒ์ด์ฌ์˜ Node ๋น„๊ต

// C++์˜ Node ๊ตฌ์กฐ์ฒด
struct Node {
int val; // 4๋ฐ”์ดํŠธ
// ํŒจ๋”ฉ: 4๋ฐ”์ดํŠธ (64bit ์‹œ์Šคํ…œ์—์„œ 8๋ฐ”์ดํŠธ ์ •๋ ฌ์„ ์œ„ํ•ด)
Node* next; // 8๋ฐ”์ดํŠธ (64bit ํฌ์ธํ„ฐ)
Node(int v) : val(v), next(nullptr) {} // ์ƒ์„ฑ์ž๋Š” ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์•„๋‹Œ ์ฝ”๋“œ ์„ธ๊ทธ๋จผํŠธ์— ์ €์žฅ๋œ๋‹ค.
};

Node node(1); // ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
// ์ด 16๋ฐ”์ดํŠธ
# Python์€?
class Node:
def __init__(self, val): # ์ƒ์„ฑ์ž๋Š” ํด๋ž˜์Šค์— ์ €์žฅ๋œ๋‹ค.
self.val = val
self.next = None

node = Node(1)

์ด๊ฒƒ์˜ ๊ฒฐ๊ณผ๋ฅผ ์•Œ๊ธฐ ์œ„ํ•ด์„œ๋Š” 'ํŒŒ์ด์ฌ์€ ๊ฐ์ฒด์˜ ์†์„ฑ(attribute)์„ ์–ด๋–ป๊ฒŒ ์ €์žฅํ•˜๋Š”๊ฐ€?' ๋ฅผ ์•Œ์•„์•ผ ํ•œ๋‹ค.

C++์—์„œ๋Š” ๊ตฌ์กฐ์ฒด์˜ ๋ฉค๋ฒ„ ๋ณ€์ˆ˜(val, next)๊ฐ€ ๊ตฌ์กฐ์ฒด ๋‚ด๋ถ€์— ์ง์ ‘ ์ €์žฅ๋œ๋‹ค. ํ•˜์ง€๋งŒ ํŒŒ์ด์ฌ์€ ๋‹ค๋ฅด๋‹ค. ํŒŒ์ด์ฌ ๊ฐ์ฒด์˜ ์†์„ฑ์€ __dict__๋ผ๋Š” ๋”•์…”๋„ˆ๋ฆฌ์— ์ €์žฅ๋œ๋‹ค. ์œ„์˜ ์˜ˆ์‹œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•ด๋ณด๋ฉด ์•Œ ์ˆ˜ ์žˆ๋‹ค(์ ˆ๋Œ€ cpython์„ ๋‹ค์‹œ ๊นŒ๊ธฐ ๊ท€์ฐฎ์€ ๊ฒŒ ์•„๋‹ˆ๋‹ค).

print(node.__dict__)  # {'val': 1, 'next': None}

์ฆ‰, ํŒŒ์ด์ฌ ๊ฐ์ฒด๋Š” ๊ฐ์ฒด ์ž์ฒด (PyObject ๊ตฌ์กฐ)์™€ __dict__ ๋”•์…”๋„ˆ๋ฆฌ (์†์„ฑ์„ ์ €์žฅํ•˜๋Š” ๋ณ„๋„ ๊ฐ์ฒด)๋กœ ๋‚˜๋‰˜์–ด ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์‹ค์ œ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์•Œ๋ ค๋ฉด ๋‘˜ ๋‹ค ์ธก์ •ํ•ด์•ผ ํ•œ๋‹ค:

import sys
print(sys.getsizeof(node)) # ๊ฐ์ฒด ์ž์ฒด -> 48 ๋ฐ”์ดํŠธ
print(sys.getsizeof(node.__dict__)) # ์†์„ฑ ๋”•์…”๋„ˆ๋ฆฌ -> 64 ๋ฐ”์ดํŠธ
# ์ด 112 ๋ฐ”์ดํŠธ, (๋ฒ„์ „์— ๋”ฐ๋ผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค.)

์ฆ‰, ๋ฉ”๋ชจ๋ฆฌ์ ์œผ๋กœ๋„ ์—„์ฒญ๋‚œ ์†ํ•ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋ฌด์—‡์„ ์“ฐ๋ƒ?

ํŒŒ์ด์ฌ์—์„œ ์–‘์ชฝ ๋์—์„œ์˜ ์‚ฝ์ž…/์‚ญ์ œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด collections.deque๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. deque๋Š” double-ended-queue์˜ ์ค„์ž„๋ง๋กœ ์–‘๋ฐฉํ–ฅ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” queueํ˜• ์ž๋ฃŒ๊ตฌ์กฐ์ด๋‹ค. ๋‚ด๋ถ€์ ์œผ๋กœ ๋ธ”๋ก ๋‹จ์œ„ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š”๋ฐ, ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ์™€ ๋ฐฐ์—ด์˜ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ๊ตฌ์กฐ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค. ์–‘์ชฝ ๋์—์„œ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์œผ๋กœ O(1)ย ์‹œ๊ฐ„์— append์™€ pop์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฆฌ์ŠคํŠธ๋„ pop()๊ณผ append()๋ฅผ ์ง€์›ํ•˜์ง€๋งŒ, ๋งจ ์•ž์—์„œ ์š”์†Œ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” pop(0)์ด๋‚˜ ๋งจ ์•ž์— ์‚ฝ์ž…ํ•˜๋Š” insert(0, v)๋Š” O(n)O(n)ย ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค.

์•„๋ž˜๋Š” cpython/Modules/_collectionsmodule.c์˜ deque ๊ตฌ์กฐ์ฒด ์ •์˜๋‹ค. ๋ธ”๋ก(block)์ด ์ด์ค‘ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋กœ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๊ณ , ๊ฐ ๋ธ”๋ก์€ ๊ณ ์ • ํฌ๊ธฐ ๋ฐฐ์—ด์„ ๊ฐ€์ง„๋‹ค๋Š” ๊ฑธ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

#define BLOCKLEN 64  // ๊ฐ ๋ธ”๋ก์˜ ํฌ๊ธฐ

// ๋ธ”๋ก ๊ตฌ์กฐ์ฒด - ์ด์ค‘ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ์˜ ๋…ธ๋“œ
typedef struct BLOCK {
struct BLOCK *leftlink; // ์ด์ „ ๋ธ”๋ก ํฌ์ธํ„ฐ
PyObject *data[BLOCKLEN]; // 64๊ฐœ ์š”์†Œ๋ฅผ ๋‹ด๋Š” ๋ฐฐ์—ด
struct BLOCK *rightlink; // ๋‹ค์Œ ๋ธ”๋ก ํฌ์ธํ„ฐ
} block;

struct dequeobject {
PyObject_VAR_HEAD
block *leftblock;
block *rightblock;
Py_ssize_t leftindex; /* 0 <= leftindex < BLOCKLEN */
Py_ssize_t rightindex; /* 0 <= rightindex < BLOCKLEN */
size_t state; /* incremented whenever the indices move */
Py_ssize_t maxlen; /* maxlen is -1 for unbounded deques */
Py_ssize_t numfreeblocks;
block *freeblocks[MAXFREEBLOCKS];
PyObject *weakreflist;
};

๊ตฌ์กฐ๋ฅผ ๊ทธ๋ฆผ์œผ๋กœ ๋‚˜ํƒ€๋‚ด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

deque ๋‚ด๋ถ€ ๊ตฌ์กฐ:
Block 1 <--> Block 2 <--> Block 3
[64๊ฐœ ์š”์†Œ] <--> [64๊ฐœ ์š”์†Œ] <--> [64๊ฐœ ์š”์†Œ]

๊ฐ ๋ธ”๋ก์€ leftlink์™€ rightlink๋กœ ์ด์ „/๋‹ค์Œ ๋ธ”๋ก์„ ๊ฐ€๋ฆฌํ‚ค๋Š” ํฌ์ธํ„ฐ๋ฅผ ๊ฐ€์ง„๋‹ค. ๊ทธ๋ฆฌ๊ณ  data[BLOCKLEN] ๋ฐฐ์—ด์— ์‹ค์ œ PyObject ํฌ์ธํ„ฐ 64๊ฐœ๋ฅผ ์—ฐ์†์ ์œผ๋กœ ์ €์žฅํ•œ๋‹ค. ์ด ๊ตฌ์กฐ ๋•๋ถ„์— ์•ž/๋’ค ์‚ฝ์ž…/์‚ญ์ œ๊ฐ€ ์ง„์งœ O(1)O(1)์ด๋ฉด์„œ๋„ ์บ์‹œ ํšจ์œจ๊นŒ์ง€ ์ข‹๋‹ค. ๋ธ”๋ก ๋‚ด๋ถ€๋Š” ์—ฐ์† ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์“ฐ๋‹ˆ ์บ์‹œ ํšจ์œจ์„ฑ์ด ์œ ์ง€๋œ๋‹ค. ๋‹ค๋งŒ list๋ณด๋‹ค๋Š” ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์•ฝ๊ฐ„ ์žˆ๊ณ , ์ค‘๊ฐ„ ์‚ฝ์ž…/์‚ญ์ œ๋Š” ์—ฌ์ „ํžˆ O(n)O(n)์ด๋ฉฐ, ์ธ๋ฑ์‹ฑ์€ ์–‘ ๋์—์„œ๋Š” O(1)O(1)์ด์ง€๋งŒ ์ค‘๊ฐ„ ์š”์†Œ ์ ‘๊ทผ์€ O(n)O(n)์ด๋‹ค.

from collections import deque

# deque ์ƒ์„ฑ
dq = deque([1, 2, 3])

# ์–‘์ชฝ ๋์—์„œ O(1) ์—ฐ์‚ฐ
dq.append(4) # ์˜ค๋ฅธ์ชฝ์— ์ถ”๊ฐ€: [1, 2, 3, 4]
dq.appendleft(0) # ์™ผ์ชฝ์— ์ถ”๊ฐ€: [0, 1, 2, 3, 4]
dq.pop() # ์˜ค๋ฅธ์ชฝ์—์„œ ์ œ๊ฑฐ: [0, 1, 2, 3]
dq.popleft() # ์™ผ์ชฝ์—์„œ ์ œ๊ฑฐ: [1, 2, 3]

# ์ธ๋ฑ์‹ฑ (์–‘ ๋์€ O(1), ์ค‘๊ฐ„์€ O(n))
print(dq[0]) # 1 - ์™ผ์ชฝ ๋, O(1)
print(dq[-1]) # 3 - ์˜ค๋ฅธ์ชฝ ๋, O(1)
print(dq[1]) # 2 - ์ค‘๊ฐ„, O(n)

๋”ฐ๋ผ์„œ ์Šคํƒ/ํ์ฒ˜๋Ÿผ ํ•œ์ชฝ ๋ ๋˜๋Š” ์–‘์ชฝ ๋์—์„œ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” deque๊ฐ€ ์ ํ•ฉํ•˜๊ณ , ์ž„์˜ ์œ„์น˜์˜ ์š”์†Œ์— ๋น ๋ฅด๊ฒŒ ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜ ์ค‘๊ฐ„์—์„œ ์‚ฝ์ž…/์‚ญ์ œํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋™์  ๋ฐฐ์—ด์ธ list๊ฐ€ ์ ํ•ฉํ•˜๋‹ค.

๊ทธ๋Ÿผ์—๋„ ํŒŒ์ด์ฌ์—์„œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์“ฐ๋Š” ๊ฒฝ์šฐโ€‹

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๊ทธ๋ƒฅ ๋„˜์–ด๊ฐ€๋ฉด ์žฌ๋ฏธ๊ฐ€ ์—†์œผ๋‹ˆ๊นŒ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์“ฐ๋Š” ๊ฒฝ์šฐ๋„ ์•Œ์•„๋ณด์ž. ํŒŒ์ด์ฌ์—์„œ์˜ ๊ฒฝ์šฐ๋งŒ ์ฐพ์•„๋ณด๋ ค๋‹ค๊ฐ€ ์ด๊ฒƒ์ €๊ฒƒ ๋” ์ถ”๊ฐ€ํ–ˆ๋‹ค.

์œ„์—์„œ ์บ์‹œ ์ง€์—ญ์„ฑ ๋ฌธ์ œ์™€ ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋ฒ„ํ—ค๋“œ ๋•Œ๋ฌธ์— ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋น„ํšจ์œจ์ ์ด๋ผ๊ณ  ํ–ˆ์ง€๋งŒ, ์—ฌ์ „ํžˆ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์œ ์šฉํ•œ ํŠน์ˆ˜ํ•œ ๊ฒฝ์šฐ๋“ค์ด ์žˆ๊ธฐ๋Š” ํ•˜๋‹ค.

Python functools.lru_cache์˜ ์ด์ค‘ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธโ€‹

ํŒŒ์ด์ฌ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ functools.lru_cache๋Š” LRU(Least Recently Used) ์บ์‹œ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ์ด์ค‘ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. LRU Cache๋Š” ๊ฐ€์žฅ ์˜ค๋ž˜ ์‚ฌ์šฉ๋˜์ง€ ์•Š์€ ํ•ญ๋ชฉ์„ ์ œ๊ฑฐํ•˜๋Š” ์บ์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ธ๋ฐ, ๋”•์…”๋„ˆ๋ฆฌ๋งŒ์œผ๋กœ๋Š” "์–ด๋–ค ๋ฐ์ดํ„ฐ๊ฐ€ ์ตœ๊ทผ์— ์‚ฌ์šฉ๋๋Š”์ง€" ์ˆœ์„œ๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์—†๋‹ค. ๋ฐฐ์—ด๋กœ ์ˆœ์„œ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด ์ค‘๊ฐ„ ์‚ฝ์ž…/์‚ญ์ œ๊ฐ€ O(n)O(n)์ด ๊ฑธ๋ ค์„œ ํšจ์œจ์ ์ด์ง€ ์•Š๋‹ค.

cpython/Lib/functools.py๋ฅผ ๋ณด๋ฉด ์›ํ˜• ์ด์ค‘ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ(circular doubly linked list)๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๊ฐ ๋งํฌ๋Š” [previous_link, next_link, key, cached_result] ํ˜•ํƒœ์˜ ๋ฆฌ์ŠคํŠธ๋‹ค. ์ฝ”๋“œ์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ์ •์˜๋ผ ์žˆ๋‹ค.

def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
# Constants shared by all lru cache instances:
sentinel = object() # unique object used to signal cache misses
make_key = _make_key # build a key from the function arguments
PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields

cache = {}
hits = misses = 0
full = False
cache_get = cache.get # bound method to lookup a key or return None
cache_len = cache.__len__ # get cache size without calling len()
lock = RLock() # because linkedlist updates aren't threadsafe
root = [] # root of the circular doubly linked list -> Sentinel ๋…ธ๋“œ
root[:] = [root, root, None, None] # initialize by pointing to self

sentinel ๋…ธ๋“œ(root)๊ฐ€ ์ž๊ธฐ ์ž์‹ ์„ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ์ดˆ๊ธฐํ™”ํ•˜๋ฉด ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋น„์—ˆ์„ ๋•Œ์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๋‹จ์ˆœํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค. ์บ์‹œ๋Š” ๋”•์…”๋„ˆ๋ฆฌ์™€ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ๋‹ค. ๋”•์…”๋„ˆ๋ฆฌ๋Š” {key: link} ํ˜•ํƒœ๋กœ O(1)O(1) ์กฐํšŒ๋ฅผ ์ œ๊ณตํ•˜๊ณ , ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋Š” ์‚ฌ์šฉ ์ˆœ์„œ๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค. ์ƒˆ ํ•ญ๋ชฉ์„ ์บ์‹œ์— ์ถ”๊ฐ€ํ•  ๋•Œ๋Š” root ๋ฐ”๋กœ ๋‹ค์Œ์— ๋งํฌ๋ฅผ ์‚ฝ์ž…ํ•˜๊ณ , ๊ธฐ์กด ํ•ญ๋ชฉ์— ์ ‘๊ทผํ•˜๋ฉด ํ•ด๋‹น ๋งํฌ๋ฅผ ์ฐพ์•„์„œ ์ œ๊ฑฐํ•œ ๋’ค ๋‹ค์‹œ root ๋‹ค์Œ์œผ๋กœ ์ด๋™์‹œํ‚จ๋‹ค. ์ด๊ฒŒ O(1)O(1)์ธ ์ด์œ ๋Š” ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๋งํฌ์˜ ์œ„์น˜๋ฅผ ์ด๋ฏธ ์•Œ๊ณ  ์žˆ์–ด์„œ ํฌ์ธํ„ฐ 4๊ฐœ(๋งํฌ์˜ prev/next, ์ด์›ƒ ๋…ธ๋“œ๋“ค์˜ prev/next)๋งŒ ์—…๋ฐ์ดํŠธํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์บ์‹œ๊ฐ€ ๊ฝ‰ ์ฐจ๋ฉด root์˜ prev(๊ฐ€์žฅ ์˜ค๋ž˜๋œ ํ•ญ๋ชฉ)๋ฅผ O(1)O(1)์— ์ œ๊ฑฐํ•œ๋‹ค. ์ด ๊ตฌ์กฐ ๋•๋ถ„์— ํŒŒ์ด์ฌ์˜ lru_cache ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” ํ•จ์ˆ˜ ํ˜ธ์ถœ ๊ฒฐ๊ณผ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์บ์‹ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

Python weakref์˜ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธโ€‹

ํŒŒ์ด์ฌ์˜ weakref ๋ชจ๋“ˆ์€ ์•ฝํ•œ ์ฐธ์กฐ(weak reference)๋ฅผ ๊ด€๋ฆฌํ•  ๋•Œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์•ฝํ•œ ์ฐธ์กฐ๋Š” ๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค์ง€๋งŒ ์ฐธ์กฐ ์นด์šดํŠธ๋ฅผ ์ฆ๊ฐ€์‹œํ‚ค์ง€ ์•Š๋Š” ์ฐธ์กฐ ๋ฐฉ์‹์ด๋‹ค. ๊ฐ์ฒด๊ฐ€ ์‚ญ์ œ๋  ๋•Œ ๋ชจ๋“  ์•ฝํ•œ ์ฐธ์กฐ๋ฅผ ๋ฌดํšจํ™”ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๋•Œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์œ ์šฉํ•˜๋‹ค.

cpython/Include/cpython/weakrefobject.h์— ์•ฝํ•œ ์ฐธ์กฐ ๊ฐ์ฒด๊ฐ€ ์ •์˜๋˜์–ด ์žˆ๋‹ค. ์ฐธ๊ณ ๋กœcpython/Include/weakrefobject.h์—์„œ ์ด ํŒŒ์ผ์„ includeํ•œ๋‹ค.

/* PyWeakReference is the base struct for the Python ReferenceType, ProxyType,
* and CallableProxyType.
*/
struct _PyWeakReference {
PyObject_HEAD

/* The object to which this is a weak reference, or Py_None if none.
* Note that this is a stealth reference: wr_object's refcount is
* not incremented to reflect this pointer.
*/
PyObject *wr_object;

/* A callable to invoke when wr_object dies, or NULL if none. */
PyObject *wr_callback;

/* A cache for wr_object's hash code. As usual for hashes, this is -1
* if the hash code isn't known yet.
*/
Py_hash_t hash;

/* If wr_object is weakly referenced, wr_object has a doubly-linked NULL-
* terminated list of weak references to it. These are the list pointers.
* If wr_object goes away, wr_object is set to Py_None, and these pointers
* have no meaning then.
*/
PyWeakReference *wr_prev;
PyWeakReference *wr_next;
vectorcallfunc vectorcall;

#ifdef Py_GIL_DISABLED
/* Pointer to the lock used when clearing in free-threaded builds.
* Normally this can be derived from wr_object, but in some cases we need
* to lock after wr_object has been set to Py_None.
*/
PyMutex *weakrefs_lock;
#endif
};

์•ฝํ•œ ์ฐธ์กฐ ๋Œ€์ƒ ๊ฐ์ฒด๋Š” weakreflist ํฌ์ธํ„ฐ๋ฅผ ํ†ตํ•ด ์ž์‹ ์„ ๊ฐ€๋ฆฌํ‚ค๋Š” ์•ฝํ•œ ์ฐธ์กฐ๋“ค์„ ์ด์ค‘ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๋กœ ์œ ์ง€ํ•œ๋‹ค. ๊ฐ์ฒด๊ฐ€ ์‚ญ์ œ๋  ๋•Œ ์ด ๋ฆฌ์ŠคํŠธ๋ฅผ ์ˆœํšŒํ•˜๋ฉด์„œ ๋ชจ๋“  ์•ฝํ•œ ์ฐธ์กฐ๋ฅผ ๋ฌดํšจํ™”ํ•˜๊ณ  ์ฝœ๋ฐฑ์„ ํ˜ธ์ถœํ•œ๋‹ค. ๋ฐฐ์—ด๋กœ ๊ด€๋ฆฌํ•˜๋ฉด ์ค‘๊ฐ„ ์ œ๊ฑฐํ•  ๋•Œ๋งˆ๋‹ค ์žฌํ• ๋‹น์ด ํ•„์š”ํ•˜์ง€๋งŒ, ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋Š” ํฌ์ธํ„ฐ๋งŒ ์žฌ์—ฐ๊ฒฐํ•˜๋ฉด ๋˜๋ฏ€๋กœ O(1)O(1)์— ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

Python asyncio์˜ ์ด๋ฒคํŠธ ๋ฃจํ”„โ€‹

์•ฝํ•œ ์ฐธ์กฐ์ฒ˜๋Ÿผ ๋Ÿฐํƒ€์ž„ ๋ ˆ๋ฒจ์—์„œ๋„ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๊ฐ€ ๋“ฑ์žฅํ•œ๋‹ค. ์ด ์„น์…˜์—์„œ ๋ณด๋Š” ๊ตฌ์กฐ๋Š” "์ฆ‰์‹œ ์‹คํ–‰ ๋Œ€๊ธฐ์—ด"์ธ _ready๊ฐ€ collections.deque๋กœ ๊ตฌ์„ฑ๋œ ๋ถ€๋ถ„์ด๋‹ค. deque๋Š” ํ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ฃผ์ง€๋งŒ, CPython ๊ตฌํ˜„์€ ๊ณ ์ • ํฌ๊ธฐ ๋ฐฐ์—ด ๋ธ”๋ก์„ ์ด์ค‘์œผ๋กœ ์—ฐ๊ฒฐํ•œ "๋ธ”๋ก๋“ค์˜ ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ"๋ผ ์–‘๋ ์—ฐ์‚ฐ์ด O(1)์ด๋‹ค(cpython/Modules/_collectionsmodule.c). ๋ฐ˜๋Œ€๋กœ ์‹œ๊ฐ„์ˆœ ์ •๋ ฌ์ด ํ•„์š”ํ•œ _scheduled๋Š” heapq๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋™์  ๋ฐฐ์—ด(list)์ด๋‹ค. ์•ž์ชฝ๋งŒ ๋นผ๊ณ  ๋’ค์ชฝ๋งŒ ๋„ฃ๋Š” _ready ํŠน์„ฑ์ƒ pop(0)์„ ํ•ด์•ผ ํ•˜๋Š” ์ผ๋ฐ˜ ๋ฆฌ์ŠคํŠธ๋ณด๋‹ค deque๊ฐ€ ๋งž๋‹ค. ์•„๋ž˜ cpython/Lib/asyncio/base_events.py์˜ BaseEventLoop ์ดˆ๊ธฐํ™”์—์„œ _ready๊ฐ€ deque๋กœ ๋ฐฐ์น˜๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

class BaseEventLoop(events.AbstractEventLoop):
def __init__(self):
self._ready = collections.deque() # ์‹คํ–‰ ์ค€๋น„๋œ ์ฝœ๋ฐฑ๋“ค
self._scheduled = [] # ์‹œ๊ฐ„์ˆœ์œผ๋กœ ์ •๋ ฌ๋œ ํž™
# ...

def call_soon(self, callback, *args, context=None):
"""Arrange for a callback to be called as soon as possible.

This operates as a FIFO queue: callbacks are called in the
order in which they are registered. Each callback will be
called exactly once.

Any positional arguments after the callback will be passed to
the callback when it is called.
"""
self._check_closed()
if self._debug:
self._check_thread()
self._check_callback(callback, 'call_soon')
handle = self._call_soon(callback, args, context)
if handle._source_traceback:
del handle._source_traceback[-1]
return handle

def _call_soon(self, callback, args, context):
handle = events.Handle(callback, args, self, context)
if handle._source_traceback:
del handle._source_traceback[-1]
self._ready.append(handle) # deque์— O(1)๋กœ ์ถ”๊ฐ€
return handle

def _run_once(self):
"""Run one full iteration of the event loop.

This calls all currently ready callbacks, polls for I/O,
schedules the resulting callbacks, and finally schedules
'call_later' callbacks.
"""
# ์‹œ๊ฐ„์ด ๋œ ์˜ˆ์•ฝ ์ฝœ๋ฐฑ์„ ํž™์—์„œ ๊บผ๋‚ด ready๋กœ ์˜ฎ๊ธด๋‹ค.
ready = self._ready
self._ready = collections.deque()
end_time = self.time()
while self._scheduled:
handle = self._scheduled[0]
if handle._when > end_time:
break
handle = heapq.heappop(self._scheduled)
ready.append(handle)

# ready๋Š” ์–‘ ๋๋งŒ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ deque๊ฐ€ ํšจ์œจ์ ์ด๋‹ค.
ntodo = len(ready)
for _ in range(ntodo):
handle = ready.popleft()
if not handle._cancelled:
handle._run()

ready/scheduled์— ๋‹ด๊ธฐ๋Š” ํ•ญ๋ชฉ์€ ์ฝœ๋ฐฑ๊ณผ ์ธ์ž๋ฅผ ๋“ค๊ณ  ์žˆ๋Š” Handle ๊ฐ์ฒด๋‹ค. ์–ด๋–ค ํ•„๋“œ๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š”์ง€ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์•„๋ž˜ ์ •์˜๋ฅผ ๋ง๋ถ™์˜€๋‹ค. __slots__๋งŒ ๊ฐ€์ง„ ๊ฒฝ๋Ÿ‰ ๊ตฌ์กฐ๋ผ ํ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋œํ•˜๋‹ค.

class Handle:
"""Object returned by callback registration methods."""

__slots__ = ('_callback', '_args', '_cancelled', '_loop',
'_source_traceback', '_repr', '__weakref__',
'_context')

def __init__(self, callback, args, loop, context=None):
if context is None:
context = contextvars.copy_context()
self._context = context
self._loop = loop
self._callback = callback
self._args = args
self._cancelled = False
self._repr = None
if self._loop.get_debug():
self._source_traceback = format_helpers.extract_stack(
sys._getframe(1))
else:
self._source_traceback = None
# ...

์ •๋ ฌ์ด ํ•„์š”ํ•œ _scheduled์—๋Š” ๋ฐฐ์—ด ๊ธฐ๋ฐ˜ ํž™์ด, ์•ž์—์„œ ๊บผ๋‚ด๊ณ  ๋’ค์— ๋„ฃ๊ธฐ๋งŒ ํ•˜๋Š” _ready์—๋Š” ๊ณ ์ • ํฌ๊ธฐ ๋ธ”๋ก์„ ์ด์€ deque(๋ธ”๋ก ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ)๊ฐ€ ๋“ค์–ด๊ฐ„๋‹ค. ์ด์ „์— ์‚ดํŽด๋ณธ "๋™์  ๋ฐฐ์—ด vs ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ" ์„ ํƒ ๊ธฐ์ค€์ด ์ด๋ฒคํŠธ ๋ฃจํ”„ ์•ˆ์—์„œ๋„ ๊ทธ๋Œ€๋กœ ๋ฐ˜๋ณต๋˜๋Š” ์…ˆ์ด๋‹ค.

์ฐธ๊ณ ๋กœ deque์˜ ๋‚ด๋ถ€ ๋ธ”๋ก ๊ตฌ์กฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ •์˜๋ผ ์žˆ๋‹ค(cpython/Modules/_collectionsmodule.c). ํ•œ ๋ธ”๋ก์ด 64์นธ์งœ๋ฆฌ ๋ฐฐ์—ด์„ ๋“ค๊ณ , ์ขŒ์šฐ ๋ธ”๋ก๊ณผ ๋งํฌ๋กœ ์ด์–ด์ง„๋‹ค.

#define BLOCKLEN 64

typedef struct BLOCK {
struct BLOCK *leftlink;
PyObject *data[BLOCKLEN];
struct BLOCK *rightlink;
} block;

์ด๋ฐ–์— ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์“ฐ๋Š” ๊ฒฝ์šฐโ€‹

Python OrderedDict์˜ ์ด์ค‘ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธโ€‹

ํŒŒ์ด์ฌ 3.7 ์ด์ „์—๋Š” ์ผ๋ฐ˜ ๋”•์…”๋„ˆ๋ฆฌ๊ฐ€ ์‚ฝ์ž… ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— OrderedDict๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค(3.7 ์ดํ›„์—๋Š” ์ผ๋ฐ˜ dict๋„ ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•˜์ง€๋งŒ, OrderedDict๋Š” ์—ฌ์ „ํžˆ ์ˆœ์„œ ๊ด€๋ จ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•œ๋‹ค). CPython์˜ C ๊ตฌํ˜„(cpython/Objects/odictobject.c)์„ ๋ณด๋ฉด ์ด์ค‘ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋กœ ์ˆœ์„œ๋ฅผ ์œ ์ง€ํ•œ๋‹ค๋Š” ๊ฑธ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

ํ•ต์‹ฌ ๊ตฌ์กฐ์ฒด๋Š” ๋‘ ๊ฐ€์ง€๋‹ค.

/* PyODictObject */
struct _odictobject {
PyDictObject od_dict; /* the underlying dict */
_ODictNode *od_first; /* first node in the linked list, if any */
_ODictNode *od_last; /* last node in the linked list, if any */
/* od_fast_nodes, od_fast_nodes_size and od_resize_sentinel are managed
* by _odict_resize().
* Note that we rely on implementation details of dict for both. */
_ODictNode **od_fast_nodes; /* hash table that mirrors the dict table */
Py_ssize_t od_fast_nodes_size;
void *od_resize_sentinel; /* changes if odict should be resized */

size_t od_state; /* incremented whenever the LL changes */
PyObject *od_inst_dict; /* OrderedDict().__dict__ */
PyObject *od_weakreflist; /* holds weakrefs to the odict */
};

...

struct _odictnode {
PyObject *key;
Py_hash_t hash;
_ODictNode *next;
_ODictNode *prev;
};

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฑด od_fast_nodes๋‹ค. ๋‹จ์ˆœํžˆ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋งŒ ์“ฐ๋ฉด ํŠน์ • ํ‚ค์˜ ๋…ธ๋“œ๋ฅผ ์ฐพ๋Š” ๊ฒŒ O(n)O(n)์ด ๊ฑธ๋ฆฐ๋‹ค. ๊ทธ๋ž˜์„œ OrderedDict๋Š” "dict์˜ ํ‚ค ์ˆœ์„œ๋ฅผ ๋…ธ๋“œ ํฌ์ธํ„ฐ ๋ฐฐ์—ด๋กœ ๋ฏธ๋Ÿฌ๋ง"ํ•ด์„œ O(1)O(1) ์กฐํšŒ๋ฅผ ์œ ์ง€ํ•œ๋‹ค. ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ๋Š” _odict_add_tail() ํ•จ์ˆ˜๊ฐ€ ๋ฆฌ์ŠคํŠธ ๋์— ์—ฐ๊ฒฐํ•œ๋‹ค:

static void
_odict_add_tail(PyODictObject *od, _ODictNode *node)
{
_odictnode_PREV(node) = _odict_LAST(od);
_odictnode_NEXT(node) = NULL;
if (_odict_LAST(od) == NULL)
_odict_FIRST(od) = node;
else
_odictnode_NEXT(_odict_LAST(od)) = node;
_odict_LAST(od) = node;
od->od_state++;
}

์‚ญ์ œํ•  ๋•Œ๋Š” _odict_remove_node()๊ฐ€ ์•ž๋’ค ํฌ์ธํ„ฐ๋ฅผ ์žฌ์—ฐ๊ฒฐํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ๋”•์…”๋„ˆ๋ฆฌ์˜ O(1)O(1) ์กฐํšŒ ์„ฑ๋Šฅ์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ์‚ฝ์ž… ์ˆœ์„œ๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ๋‹ค.

Redis์˜ Skiplist์™€ ์ด์ค‘ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธโ€‹

Redis๋Š” Sorted Set(์ •๋ ฌ๋œ ์ง‘ํ•ฉ)์„ ๊ตฌํ˜„ํ•  ๋•Œ skiplist๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ, ์ด skiplist๋Š” ๋ ˆ๋ฒจ 1์—์„œ๋งŒ backward ํฌ์ธํ„ฐ๋ฅผ ๊ฐ€์ง„ ์ด์ค‘ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋‹ค. redis/src/t_zset.c๋ฅผ ๋ณด๋ฉด Redis๋Š” William Pugh์˜ ์›๋ž˜ skiplist ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์„ธ ๊ฐ€์ง€ ๋ณ€๊ฒฝํ•ด์„œ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๋‚˜์™€ ์žˆ๋‹ค. ์ฒซ์งธ, ์ค‘๋ณต๋œ ์ ์ˆ˜(score)๋ฅผ ํ—ˆ์šฉํ•œ๋‹ค. ๋‘˜์งธ, ์ ์ˆ˜๋ฟ๋งŒ ์•„๋‹ˆ๋ผ satellite data๊นŒ์ง€ ๋น„๊ตํ•œ๋‹ค. ์…‹์งธ, "there is a back pointer, so it's a doubly linked list with the back pointers being only at level 1"์ด๋ผ๊ณ  ๋ช…์‹œ๋ผ ์žˆ๋‹ค.

/* ZSETs are ordered sets using two data structures to hold the same elements
* in order to get O(log(N)) INSERT and REMOVE operations into a sorted
* data structure.
*
* The elements are added to a hash table mapping Redis objects to scores.
* At the same time the elements are added to a skip list mapping scores
* to Redis objects (so objects are sorted by scores in this "view").
*
* Note that the SDS string representing the element is the same in both
* the hash table and skiplist in order to save memory. What we do in order
* to manage the shared SDS string more easily is to free the SDS string
* only in zslFreeNode(). The dictionary has no value free method set.
* So we should always remove an element from the dictionary, and later from
* the skiplist.
*
* This skiplist implementation is almost a C translation of the original
* algorithm described by William Pugh in "Skip Lists: A Probabilistic
* Alternative to Balanced Trees", modified in three ways:
* a) this implementation allows for repeated scores.
* b) the comparison is not just by key (our 'score') but by satellite data.
* c) there is a back pointer, so it's a doubly linked list with the back
* pointers being only at "level 1". This allows to traverse the list
* from tail to head, useful for ZREVRANGE. */

์ด backward ํฌ์ธํ„ฐ๋Š” ์™œ ํ•„์š”ํ• ๊นŒ? Redis๋Š” ZREVRANGE ๊ฐ™์ด ์ •๋ ฌ๋œ ์ง‘ํ•ฉ์—์„œ ์ ์ˆ˜ ์ˆœ์„œ๋ฅผ ๋†’์€ ์ ์ˆ˜๋ถ€ํ„ฐ ๋‚ฎ์€ ์ ์ˆ˜๋กœ ์ฆ‰, ์—ญ์ˆœ์œผ๋กœ ํŠน์ • ๋ฒ”์œ„์˜ ์š”์†Œ๋“ค์„ ๊ฐ€์ ธ์˜ค๋Š” ์ปค๋งจ๋“œ๋ฅผ ์ง€์›ํ•œ๋‹ค. skiplist์˜ ์—ฌ๋Ÿฌ ๋ ˆ๋ฒจ ์ค‘ ๊ฐ€์žฅ ํ•˜์œ„ ๋ ˆ๋ฒจ(level 1)์—๋งŒ backward ํฌ์ธํ„ฐ๋ฅผ ๋‘๋ฉด, ์ •๋ฐฉํ–ฅ์œผ๋กœ๋Š” skiplist์˜ O(logN)O(log N) ํƒ์ƒ‰ ํšจ์œจ์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ์—ญ๋ฐฉํ–ฅ์œผ๋กœ๋Š” ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ์ฒ˜๋Ÿผ ์ˆœ์ฐจ์ ์œผ๋กœ ํƒ์ƒ‰ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ๋ณด์ž.

...
x->backward = (update[0] == zsl->header) ? NULL : update[0];
if (x->level[0].forward)
x->level[0].forward->backward = x;
...

๊ฐ ๋…ธ๋“œ๊ฐ€ ์ž์‹ ์˜ ์ด์ „ ๋…ธ๋“œ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” backward ํฌ์ธํ„ฐ๋ฅผ ์œ ์ง€ํ•œ๋‹ค. Redis๋Š” ์ด skiplist๋ฅผ ํ•ด์‹œ ํ…Œ์ด๋ธ”๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ๋‹ค. ํ•ด์‹œ ํ…Œ์ด๋ธ”์€ ๋ฉค๋ฒ„๋กœ O(1)O(1) ์กฐํšŒ๋ฅผ ์ œ๊ณตํ•˜๊ณ , skiplist๋Š” ์ ์ˆ˜ ๊ธฐ์ค€ O(logN)O(log N) ๋ฒ”์œ„ ์ฟผ๋ฆฌ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ์ž‘์€ ๋ฐ์ดํ„ฐ์…‹์—๋Š” listpack์„ ์“ฐ๋‹ค๊ฐ€ ํฌ๊ธฐ๊ฐ€ ์ปค์ง€๋ฉด skiplist+dict๋กœ ์ „ํ™˜ํ•œ๋‹ค. listpack์€ ๋ชจ๋“  ์š”์†Œ๋ฅผ ํ•˜๋‚˜์˜ ๋ฉ”๋ชจ๋ฆฌ ๋ธ”๋ก์— ์—ฐ์†์ ์œผ๋กœ ์ €์žฅํ•˜๋Š” ์••์ถ•๋œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ด๋‹ค. ์ด๋Ÿฐ ์„ค๊ณ„ ๋•๋ถ„์— Redis๋Š” ์ •๋ ฌ๋œ ์ง‘ํ•ฉ ์—ฐ์‚ฐ์„ ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

glibc malloc์˜ Free Listโ€‹

malloc/free๊ฐ€ ๋นˆ ๋ฉ”๋ชจ๋ฆฌ ๋ธ”๋ก์„ ๊ด€๋ฆฌํ•  ๋•Œ free list๋ผ๋Š” ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ค‘์š”ํ•œ ๊ฑด ๋ณ„๋„ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์“ฐ์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์ด๋‹ค. ๋นˆ ๋ธ”๋ก ์ž์ฒด์˜ ์ฒซ ๋ช‡ ๋ฐ”์ดํŠธ์— next ํฌ์ธํ„ฐ๋ฅผ ์ €์žฅํ•œ๋‹ค. 1000๋ฐ”์ดํŠธ ๋นˆ ๋ธ”๋ก์ด ์žˆ์œผ๋ฉด ์ฒซ 8๋ฐ”์ดํŠธ๋ฅผ ๋‹ค์Œ ๋นˆ ๋ธ”๋ก ์ฃผ์†Œ๋กœ ์“ฐ๊ณ , ๋‚˜๋จธ์ง€ 992๋ฐ”์ดํŠธ๋Š” ๊ทธ๋Œ€๋กœ ๋‘”๋‹ค. ๋ธ”๋ก ํฌ๊ธฐ๊ฐ€ ์ œ๊ฐ๊ฐ์ด๊ณ  ์ฃผ์†Œ๊ฐ€ ์—ฐ์†์ ์ด์ง€ ์•Š์•„์„œ ๋ฐฐ์—ด๋กœ๋Š” ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ตฌ์กฐ๋‹ค.

glibc/malloc/malloc.c์˜ ์‹ค์ œ ๊ตฌํ˜„์„ ๋ณด๋ฉด:

// ๋นˆ ๋ฉ”๋ชจ๋ฆฌ ๋ธ”๋ก์„ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋กœ ๊ด€๋ฆฌ
struct malloc_chunk {
INTERNAL_SIZE_T mchunk_prev_size; /* ์ด์ „ ์ฒญํฌ ํฌ๊ธฐ */
INTERNAL_SIZE_T mchunk_size; /* ํ˜„์žฌ ์ฒญํฌ ํฌ๊ธฐ */
struct malloc_chunk* fd; /* forward link - ๋‹ค์Œ ๋นˆ ๋ธ”๋ก */
struct malloc_chunk* bk; /* backward link - ์ด์ „ ๋นˆ ๋ธ”๋ก */
struct malloc_chunk* fd_nextsize; /* ํฐ ์ฒญํฌ์šฉ ๋‹ค์Œ ํฌ๊ธฐ */
struct malloc_chunk* bk_nextsize; /* ํฐ ์ฒญํฌ์šฉ ์ด์ „ ํฌ๊ธฐ */
};

// Free chunk๋ฅผ ๋ฆฌ์ŠคํŠธ์—์„œ ์ œ๊ฑฐ - O(1) ์—ฐ์‚ฐ
static void
unlink_chunk (mstate av, mchunkptr p)
{
mchunkptr fd = p->fd; // ํฌ์ธํ„ฐ ์žฌ์—ฐ๊ฒฐ๋งŒ์œผ๋กœ
mchunkptr bk = p->bk; // O(1)์— ์ œ๊ฑฐ ์™„๋ฃŒ
fd->bk = bk;
bk->fd = fd;
}

ํ•ด์ œ๋œ ๋ฉ”๋ชจ๋ฆฌ ๋ธ”๋ก ์ž์ฒด์˜ ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„์— fd/bk ํฌ์ธํ„ฐ๋ฅผ ์ €์žฅํ•˜์—ฌ ๋ณ„๋„ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น ์—†์ด free list๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.

libstdc++ unordered_map์˜ ์ฒด์ด๋‹โ€‹

๊ฐ™์€ ํ•ด์‹œ ๊ฐ’์„ ๊ฐ€์ง„ ์š”์†Œ๋“ค์„ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋กœ ์—ฐ๊ฒฐํ•˜๋ฉด ์ถฉ๋Œ ์‹œ O(1)์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฐฐ์—ด์ด์—ˆ๋‹ค๋ฉด ๋ฒ„ํ‚ท๋งˆ๋‹ค ๋™์  ๋ฐฐ์—ด์„ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๊ณ  ์žฌํ• ๋‹น ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

C++ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ std::unordered_map ๊ตฌํ˜„์€ ์‹ค์ œ๋กœ ์ฒด์ด๋‹์„ ์‚ฌ์šฉํ•œ๋‹ค. gcc/libstdc++-v3/include/bits/hashtable_policy.h๋ฅผ ๋ณด๋ฉด:

// ๋‹จ์ผ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋กœ ์ถฉ๋Œ ํ•ด๊ฒฐ
struct _Hash_node_base {
_Hash_node_base* _M_nxt; // ๋‹ค์Œ ๋…ธ๋“œ ํฌ์ธํ„ฐ

_Hash_node_base() noexcept : _M_nxt() { }
_Hash_node_base(_Hash_node_base* __next) noexcept : _M_nxt(__next) { }
};

// ์‹ค์ œ ๊ฐ’์„ ์ €์žฅํ•˜๋Š” ๋…ธ๋“œ
template<typename _Value, bool _Cache_hash_code>
struct _Hash_node : _Hash_node_base,
_Hash_node_value<_Value, _Cache_hash_code> {
_Hash_node* _M_next() const noexcept {
return static_cast<_Hash_node*>(this->_M_nxt);
}
};

blog.ilvokhin.com์˜ ๋ถ„์„์— ๋”ฐ๋ฅด๋ฉด:

"The _Hashtable class itself is a combination of std::forward_list<_Hash_node> containing the elements and std::vector<std::forward_list<_Hash_node>::iterator> representing the buckets."

๊ฐ ๋ฒ„ํ‚ท์€ ํ•ด์‹œ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•œ ์š”์†Œ๋“ค์„ ๋‹จ์ผ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋กœ ์ฒด์ด๋‹ํ•˜์—ฌ O(1) ์‚ฝ์ž…์„ ๋ณด์žฅํ•œ๋‹ค.

Linux Kernel์˜ Intrusive Doubly Linked Listโ€‹

Linux ์ปค๋„์€ include/linux/list.h์— ์ด์ค‘ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ ๊ตฌ์กฐ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. ํŠน์ดํ•œ ์ ์€ "intrusive list" ์„ค๊ณ„๋ฅผ ์“ด๋‹ค๋Š” ๊ฑฐ๋‹ค.

Intrusive list๋ž€? ์ผ๋ฐ˜์ ์ธ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋Š” ๋…ธ๋“œ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜์ง€๋งŒ(struct Node { int data; Node* next; }), intrusive list๋Š” ๋ฐ˜๋Œ€๋กœ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ฒด ์•ˆ์— ๋ฆฌ์ŠคํŠธ ํฌ์ธํ„ฐ๋ฅผ ํฌํ•จ์‹œํ‚ค๋Š” ๋ฐฉ์‹์ด๋‹ค.

Linux ์ปค๋„์€ ํ”„๋กœ์„ธ์Šค ์Šค์ผ€์ค„๋ง, ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ, ๋””๋ฐ”์ด์Šค ๋“œ๋ผ์ด๋ฒ„ ๋“ฑ ์ปค๋„ ์ „์ฒด์—์„œ ์ด intrusive doubly linked list๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. linux/include/linux/list.h์˜ ๊ตฌํ˜„์„ ๋ณด๋ฉด:

struct list_head {
struct list_head *next, *prev;
};

// ์ดˆ๊ธฐํ™” - ์ž๊ธฐ ์ž์‹ ์„ ๊ฐ€๋ฆฌํ‚ด
#define LIST_HEAD_INIT(name) { &(name), &(name) }

static inline void INIT_LIST_HEAD(struct list_head *list) {
WRITE_ONCE(list->next, list);
WRITE_ONCE(list->prev, list);
}

// O(1) ์‚ฝ์ž…
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next) {
next->prev = new;
new->next = next;
new->prev = prev;
WRITE_ONCE(prev->next, new);
}

์–ด๋–ค ๊ตฌ์กฐ์ฒด๋“  struct list_head ํ•„๋“œ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ์— ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค. ์ปค๋„์€ container_of ๋งคํฌ๋กœ๋กœ list_head์˜ ์ฃผ์†Œ์—์„œ ์‹ค์ œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์ฒด์˜ ์ฃผ์†Œ๋ฅผ ์—ญ์‚ฐํ•œ๋‹ค. ์ด ๋ฐฉ์‹์˜ ์žฅ์ ์€ ๊ฐ™์€ ๋ฆฌ์ŠคํŠธ ์กฐ์ž‘ ์ฝ”๋“œ(list_add, list_del, list_for_each ๋“ฑ)๋ฅผ ๋ชจ๋“  ๋ฐ์ดํ„ฐ ํƒ€์ž…์— ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค. ์‹ค์ œ๋กœ ์ปค๋„์˜ ํƒœ์Šคํฌ ๊ตฌ์กฐ์ฒด(task_struct)๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ list_head ํ•„๋“œ๋ฅผ ๊ฐ€์ ธ์„œ ๋™์‹œ์— ์—ฌ๋Ÿฌ ๋ฆฌ์ŠคํŠธ์— ์†ํ•  ์ˆ˜ ์žˆ๋‹ค(์˜ˆ: run queue, wait queue ๋“ฑ). ๋ฐฐ์—ด๋กœ๋Š” ์ด๋Ÿฐ ์œ ์—ฐ์„ฑ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์–ด๋ ต๋‹ค.

Chromium base::LinkedList์˜ Intrusive Listโ€‹

std::vector๋Š” ์šฉ๋Ÿ‰์ด ๋ถ€์กฑํ•˜๋ฉด ๋” ํฐ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ• ๋‹นํ•˜๊ณ  ๋ชจ๋“  ์š”์†Œ๋ฅผ ๋ณต์‚ฌํ•˜๋Š”๋ฐ, ์ด๋•Œ ๊ธฐ์กด iterator, ํฌ์ธํ„ฐ, ์ฐธ์กฐ๊ฐ€ ๋ชจ๋‘ ๋ฌดํšจํ™”๋œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด capacity๊ฐ€ 3์ธ vector์— 4๋ฒˆ์งธ ์š”์†Œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์žฌํ• ๋‹น์ด ์ผ์–ด๋‚˜์„œ ๊ธฐ์กด iterator๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด undefined behavior๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋ฐ˜๋ฉด std::list๋Š” ๊ฐ ๋…ธ๋“œ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ํž™์— ํ• ๋‹น๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒˆ ๋…ธ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด๋„ ๊ธฐ์กด ๋…ธ๋“œ๋“ค์˜ ์ฃผ์†Œ๋Š” ๋ณ€ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์‚ญ์ œ๋œ ๋…ธ๋“œ์˜ iterator๋งŒ ๋ฌดํšจํ™”๋˜๊ณ  ๋‚˜๋จธ์ง€๋Š” ์œ ํšจํ•˜๋‹ค. ์—ฌ๋Ÿฌ iterator๋ฅผ ๋™์‹œ์— ์œ ์ง€ํ•˜๋ฉด์„œ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•˜๋Š” ๋ณต์žกํ•œ ์ž๋ฃŒ๊ตฌ์กฐ์—์„œ๋Š” ์ด๊ฒŒ ์ค‘์š”ํ•˜๋‹ค.

Chromium ํ”„๋กœ์ ํŠธ๋Š” std::list ๋Œ€์‹  ์ž์ฒด ๊ตฌํ˜„ํ•œ base::LinkedList๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. chromium/base/containers/linked_list.h์˜ ์ฃผ์„์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค๋ช…๋˜์–ด ์žˆ๋‹ค:

"Erasing an element of type T* from base::LinkedList&lt;T&gt; is an O(1) operation. Whereas for std::list&lt;T*&gt; it is O(n). That is because with std::list<T*> you must obtain an iterator to the T* element before you can call erase(iterator)."

// Chromium์˜ Intrusive ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ ๊ตฌํ˜„
template <typename T>
class LinkedList {
public:
// The "root" node is self-referential, and forms the basis of a circular
// list (root_.next() will point back to the start of the list,
// and root_->previous() wraps around to the end of the list).
LinkedList() { root_.set(&root_, &root_); }
LinkedList(const LinkedList&) = delete;
LinkedList& operator=(const LinkedList&) = delete;

// Use move constructor with care. Returning a LinkedList from a function may
// be unsafe if the nodes are allocated on the stack. This operation is O(1)
// as only head and tail nodes are modified. `other` is left empty.
LinkedList(LinkedList&& other) : root_(std::move(other.root_)) {
other.root_.MakeSelfReferencing();
}

// Appends |e| to the end of the linked list.
void Append(LinkNode<T>* e) { e->InsertBefore(&root_); }

LinkNode<T>* head() const { return root_.next(); }

LinkNode<T>* tail() const { return root_.previous(); }

const LinkNode<T>* end() const { return &root_; }

bool empty() const { return head() == end(); }

private:
LinkNode<T> root_;
};

std::list๋Š” ๊ฐ’์œผ๋กœ ์‚ญ์ œ ์‹œ ๋จผ์ € ์š”์†Œ๋ฅผ ์ฐพ์•„์•ผ ํ•˜์ง€๋งŒ(O(n)), Chromium์˜ ๊ตฌํ˜„์€ ๊ฐ์ฒด๊ฐ€ ์ž์‹ ์˜ ๋…ธ๋“œ๋ฅผ ์ง์ ‘ ํฌํ•จํ•˜๋ฏ€๋กœ ์ฆ‰์‹œ ์ œ๊ฑฐ ๊ฐ€๋Šฅํ•˜๋‹ค.

์ •๋ฆฌโ€‹

์ฒ˜์Œ์—๋Š” ํŒŒ์ด์ฌ์—์„œ ์ฝ”๋”ฉ ํ…Œ์ŠคํŠธ ๋•Œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ž˜ ์“ฐ์ง€ ์•Š๋Š” ์ด์œ ๋กœ ์‹œ์ž‘ํ–ˆ๋‹ค๊ฐ€ ์—ฌ๊ธฐ๊นŒ์ง€ ์™”๋‹ค. ์ข€ ์•ผํฌ ํ„ธ์„ ๋งŽ์ด ๊นŽ์€ ๊ฑธ์ง€๋„. '์ฝ”๋”ฉ ํ…Œ์ŠคํŠธ ๋•Œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ž˜ ์“ฐ์ง€ ์•Š๋Š” ์ด์œ '์— ์ง‘์ค‘ํ–ˆ์–ด์•ผ ํ–ˆ๋Š”๋ฐ, ์–ด์ฉŒ๋‹ค ๋ณด๋‹ˆ ์ด์ƒํ•˜๊ฒŒ ๋๋‹ค. ๊ธ€์„ ์ ๋‹ค ๋ณด๋‹ˆ ์€๊ทผ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•œ ๊ณณ์ด ๋งŽ์•„์„œ ๋†€๋žฌ๋‹ค.

๋‚ด๊ฐ€ ๋งํ•˜๊ณ  ์‹ถ์—ˆ๋˜ ๊ฒฐ๋ก ์€ ์–ด์จŒ๋“  ์ง„์งœ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋ผ๊ณ  ๋ฌธ์ œ๊ฐ€ ๋‚˜์˜ค์ง€ ์•Š๋Š” ์ด์ƒ ์ฝ”๋”ฉ ํ…Œ์ŠคํŠธ์—์„œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด์„œ ์“ธ ์ผ์€ ๊ฑฐ์˜ ์—†์ง€ ์•Š์„๊นŒ ์‹ถ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

์•„๋ž˜ ์‹œ๊ฐ„ ๋ณต์žก๋„์™€ C++๊ณผ Python์œผ๋กœ ๊ตฌํ˜„ํ•œ ์˜ˆ์‹œ๋ฅผ ๋์œผ๋กœ ์ด ๊ธ€์„ ๋งˆ์นœ๋‹ค.

์—ฐ์‚ฐlistdeque์ง์ ‘ ๊ตฌํ˜„ํ•œ ๋งํฌ๋“œ ๋ฆฌ์ŠคํŠธ
๋์— ์ถ”๊ฐ€O(1) amortizedO(1)O(n) - ๋ ์ฐพ๊ธฐ
์•ž์— ์ถ”๊ฐ€O(n)O(1)O(1)
๋์—์„œ ์ œ๊ฑฐO(1)O(1)O(n) - ๋ ์ฐพ๊ธฐ
์•ž์—์„œ ์ œ๊ฑฐO(n)O(1)O(1)
์ธ๋ฑ์Šค ์ ‘๊ทผO(1)O(1)O(n)
๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ๋†’์Œ์ค‘๊ฐ„๋‚ฎ์Œ
์บ์‹œ ํšจ์œจ๋†’์Œ์ค‘๊ฐ„๋‚ฎ์Œ

๊ตฌํ˜„ ์˜ˆ์ œโ€‹

Pythonโ€‹

Class Node:
def __init__(self, val):
self.val = val
self.next = None # ๊ฐ ๋…ธ๋“œ๋Š” ์ž์‹ ์˜ ๊ฐ’๊ณผ ๋‹ค์Œ ๋…ธ๋“œ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ํฌ์ธํ„ฐ(next)๋ฅผ ๊ฐ€์ง„๋‹ค.

Class LinkedList:
def __init(self):
self.head = None # ๋ฆฌ์ŠคํŠธ ์ฒซ ๋ฒˆ์งธ ๋…ธ๋“œ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ํฌ์ธํ„ฐ

def insert_front(self, val):
node = Node(val)
node.next = self.head
self.head = node

def insert_back(self, val):
# ๋นˆ ๋ฆฌ์ŠคํŠธ๋ฉด head๋ฅผ ์ƒˆ ๋…ธ๋“œ๋กœ ์„ค์ •
if not self.head:
self.head = Node(val)
return

# cur์„ head์—์„œ ์‹œ์ž‘ํ•ด ๋๊นŒ์ง€ ์ด๋™ -> cur.next๊ฐ€ None์ด ๋  ๋•Œ๊นŒ์ง€
cur = self.head
while cur.next:
cur = cur.next
cur.next = Node(val)

def delete(self, val):
# ๋นˆ ๋ฆฌ์ŠคํŠธ์ธ ๊ฒฝ์šฐ ์ข…๋ฃŒ
if not self.head:
return
# head๊ฐ€ ์‚ญ์ œํ•  ๊ฐ’์ธ ๊ฒฝ์šฐ -> head๋ฅผ ๋‹ค์Œ ๋…ธ๋“œ๋กœ ๋ฐ”๊พธ๊ธฐ
if self.head.val == val:
self.head = self.head.next
return
# ์ง€๊ธˆ ๋ณด๊ณ  ์žˆ๋Š” ๋…ธ๋“œ(cur)์˜ next๊ฐ€ ์‚ญ์ œ ๋Œ€์ƒ์ธ์ง€ ํ™•์ธ ํ›„ ์‚ญ์ œํ•˜๋ฉด ํ•œ ์นธ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
cur = self.head
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
return
cur = cur.next

C++โ€‹

#include <iostream>

using namespace std;

struct Node {
int val;
Node* next;
Node(int v) : val(v), next(nullptr) {}
};

class LinkedList {
public:
Node* head;

LinkedList() : head(nullptr) {}

// ๋งจ ์•ž์— ์‚ฝ์ž…
void insert_front(int val) {
Node* node = new Node(val);
node->next = head;
head = node;
}

// ๋งจ ๋’ค์— ์‚ฝ์ž…
void insert_back(int val) {
if (head == nullptr) {
head = new Node(val);
return;
}
Node* cur = head;
while (cur->next != nullptr) {
cur = cur->next;
}
cur->next = new Node(val);
}

// ํŠน์ • ๊ฐ’ ์‚ญ์ œ
void delete_value(int val) {
if (head == nullptr)
return;

// head ์‚ญ์ œํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ
if (head->val == val) {
Node* temp = head;
head = head->next;
delete temp;
return;
}

// ๋‚˜๋จธ์ง€ ๋…ธ๋“œ ํƒ์ƒ‰
Node* cur = head;
while (cur->next != nullptr) {
if (cur->next->val == val) {
Node* temp = cur->next;
cur->next = cur->next->next;
delete temp;
return;
}
cur = cur->next;
}
}

// ๋ฆฌ์ŠคํŠธ ์ถœ๋ ฅ
void print() {
Node* cur = head;
while (cur != nullptr) {
cout << cur->val << " -> ";
cur = cur->next;
}
cout << "NULL\n";
}
};

int main() {
LinkedList list;

list.insert_front(3);
list.insert_front(2);
list.insert_front(1);

list.insert_back(4);
list.insert_back(5);

list.print(); // 1 -> 2 -> 3 -> 4 -> 5 -> NULL

list.delete_value(3);
list.print(); // 1 -> 2 -> 4 -> 5 -> NULL

list.delete_value(1);
list.print(); // 2 -> 4 -> 5 -> NULL

return 0;

Referencesโ€‹