পাইথনের অ্যারেগুলি ধীর কেন?


153

আমি array.arrayতালিকাগুলির চেয়ে দ্রুততর হওয়ার প্রত্যাশা করলাম , যেমন অ্যারেগুলি আনবক্সড করা আছে।

তবে, আমি নিম্নলিখিত ফলাফল পেতে:

In [1]: import array

In [2]: L = list(range(100000000))

In [3]: A = array.array('l', range(100000000))

In [4]: %timeit sum(L)
1 loop, best of 3: 667 ms per loop

In [5]: %timeit sum(A)
1 loop, best of 3: 1.41 s per loop

In [6]: %timeit sum(L)
1 loop, best of 3: 627 ms per loop

In [7]: %timeit sum(A)
1 loop, best of 3: 1.39 s per loop

এমন পার্থক্যের কারণ কী হতে পারে?


4
নম্পী সরঞ্জামগুলি দক্ষতার সাথে আপনার অ্যারের কাজে লাগাতে পারে:% টাইমাইট এনপি.সুম (এ): 100 লুপ, 3 লুপ প্রতি 8.87 এমএসের সেরা
বিএম

6
arrayপ্যাকেজটি ব্যবহার করার দরকার পড়ে এমন পরিস্থিতি আমি কখনই পারিনি । যদি আপনি উল্লেখযোগ্য পরিমাণে গণিত করতে চান তবে নম্পি হালকা গতিতে কাজ করে (অর্থাত সি), এবং সাধারণত জিনিসগুলির নির্বিকার প্রয়োগের চেয়ে ভাল sum()
নিক টি

40
ঘনিষ্ঠ ভোটাররা: কেন এই মতামত ভিত্তিক? ওপি একটি পরিমাপযোগ্য এবং পুনরাবৃত্তিযোগ্য ঘটনা সম্পর্কে একটি নির্দিষ্ট, প্রযুক্তিগত প্রশ্ন জিজ্ঞাসা করছে বলে মনে হচ্ছে।
কেভিন

5
@NickT পড়ুন একটি অপ্টিমাইজেশান উপাখ্যানarrayপরিসংখ্যান (ASCII বাইট উপস্থাপন করে) একটি strবস্তুতে পূর্ণসংখ্যার একটি স্ট্রিং রূপান্তর করার ক্ষেত্রে খুব দ্রুত is গুইডো নিজেই কেবল অন্যান্য প্রচুর সমাধানের পরে এটি নিয়ে এসেছিলেন এবং অভিনয়টি দেখে বেশ অবাক হয়েছিলেন। যাইহোক এটিই একমাত্র জায়গা যেখানে আমি মনে করি এটি দরকারী হিসাবে দেখা। numpyঅ্যারে নিয়ে কাজ করার জন্য অনেক ভাল তবে এটি একটি তৃতীয় পক্ষের নির্ভরতা।
বাকুরিউ

উত্তর:


220

স্টোরেজ "unboxed", কিন্তু প্রত্যেক সময় আপনি "বক্স" করার একটি উপাদান পাইথন অ্যাক্সেসের এটা যাতে এটা নিয়ে কিছু করতে হবে (ক নিয়মিত পাইথন বস্তুর এটা এম্বেড)। উদাহরণস্বরূপ, sum(A)অ্যারের উপরে আপনার পুনরাবৃত্তিগুলি এবং প্রতিটি পাইকন intঅবজেক্টে প্রতিটি পূর্ণসংখ্যককে একবারে একটি করে বাক্স করে । যে সময় ব্যয়। আপনারsum(L) , তালিকা তৈরির সময় সমস্ত বক্সিং সম্পন্ন হয়েছিল।

সুতরাং, শেষে, একটি অ্যারে সাধারণত ধীর হয়, তবে যথেষ্ট কম মেমরির প্রয়োজন হয়।


পাইথন 3 এর সাম্প্রতিক সংস্করণ থেকে প্রাসঙ্গিক কোডটি এখানে রয়েছে, তবে পাইথন প্রথম প্রকাশিত হবার পরে একই সিপাইথন বাস্তবায়নের জন্য একই মূল ধারণাগুলি প্রয়োগ হয় ideas

