দুটি অভিন্ন তালিকার কেন আলাদা মেমরির ছাপ রয়েছে?


155

আমি দুটি তালিকাগুলি তৈরি করেছি l1এবং l2প্রত্যেকটি আলাদা আলাদা পদ্ধতি তৈরি করে:

import sys

l1 = [None] * 10
l2 = [None for _ in range(10)]

print('Size of l1 =', sys.getsizeof(l1))
print('Size of l2 =', sys.getsizeof(l2))

তবে আউটপুট আমাকে অবাক করেছে:

Size of l1 = 144
Size of l2 = 192

একটি তালিকা বোধগম্যতার সাথে তৈরি তালিকাটি মেমরির বড় আকারের, তবে দুটি তালিকা পাইথনে অভিন্ন ident

কেন এমন? এটি কি কিছু সিপথনের অভ্যন্তরীণ জিনিস, না অন্য কোনও ব্যাখ্যা?


2
সম্ভবত, পুনরাবৃত্তি অপারেটর এমন কিছু ফাংশন শুরু করবে যা অন্তর্নিহিত অ্যারেটির ঠিক আকার দেয়। মনে রাখবেন, যে 144 == sys.getsizeof([]) + 8*10)যেখানে 8 একটি পয়েন্টার মাপ।
juanpa.arrivillaga

1
নোট করুন যে আপনি যদি এইটিতে পরিবর্তন 10করেন 11তবে [None] * 11তালিকার আকার থাকে 152তবে তালিকার বোধগমির আকার এখনও থাকে 192। আগের লিঙ্কযুক্ত প্রশ্নটি হুবহু নকল নয়, তবে এটি কেন ঘটে তা বোঝার ক্ষেত্রে এটি প্রাসঙ্গিক।
প্যাট্রিক হাহ

উত্তর:


162

আপনি যখন লিখবেন [None] * 10, পাইথন জানে যে এটির জন্য ঠিক 10 টি সামগ্রীর একটি তালিকা প্রয়োজন হবে, সুতরাং এটি ঠিক এটি বরাদ্দ করে।

আপনি যখন কোনও তালিকা বোধগম্যতা ব্যবহার করেন তখন পাইথন জানেন না যে এটির কতটা প্রয়োজন। সুতরাং এটি উপাদানগুলি যুক্ত হওয়ার সাথে সাথে তালিকায় ধীরে ধীরে বৃদ্ধি পায়। প্রতিটি পুনঃনির্ধারণের জন্য এটি তাত্ক্ষণিকভাবে প্রয়োজনের চেয়ে আরও বেশি জায়গা বরাদ্দ করে, যাতে এটি প্রতিটি উপাদানের জন্য পুনরায় প্রকাশ করতে না পারে। ফলাফলের তালিকাটি প্রয়োজনের তুলনায় কিছুটা বড় হতে পারে।

অনুরূপ আকারের সাথে তৈরি তালিকার তুলনা করার সময় আপনি এই আচরণটি দেখতে পারেন:

>>> sys.getsizeof([None]*15)
184
>>> sys.getsizeof([None]*16)
192
>>> sys.getsizeof([None for _ in range(15)])
192
>>> sys.getsizeof([None for _ in range(16)])
192
>>> sys.getsizeof([None for _ in range(17)])
264

আপনি দেখতে পাচ্ছেন যে প্রথম পদ্ধতিটি যা প্রয়োজন ঠিক তেমন বরাদ্দ দেয়, যখন দ্বিতীয়টি পর্যায়ক্রমে বৃদ্ধি পায়। এই উদাহরণে, এটি 16 টি উপাদানের জন্য পর্যাপ্ত পরিমাণে বরাদ্দ করে এবং 17 তম পৌঁছানোর সময় পুনরায় উদ্বেগ করতে হয়েছিল।


1
হ্যাঁ, এটা বোঝা যায়। *সামনের আকারটি যখন জানি তখন এটি সম্ভবত আরও ভাল তালিকা তৈরি করা ।
আন্দ্রেজ ক্যাসেলি 19

