[* A] අධික ලෙස ස්ථානගත වීමට හේතු මොනවාද?


136

පැහැදිලිවම list(a), overallocate නැති [x for x in a]සමහර ස්ථානවල overallocates, සහ [*a]overallocates සියලු කාලය ?

N = 100 දක්වා ප්‍රමාණ

මෙන්න 0 සිට 12 දක්වා ප්‍රමාණයේ n සහ එහි ප්‍රති ing ලයක් ලෙස ක්‍රම තුන සඳහා බයිට් වලින්:

0 56 56 56
1 64 88 88
2 72 88 96
3 80 88 104
4 88 88 112
5 96 120 120
6 104 120 128
7 112 120 136
8 120 120 152
9 128 184 184
10 136 184 192
11 144 184 200
12 152 184 208

මේ ආකාරයට ගණනය කර ඇති අතර , පයිතන් 3 භාවිතා කරමින් repl.it හි ප්‍රජනනය කළ හැකිය . 8 :

from sys import getsizeof

for n in range(13):
    a = [None] * n
    print(n, getsizeof(list(a)),
             getsizeof([x for x in a]),
             getsizeof([*a]))

ඉතින්: මෙය ක්‍රියාත්මක වන්නේ කෙසේද? [*a]සමස්තයක් ලෙස ස්ථානගත වන්නේ කෙසේද ? ඇත්ත වශයෙන්ම, දී ඇති ආදානයෙන් ප්‍රති result ල ලැයිස්තුව නිර්මාණය කිරීමට එය භාවිතා කරන යාන්ත්‍රණය කුමක්ද? එය නැවත ඉරේටරයක් aභාවිතා කර එවැනි දෙයක් භාවිතා list.appendකරයිද? ප්‍රභව කේතය කොහිද?

( රූප නිපදවූ දත්ත සහ කේත සමඟ කොලබ් .)

කුඩා n වෙත විශාලනය කිරීම:

N = 40 දක්වා ප්‍රමාණ

විශාල n වෙත විශාලනය කිරීම:

N = 1000 දක්වා ප්‍රමාණ


1
Fwiw, ඔබේ පරීක්ෂණ අවස්ථා දීර් ing කිරීමෙන් ලැයිස්තු අවබෝධය හැසිරෙන්නේ ලූපයක් ලිවීම හා එක් එක් අයිතමය ලැයිස්තුවට එකතු [*a]කිරීම ලෙස extendවන අතර හිස් ලැයිස්තුවක් භාවිතා කරන ලෙස පෙනේ .
jdehesa

4
එක් එක් සඳහා ජනනය කරන ලද බයිට් කේතය බැලීමට එය උදව් වනු ඇත. list(a)සම්පූර්ණයෙන්ම C හි ක්‍රියාත්මක වේ; එය අභ්‍යන්තර බෆරයේ නෝඩය නෝඩ් මගින් වෙන් කළ හැකිය a. [x for x in a]හුදෙක් භාවිතා LIST_APPENDඑය සාමාන්ය ලැයිස්තුව රටාවක් "අවශ්ය වූ විට නැවත වෙන්, ටිකක් overallocate" සාමාන්යය පහත නිසා, ගොඩක්. [*a]භාවිතා කරයි BUILD_LIST_UNPACK, කුමන ... පෙනෙන හැටියට සෑම විටම අධික ලෙස වෙන් කිරීම හැර, එය කරන්නේ කුමක්දැයි මම නොදනිමි :)
චෙප්නර්

2
එසේම, Python 3.7 දී, එය කරන බව පෙනී යයි list(a)හා [*a]සමාන වන අතර, දෙකම overallocate සාපේක්ෂව [x for x in a]ඉතින් ..., sys.getsizeofමෙතන භාවිතා කිරීමේ අයිතිය මෙවලමක් විය නොහැකි විය.
චෙප්නර්

7
p චෙප්නර් sys.getsizeofනිවැරදි මෙවලම යැයි මම සිතමි , එය පෙන්නුම් කරන්නේ අධික ලෙස ස්ථානගත කිරීම සඳහා list(a)භාවිතා කළ බවයි. පයිතන් 3.8 හි ඇත්ත වශයෙන්ම අළුත් දේ එය සඳහන් කරයි: "ලැයිස්තු සාදන්නා අධික ලෙස ස්ථානගත නොකරයි [...]" .
ස්ටෙෆාන් පොච්මන්

5
@ චෙප්නර්: එය 3.8 හි නිවැරදි කරන ලද දෝෂයකි ; ඉදිකිරීම්කරු අධික ලෙස ස්ථානගත කිරීම අවශ්‍ය නොවේ.
ෂැඩෝ රේන්ජර්

Answers:


82

[*a] අභ්‍යන්තරව C ට සමාන වන්නේ :

  1. නව හිස් එකක් සාදන්න list
  2. අමතන්න newlist.extend(a)
  3. ප්‍රතිලාභ list.

එබැවින් ඔබ ඔබේ පරීක්ෂණය පුළුල් කරන්නේ නම්:

from sys import getsizeof