একটি তালিকা আইটেম অ্যাক্সেস কোড এখানে:

PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
    /* error checking omitted */
    return ((PyListObject *)op) -> ob_item[i];
}

এর খুব সামান্যই রয়েছে: somelist[i]কেবল তালিকার i'তম অবজেক্টটি ফিরিয়ে দেয় (এবং সিপিথনের সমস্ত পাইথন অবজেক্টগুলি এমন কাঠামোর দিকে নির্দেশিত যার প্রাথমিক বিভাগটি একটি বিন্যাসের সাথে সঙ্গতিপূর্ণ struct PyObject)।

এবং টাইপ কোড সহ __getitem__একটিটির জন্য এখানে প্রয়োগকরণ :arrayl

static PyObject *
l_getitem(arrayobject *ap, Py_ssize_t i)
{
    return PyLong_FromLong(((long *)ap->ob_item)[i]);
}

কাঁচা মেমরি প্ল্যাটফর্ম-দেশীয় C longপূর্ণসংখ্যার ভেক্টর হিসাবে গণ্য করা হয় ; i'ম C longপর্যন্ত পড়েছেন হয়; এবং তারপরে পাইথন অবজেক্টে PyLong_FromLong()নেটিভকে ("বাক্স") মোড়ানোর জন্য বলা হয় (যা পাইথন 3 এ, যা পাইথন 2 এর পার্থক্য দূর করে এবং আসলে টাইপ হিসাবে দেখানো হয় )।C longlongintlongint

এই বক্সিংটি একটি পাইথন intঅবজেক্টের জন্য নতুন মেমরি বরাদ্দ করতে হবে এবং এতে নেটিভের C longবিটগুলি স্প্রে করতে হবে। মূল উদাহরণের প্রসঙ্গে, এই অবজেক্টটির জীবনকাল খুব সংক্ষিপ্ত ( sum()চলমান সামগ্রীতে সামগ্রী যুক্ত করার জন্য যথেষ্ট দীর্ঘ ) এবং তারপরে নতুন intঅবজেক্টটি ডিলেক্ট করার জন্য আরও বেশি সময় প্রয়োজন ।

এখান থেকেই গতির পার্থক্যটি সর্বদা থেকে এসেছে এবং সিপিথন বাস্তবায়নে সর্বদা আসে।


87

টিম পিটার্সের দুর্দান্ত উত্তরে যুক্ত করতে অ্যারেগুলি বাফার প্রোটোকল প্রয়োগ করে , তবে তালিকাগুলি তা দেয় না। এর অর্থ হ'ল, আপনি যদি সি এক্সটেনশন (বা নৈতিক সমতুল্য, যেমন একটি সিথন মডিউল লেখার জন্য ) লিখছেন তবে আপনি পাইথন যা কিছু করতে পারেন তার থেকে খুব দ্রুত অ্যারের উপাদানগুলির সাথে অ্যাক্সেস করতে এবং কাজ করতে পারবেন। এটি আপনাকে প্রশস্ততার ক্রম ছাড়িয়ে যথেষ্ট গতি উন্নতি করবে। তবে এর বেশ কয়েকটি ডাউনসাইড রয়েছে:

  1. আপনি এখন পাইথনের পরিবর্তে সি লেখার ব্যবসায় রয়েছেন। এটিকে প্রশমিত করার জন্য সিথন হ'ল এক উপায়, তবে এটি ভাষার মধ্যে বহু মৌলিক পার্থক্য দূর করে না; আপনাকে সি শব্দার্থবিদ্যার সাথে পরিচিত হওয়া এবং এটি কী করছে তা বুঝতে হবে।
  2. পাইপাইয়ের সি এপিআই কিছুটা কাজ করে তবে খুব দ্রুত নয়। আপনি যদি পাইপিকে টার্গেট করে থাকেন তবে আপনার সম্ভবত নিয়মিত তালিকাগুলি সহ সাধারণ কোডটি লিখতে হবে এবং তারপরে জেআইটিটার এটি আপনার জন্য অনুকূলিত করতে দিন।
  3. সি এক্সটেনশানগুলি বিশুদ্ধ পাইথন কোডের চেয়ে বিতরণ করা আরও শক্ত কারণ তাদের সংকলন করা দরকার। সংকলন আর্কিটেকচার এবং অপারেটিং সিস্টেম নির্ভর করে, তাই আপনার লক্ষ্য প্ল্যাটফর্মের জন্য আপনি সংকলন করছেন তা নিশ্চিত করতে হবে।

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