27
@ আন্দ্রেজকিজলি কেবলমাত্র আপনার তালিকায় [x] * nঅপরিবর্তনীয় ব্যবহার করুন x। ফলাফলের তালিকাটি অভিন্ন বস্তুর রেফারেন্স রাখবে।
schwobaseggl

5
@ sswwbaseggl ভাল, আপনি যা চান তা হতে পারে তবে এটি বুঝতে পেরে ভাল।
juanpa.arrivillaga

19
@ জুয়ানপা.আরিভিলাগা সত্য, এটি হতে পারে। তবে সাধারণত এটি হয় না এবং বিশেষত এসও সমস্ত পোস্টার ভরা ভেবে ভেবে কেন তাদের সমস্ত ডেটা একসাথে পরিবর্তিত হয়েছিল: ডি
শ্যাওব্যাসেগগল

50

এই প্রশ্নে উল্লিখিত হিসাবে তালিকা-অনুধাবন list.appendহুডের নীচে ব্যবহার করে, সুতরাং এটি তালিকা-পুনরায় আকারের পদ্ধতিটিকে কল করবে, যা সামগ্রিকভাবে উত্থাপন করে ।

এটি নিজের কাছে প্রদর্শনের জন্য, আপনি প্রকৃতপক্ষে disবিচ্ছিন্নকরণকারীটি ব্যবহার করতে পারেন :

>>> code = compile('[x for x in iterable]', '', 'eval')
>>> import dis
>>> dis.dis(code)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x10560b810, file "", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (iterable)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x10560b810, file "", line 1>:
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (x)
              8 LOAD_FAST                1 (x)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE
>>>

লক্ষ্য করুন LIST_APPENDএর disassembly মধ্যে opcode <listcomp>কোড অবজেক্ট। ডক্স থেকে :

LIST_APPEND (ঝ)

কল list.append(TOS[-i], TOS)। তালিকা বোঝার বাস্তবায়ন করতে ব্যবহৃত হয়।

এখন, তালিকা-পুনরাবৃত্তি অপারেশনের জন্য, আমাদের যদি বিবেচনা করা হয় তবে কী চলছে তা সম্পর্কে আমাদের একটি ইঙ্গিত রয়েছে:

>>> import sys
>>> sys.getsizeof([])
64
>>> 8*10
80
>>> 64 + 80
144
>>> sys.getsizeof([None]*10)
144

সুতরাং, মনে হয় এটি সঠিকভাবে বরাদ্দ করতে সক্ষম হবে । উত্স কোডের দিকে তাকালে আমরা দেখতে পাই ঠিক এটি ঘটে:

static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
    Py_ssize_t i, j;
    Py_ssize_t size;
    PyListObject *np;
    PyObject **p, **items;
    PyObject *elem;
    if (n < 0)
        n = 0;
    if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
        return PyErr_NoMemory();
    size = Py_SIZE(a) * n;
    if (size == 0)
        return PyList_New(0);
    np = (PyListObject *) PyList_New(size);

যেমন, এখানে: size = Py_SIZE(a) * n;। বাকি ফাংশনগুলি কেবল অ্যারে পূরণ করে।


"এই প্রশ্নে উল্লিখিত হিসাবে তালিকা-বোধগম্য তালিকাটি ব্যবহার করে the হুডের নীচে যুক্ত করুন" আমি মনে করি এটি ব্যবহার করে এটি আরও সঠিক বলে মনে হয় .extend()
15:42

@ অ্যাকচ্যামুলেশন আপনি কেন এমন বিশ্বাস করেন?
juanpa.arrivillaga