for n in range(13):
    a = [None] * n
    l = []
    l.extend(a)
    print(n, getsizeof(list(a)),
             getsizeof([x for x in a]),
             getsizeof([*a]),
             getsizeof(l))

එය මාර්ගගතව උත්සාහ කරන්න!

ප්‍රති for ල ඔබ දකිනු ඇති getsizeof([*a])අතර l = []; l.extend(a); getsizeof(l)ඒවා සමාන වේ.

මෙය සාමාන්‍යයෙන් කළ යුතු නිවැරදි දෙයයි; විට extending ඔබ සාමාන්යයෙන් වැඩි පසුව එකතු කිරීමට බලාපොරොත්තු, හා සමානව සාමාන්යකරනය විහිදු සඳහා කරන්නේ, එය බහු දේවල් එකින් එක එකතු වනු ඇත බව උපකල්පනය කරනවා. [*a]සාමාන්‍ය අවස්ථාව නොවේ; පයිතන් උපකල්පනය කරන්නේ list( [*a, b, c, *d]) වෙත බහුවිධ අයිතම හෝ පුනරාවර්තන එකතු වන බවයි , එබැවින් පොදු ස්ථානගත කිරීම් වලදී වැඩිපුර ස්ථානගත කිරීම ඉතිරි වේ.

ඊට හාත්පසින්ම වෙනස්ව, listභාවිතා කරන විට තනි, නියම කළ හැකි (සමඟ list()) ඉදිකරන ලද ඒවා වර්ධනය හෝ හැකිලෙන්නේ නැත. පයිතන් මෑතකදී දෝෂයක් නිරාකරණය කර ඇති අතර එමඟින් දන්නා ප්‍රමාණයේ යෙදවුම් සඳහා පවා ඉදිකිරීම්කරු සමස්තයක් ලෙස ස්ථානගත කර ඇත.

listඅවබෝධය සඳහා , ඒවා effectively ලදායි ලෙස පුනරාවර්තනයට සමාන වේ append, එබැවින් වරකට මූලද්‍රව්‍යයක් එකතු කිරීමේදී සාමාන්‍ය සමස්ත වර්ධන රටාවේ අවසාන ප්‍රති result ලය ඔබට පෙනේ.

පැහැදිලිව කිවහොත්, මේ කිසිවක් භාෂා සහතිකයක් නොවේ. එය CPython ක්‍රියාත්මක කරන ආකාරයයි. පයිතන් භාෂා පිරිවිතර සාමාන්‍යයෙන් නිශ්චිත වර්ධන රටාවන් සමඟ නොගැලපේ list(ක්‍රම ක්‍රමයෙන් O(1) appendසහ popඅවසානයේ සිට සහතික කිරීම හැරුණු කොට ). අදහස් දැක්වීම්වල සඳහන් කර ඇති පරිදි, නිශ්චිත ක්‍රියාත්මක කිරීම නැවත 3.9 කින් වෙනස් වේ; එය බාධාවක් නොවන අතර [*a], එය එහිදී විය කිරීම සඳහා භාවිතා දේ වෙනත් අවස්ථාවලදී බලපෑ හැකි "තාවකාලික ඉදි tupleතනි භාණ්ඩ හා පසුව extendසමග tuple" දැන් බහු අයදුම්පත් බවට පත් LIST_APPENDවූ overallocation සිදුවන දේ සංඛ්යා ගණනය යන්න විට වෙනස් කල හැකි,.


4
Te ස්ටෙෆාන් පොච්මන්: මම මීට පෙර කේතය කියෙව්වා (ඒ නිසා මම මෙය දැනටමත් දැන සිටියෙමි). මෙය බයිට් කේත හසුරුවන්නා වන අතරBUILD_LIST_UNPACK , එය ඇමතුමට _PyList_Extendසමාන C ලෙස භාවිතා කරයි extend(ක්‍රමයෙන් බැලීමෙන් නොව කෙලින්ම). ඔවුන් එය ඉවත් කිරීම සමඟ ගොඩ නැගීමේ මාර්ග සමඟ ඒකාබද්ධ කළහ tuple; tupleකෑලි ගොඩනැඟීම සඳහා මනාව තක්සේරු නොකරන්න, එබැවින් ඒවා සෑම විටම list(සමස්ත වෙන්කිරීමේ ප්‍රතිලාභ ලබා ගැනීම සඳහා) ඉවත් කර, tupleඉල්ලූ විට අවසානයේ පරිවර්තනය වේ .
ෂැඩෝ රේන්ජර්