10

টিম পিটারস উত্তর দিয়েছেন কেন এটি ধীর, তবে আসুন কীভাবে এটি উন্নত করা যায় তা দেখুন।

আপনার উদাহরণের সাথে লেগে থাকা sum(range(...))(এখানে স্মৃতিতে ফিট করার জন্য আপনার উদাহরণের চেয়ে 10 গুণক কম):

import numpy
import array
L = list(range(10**7))
A = array.array('l', L)
N = numpy.array(L)

%timeit sum(L)
10 loops, best of 3: 101 ms per loop

%timeit sum(A)
1 loop, best of 3: 237 ms per loop

%timeit sum(N)
1 loop, best of 3: 743 ms per loop

এইভাবে নাম্পিকে বাক্স / আনবক্স করতে হবে, যার অতিরিক্ত ওভারহেড রয়েছে। এটিকে দ্রুত করতে একটি নামি সি কোডের মধ্যে থাকতে হবে:

%timeit N.sum()
100 loops, best of 3: 6.27 ms per loop

সুতরাং তালিকা সমাধান থেকে নিমপি সংস্করণে এটি রানটাইমের 16 টি ফ্যাক্টর।

আসুন সেই ডেটা স্ট্রাকচারগুলি তৈরি করতে কত সময় লাগে তাও পরীক্ষা করে দেখুন

%timeit list(range(10**7))
1 loop, best of 3: 283 ms per loop

%timeit array.array('l', range(10**7))
1 loop, best of 3: 884 ms per loop

%timeit numpy.array(range(10**7))
1 loop, best of 3: 1.49 s per loop

%timeit numpy.arange(10**7)
10 loops, best of 3: 21.7 ms per loop

সাফ বিজয়ী: নম্পি

এছাড়াও নোট করুন যে ডেটা স্ট্রাকচার তৈরি করতে সংমিশ্রণের যত বেশি সময় লাগে, যদি বেশি না হয়। বরাদ্দ মেমরি ধীর।

এগুলির স্মৃতি ব্যবহার:

sys.getsizeof(L)
90000112
sys.getsizeof(A)
81940352
sys.getsizeof(N)
80000096

সুতরাং এগুলি বিভিন্ন ওভারহেডের সাথে প্রতি সংখ্যা 8 বাইট নেয়। পরিসীমাটির জন্য আমরা 32 বিট ইনট ব্যবহার করি, তাই আমরা কিছু মেমরি নিরাপদ রাখতে পারি।

N=numpy.arange(10**7, dtype=numpy.int32)

sys.getsizeof(N)
40000096

%timeit N.sum()
100 loops, best of 3: 8.35 ms per loop

তবে দেখা যাচ্ছে যে machine৪ বিট ইনট যুক্ত করা আমার মেশিনে 32 বিট ইনটগুলির চেয়ে দ্রুততর, সুতরাং আপনি যদি মেমরি / ব্যান্ডউইথ দিয়ে সীমাবদ্ধ থাকেন তবে এটি কেবল মূল্যবান।


-1

দয়া করে নোট করুন যে 100000000সমান 10^8নয় 10^7এবং আমার ফলাফলগুলি ফলোউইউইংয়ের মতো:

100000000 == 10**8

# my test results on a Linux virtual machine:
#<L = list(range(100000000))> Time: 0:00:03.263585
#<A = array.array('l', range(100000000))> Time: 0:00:16.728709
#<L = list(range(10**8))> Time: 0:00:03.119379
#<A = array.array('l', range(10**8))> Time: 0:00:18.042187
#<A = array.array('l', L)> Time: 0:00:07.524478
#<sum(L)> Time: 0:00:01.640671
#<np.sum(L)> Time: 0:00:20.762153
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.