কারণ এটি একের পর এক উপাদান সংযোজন করছে না। আপনি যখন তালিকায় উপাদানগুলিকে যুক্ত করেন, আপনি সত্যিই একটি নতুন মেমরি বরাদ্দ সহ একটি নতুন তালিকা তৈরি করেন এবং সেই তালিকাটি সেই নতুন মেমরি বরাদ্দে রাখছেন। অন্যদিকে তালিকাভুক্তিগুলি ইতিমধ্যে বরাদ্দকৃত বেশিরভাগ নতুন উপাদানগুলিকে মেমরির মধ্যে রাখে এবং যখন তারা বরাদ্দ হওয়া মেমরির বাইরে চলে যায় তখন তারা কেবলমাত্র নতুন উপাদানটির জন্য যথেষ্ট নয়, মেমরির একটি অংশকে বরাদ্দ দেয়।
সংগৃহীত

7
@ অ্যাক্যাকিউমুলেশন এটি ভুল। list.appendএকটি নিয়মিত ধ্রুবক সময় ক্রিয়াকলাপ কারণ যখন কোনও তালিকা পুনরায় আকার দেয়, তখন এটি সামগ্রিকভাবে ঘটে। প্রতিটি সংযোজন ক্রিয়াকলাপ নয়, সুতরাং নতুন বরাদ্দকৃত অ্যারে ফলাফল। কোনো ঘটনা প্রশ্ন আমি শো আপনার সোর্স কোড যে আসলে, তালিকা comprehensions সংযুক্ত যে কি করতে ব্যবহার list.append। আমি এক মুহূর্তের মধ্যে আমার ল্যাপটপ পাওয়ার ফিরে আসব এবং আমি আপনি একটি তালিকা ধী জন্য অনেকত্রিত বাইটকোড এবং সংশ্লিষ্ট দেখাতে পারেন যে LIST_APPENDopcode
juanpa.arrivillaga

3

কোনওটিই মেমরির ব্লক নয়, তবে এটি পূর্বনির্ধারিত আকার নয়। তা ছাড়াও অ্যারের উপাদানগুলির মধ্যে একটি অ্যারেতে কিছু অতিরিক্ত ফাঁক রয়েছে। আপনি নিজেই এটি চালিয়ে দেখতে পারেন:

for ele in l2:
    print(sys.getsizeof(ele))

>>>>16
16
16
16
16
16
16
16
16
16

যা মোট l2 এর আকার নয়, বরং কম।

print(sys.getsizeof([None]))
72

এবং এটি আকারের দশমাংশের চেয়ে অনেক বেশি l1

আপনার অপারেটিং সিস্টেমের বিবরণ এবং আপনার অপারেটিং সিস্টেমের বর্তমান মেমরির ব্যবহারের বিশদ উপর নির্ভর করে আপনার সংখ্যাগুলি পৃথক হওয়া উচিত। [কিছুই নয়] আকারটি উপলব্ধ সংলগ্ন মেমরির চেয়ে বড় কখনই হতে পারে না যেখানে ভেরিয়েবলটি সংরক্ষণের জন্য সেট করা আছে, এবং যদি পরিবর্তনশীলটিকে পরে বড় হতে বরাদ্দ করা হয় তবে চলকটি সরানো হতে পারে।


1
Noneপ্রকৃতপক্ষে অন্তর্নিহিত অ্যারেতে সঞ্চিত নয়, কেবলমাত্র PyObjectপয়েন্টার (8 বাইট) সংরক্ষণ করা হয় । সমস্ত পাইথন অবজেক্টগুলি গাদাতে বরাদ্দ করা হয়েছে। Noneএটি একটি সিঙ্গলটন, তাই অনেকগুলি ননের সাথে একটি তালিকা থাকা সহজভাবে হ'ল একই Noneবস্তুর প্রতি পাইওবজেক্ট পয়েন্টারগুলির একটি অ্যারে তৈরি করে (এবং অতিরিক্ত প্রতিটি প্রক্রিয়াতে অতিরিক্ত মেমরি ব্যবহার না করে None)। আমি নিশ্চিত না আপনি কী বোঝাতে চেয়েছেন "কারও কাছে পূর্বনির্ধারিত আকার নেই", তবে এটি সঠিক শোনাচ্ছে না। অবশেষে, getsizeofপ্রতিটি উপাদানটির সাথে আপনার লুপটি প্রদর্শিত হচ্ছে যা আপনি মনে করছেন এটি প্রদর্শিত হচ্ছে।
juanpa.arrivillaga