4
මෙය 3.9 හි වෙනස් වන බව සලකන්න , මෙහි ඉදිකිරීම් වෙනම බයිට්කෝඩ් වලින් ( BUILD_LIST, LIST_EXTENDසෑම දෙයක්ම ඉවත් කිරීම LIST_APPENDසඳහා, තනි අයිතම සඳහා), listතනි බයිට් කේත උපදෙස් සහිතව සමස්තයක් තැනීමට පෙර තොගයේ ඇති සියල්ල පැටවීම වෙනුවට (එය ඉඩ දෙයි සියලු-in-එක් උපදෙස් ක්රියාත්මක කිරීම මෙන් ඉඩ දුන්නේ නැති බවය ප්රතිස්තිකරණය ඉටු කිරීමට සම්පාදක [*a, b, *c]ලෙස LIST_EXTEND, LIST_APPEND, LIST_EXTENDඔතාගෙන අවශ්ය o w / bඉතා සීමා පනවා ඇති tupleඅවශ්යතා සපුරාලීම සඳහා BUILD_LIST_UNPACK).
ෂැඩෝ රේන්ජර්

18

සිදුවන්නේ කුමක්ද යන්න පිළිබඳ සම්පූර්ණ පින්තූරය , අනෙක් පිළිතුරු සහ අදහස් මත ගොඩනැඟීම (විශේෂයෙන් ෂැඩෝ රේන්ජර්ගේ පිළිතුර , එය එසේ සිදු වූයේ ඇයිද යන්න පැහැදිලි කරයි ).

BUILD_LIST_UNPACKභාවිතයට ගන්නා සංදර්ශන විසුරුවා හැරීම :

>>> import dis
>>> dis.dis('[*a]')
  1           0 LOAD_NAME                0 (a)
              2 BUILD_LIST_UNPACK        1
              4 RETURN_VALUE

එය හසුරුවනු ලබනceval.c අතර එය හිස් ලැයිස්තුවක් ගොඩනඟා එය (සමග a) දිගු කරයි :

        case TARGET(BUILD_LIST_UNPACK): {
            ...
            PyObject *sum = PyList_New(0);
              ...
                none_val = _PyList_Extend((PyListObject *)sum, PEEK(i));

_PyList_Extend භාවිතා කරයි list_extend :

_PyList_Extend(PyListObject *self, PyObject *iterable)
{
    return list_extend(self, iterable);
}

කුමන ඉල්ලා list_resizeඇති ප්රමාණ එකතුව සමග :

list_extend(PyListObject *self, PyObject *iterable)
    ...
        n = PySequence_Fast_GET_SIZE(iterable);
        ...
        m = Py_SIZE(self);
        ...
        if (list_resize(self, m + n) < 0) {

එය සමස්තයක් ලෙස පහත පරිදි වේ:

list_resize(PyListObject *self, Py_ssize_t newsize)
{
  ...
    new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);

අපි එය පරීක්ෂා කරමු. ඉහත සූත්‍රය සමඟ අපේක්ෂිත ලප ගණන ගණනය කරන්න, සහ අපේක්ෂිත බයිට් ප්‍රමාණය 8 කින් ගුණ කිරීමෙන් ගණනය කරන්න (මම මෙහි 64-බිට් පයිතන් භාවිතා කරන බැවින්) සහ හිස් ලැයිස්තුවේ බයිට් ප්‍රමාණය එකතු කරන්න (එනම්, ලැයිස්තු වස්තුවක නියත පොදු) :

from sys import getsizeof
for n in range(13):
    a = [None] * n
    expected_spots = n + (n >> 3) + (3 if n < 9 else 6)
    expected_bytesize = getsizeof([]) + expected_spots * 8
    real_bytesize = getsizeof([*a])
    print(n,
          expected_bytesize,
          real_bytesize,
          real_bytesize == expected_bytesize)

ප්‍රතිදානය:

0 80 56 False
1 88 88 True
2 96 96 True
3 104 104 True
4 112 112 True
5 120 120 True
6 128 128 True
7 136 136 True
8 152 152 True
9 184 184 True
10 192 192 True
11 200 200 True
12 208 208 True

හැර තරග n = 0, වන list_extendඇත්තටම කෙටි මං , ඒ නිසා ඇත්තටම ඒ තරග ද:

        if (n == 0) {
            ...
            Py_RETURN_NONE;
        }
        ...
        if (list_resize(self, m + n) < 0) {

8

මේවා CPython පරිවර්තකයේ ක්‍රියාත්මක කිරීමේ තොරතුරු වන අතර අනෙක් පරිවර්තකයන් අතර නොගැලපේ.

එමඟින්, අවබෝධය සහ list(a)හැසිරීම් මෙහි පැමිණෙන්නේ කොතැනින්දැයි ඔබට දැක ගත හැකිය :

https://github.com/python/cpython/blob/master/Objects/listobject.c#L36

අවබෝධය සඳහා විශේෂයෙන්:

 * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
...

new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);

එම රේඛාවලට මදක් පහළින්, ඇමතීමේදී list_preallocate_exactභාවිතා list(a)වේ.


1
[*a]එක් වරකට තනි මූලද්‍රව්‍ය එකතු නොකරයි. එයට තමන්ගේම කැපවූ බයිට් කේතයක් ඇත, එය තොග වශයෙන් ඇතුළු කරයි extend.
ෂැඩෝ රේන්ජර්

ගොචා - මම හිතන්නේ මම ඒ ගැන වැඩි දුරක් හාරා නැහැ. කොටස ඉවත් කර ඇත[*a]
රැන්ඩි
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.