যদি আপনি সত্য বলে থাকেন তবে [কিছুই নয়] * 10 এর আকার [কিছুই নয়] এর আকারের মতো হওয়া উচিত। তবে স্পষ্টত এটি তাই নয় - কিছু অতিরিক্ত স্টোরেজ যুক্ত করা হয়েছে। প্রকৃতপক্ষে, দশবার (160) পুনরাবৃত্ত [[কিছুই নয়] এর আকারও দশটি দ্বারা গুণিত [কিছুই নয়] এর আকারের চেয়ে কম। আপনি যেমন উল্লেখ করেছেন, পরিষ্কারভাবে পয়েন্টারটির আকার [কিছুই নয়] নিজেই [কিছুই নয়] এর আকারের চেয়ে ছোট (72 বাইটের চেয়ে 16 বাইট)। যাইহোক, 160 + 32 হল 192 I আমার মনে হয় না পূর্ববর্তী উত্তরটি সমস্যার সম্পূর্ণ সমাধান করে ves এটি স্পষ্ট যে অতিরিক্ত অতিরিক্ত কিছু পরিমাণ মেমরি (সম্ভবত মেশিনের স্টেট নির্ভর) বরাদ্দ করা হয়েছে।
স্টিভেনজেডি

"যদি আপনি সত্য বলে থাকেন তবে [কিছুই নয়] * 10 এর আকার [কিছুই নয়] এর আকারের সমান হওয়া উচিত" আমি কী বলছি যা সম্ভবত এটি বোঝাতে পারে? আবার, আপনি এই বিষয়টির প্রতি মনোনিবেশ করছেন বলে মনে হয় যে অন্তর্নিহিত বাফারটি অতিরিক্ত বরাদ্দ করা হয়েছে, বা তালিকার আকারটিতে অন্তর্নিহিত বাফারের আকারের চেয়ে বেশি কিছু রয়েছে (এটি অবশ্যই করে) তবে এটি এর বিন্দু নয় এই প্রশ্ন. আবার, আপনার ব্যবহার gestsizeofপ্রতিটি eleএর l2বিভ্রান্তিকর কারণ getsizeof(l2) একাউন্টে ধারক ভিতরে উপাদানের আকার লাগবে না
juanpa.arrivillaga

নিজেকে শেষ দাবী প্রমাণ করার জন্য, l1 = [None]; l2 = [None]*100; l3 = [l2]তখনই করুন print(sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3))। তোমার মত ফলাফলের পাবেন: 72 864 72। অর্থাৎ যথাক্রমে 64 + 1*8, 64 + 100*8এবং 64 + 1*8, আবার, 8 বাইট পয়েন্টার আকার সঙ্গে একটি 64bit সিস্টেম অভিমানী।
juanpa.arrivillaga

1
আমি যেমন বলেছি, sys.getsizeof* ধারকটিতে থাকা আইটেমগুলির আকারের জন্য অ্যাকাউন্ট হয় না। দস্তাবেজগুলি থেকে : "কেবলমাত্র অবজেক্টের সাথে সরাসরি দায়ী মেমোরির খরচ গণনা করা হয়, বস্তুগুলির মেমরি গ্রাহ্য নয় যা এটি উল্লেখ করা হয় ... পাত্রে আকারের সন্ধান করার জন্য গেটসাইফ () পুনরাবৃত্তভাবে ব্যবহারের উদাহরণের জন্য পুনরাবৃত্ত আকারের রেসিপিটি দেখুন এবং তাদের সমস্ত বিষয়বস্তু। "
juanpa.arrivillaga
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.