পাইথন 3.x পূর্ণসংখ্যার জন্য বিট-শিফ্টের চেয়ে টাইমস-টু দ্রুত?


150

আমি বাছাই করা_সামগ্রীগুলির উত্সটির দিকে চেয়ে ছিলাম এবং এই লাইনটি দেখে অবাক হয়েছি :

self._load, self._twice, self._half = load, load * 2, load >> 1

এখানে loadএকটি পূর্ণসংখ্যা কেন এক জায়গায় বিট শিফট এবং অন্য জায়গায় গুণ? এটি যুক্তিসঙ্গত বলে মনে হচ্ছে যে বিট স্থানান্তরটি 2 দ্বারা অবিচ্ছেদ্য বিভাগের চেয়ে দ্রুততর হতে পারে, তবে কেন একটি শিফ্ট দ্বারা গুণনটি প্রতিস্থাপন করবেন না? আমি নিম্নলিখিত বিষয়গুলিকে বেঞ্চমার্ক করেছি:

  1. (বার, ভাগ)
  2. (শিফট, শিফট)
  3. (বার, শিফট)
  4. (শিফট, বিভাজন)

এবং দেখা গেছে যে # 3 অন্যান্য বিকল্পের তুলনায় ধারাবাহিকভাবে দ্রুত:

# self._load, self._twice, self._half = load, load * 2, load >> 1

import random
import timeit
import pandas as pd

x = random.randint(10 ** 3, 10 ** 6)

def test_naive():
    a, b, c = x, 2 * x, x // 2

def test_shift():
    a, b, c = x, x << 1, x >> 1    

def test_mixed():
    a, b, c = x, x * 2, x >> 1    

def test_mixed_swapped():
    a, b, c = x, x << 1, x // 2

def observe(k):
    print(k)
    return {
        'naive': timeit.timeit(test_naive),
        'shift': timeit.timeit(test_shift),
        'mixed': timeit.timeit(test_mixed),
        'mixed_swapped': timeit.timeit(test_mixed_swapped),
    }

def get_observations():
    return pd.DataFrame([observe(k) for k in range(100)])

এখানে চিত্র বর্ণনা লিখুন এখানে চিত্র বর্ণনা লিখুন

প্রশ্নটি:

আমার পরীক্ষা কি বৈধ? যদি তা হয় তবে (শিফট, শিফট) তুলনায় কেন (গুন, শিফট) দ্রুত?

আমি উবুন্টু 14.04 এ পাইথন 3.5 চালাচ্ছি।

সম্পাদন করা

উপরে প্রশ্নের মূল বক্তব্য রয়েছে। ড্যান গেটেজ তার উত্তরে একটি দুর্দান্ত ব্যাখ্যা সরবরাহ করে।

সম্পূর্ণতার স্বার্থে, xগুণনের অপ্টিমাইজেশন প্রয়োগ না করা হলে বৃহত্তর জন্য নমুনার চিত্রগুলি এখানে দেওয়া হয় ।

এখানে চিত্র বর্ণনা লিখুন এখানে চিত্র বর্ণনা লিখুন


3
আপনি কোথায় সংজ্ঞা দিয়েছেন x?
জেবার্নার্ডো

3
আমি দেখতে চাই যে সামান্য এন্ডিয়ান / বিগ এন্ডিয়ান ব্যবহার করে কোনও পার্থক্য রয়েছে কিনা। সত্যিই দুর্দান্ত প্রশ্ন বিটিডব্লিউ!
LiGhTx117

1
@ LiGhTx117 আমি প্রত্যাশা করব যে অপারেশনগুলির সাথে এটি সম্পর্কিত নয়, যদি না xখুব বড় হয়, কারণ এটি ঠিক কীভাবে এটি মেমরির মধ্যে সংরক্ষণ করা হয় একটি প্রশ্ন, তাই না?
ড্যান গেটেজ

1
আমি কৌতূহলী, 2 দিয়ে ভাগ করার পরিবর্তে 0.5 দ্বারা গুণা সম্পর্কে কী? মিপস অ্যাসেমব্লিং প্রোগ্রামিংয়ের সাথে পূর্ববর্তী অভিজ্ঞতা থেকে বিভাগটি সাধারণত কোনওভাবেই একটি গুণ গুণায় ফলাফল দেয়। (এটি বিভাগের পরিবর্তে বিট
শিফটিংয়ের

2
@ সয়েস যা এটি ভাসমান স্থানে রূপান্তরিত করবে। আশা করি পূর্ণস্থল তল বিভাগটি ভাসমান পয়েন্টের মাধ্যমে বৃত্তাকার ভ্রমণের চেয়ে দ্রুততর হবে।
ড্যান গেটেজ

উত্তর:


155

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

পাইথনের পূর্ণসংখ্যাগুলি নির্বিচারে-নির্ভুলতা হওয়ায় এগুলি পূর্ণসংখ্যার প্রতি বিটের সংখ্যার সীমাবদ্ধ করে পূর্ণসংখ্যার "অঙ্কগুলি" এর অ্যারে হিসাবে সংরক্ষণ করা হয়। সুতরাং সাধারণ ক্ষেত্রে, পূর্ণসংখ্যার সাথে জড়িত ক্রিয়াকলাপগুলি একক ক্রিয়াকলাপ নয়, পরিবর্তে একাধিক "অঙ্ক" এর ক্ষেত্রে পরিচালনা করা প্রয়োজন। ইন pyport.h , এই বিট সীমা হিসাবে সংজ্ঞায়িত করা হয় অন্যথায় 64 বিট প্ল্যাটফর্মের উপর 30 বিট, বা 15 বিট। (ব্যাখ্যাটি সহজ রাখার জন্য আমি এখান থেকে কেবল এই 30 নাম্বারটি কল করব that তবে নোট করুন যে আপনি যদি পাইথনটি 32-বিটের জন্য সংকলিত ব্যবহার করেন তবে আপনার বেঞ্চমার্কের ফলাফল x32,768 এর চেয়ে কম বা না হলে তার উপর নির্ভর করবে ))

যখন কোনও অপারেশনের ইনপুটস এবং আউটপুটগুলি এই 30-বিটের সীমাতে থাকে, তখন অপারেশনটি সাধারণ উপায়ে পরিবর্তে অনুকূলিতভাবে পরিচালনা করা যায়। শুরুতে পূর্ণসংখ্যা গুণ বাস্তবায়ন নিম্নরূপ:

static PyObject *
long_mul(PyLongObject *a, PyLongObject *b)
{
    PyLongObject *z;

    CHECK_BINOP(a, b);

    /* fast path for single-digit multiplication */
    if (Py_ABS(Py_SIZE(a)) <= 1 && Py_ABS(Py_SIZE(b)) <= 1) {
        stwodigits v = (stwodigits)(MEDIUM_VALUE(a)) * MEDIUM_VALUE(b);
#ifdef HAVE_LONG_LONG
        return PyLong_FromLongLong((PY_LONG_LONG)v);
#else
        /* if we don't have long long then we're almost certainly
           using 15-bit digits, so v will fit in a long.  In the
           unlikely event that we're using 30-bit digits on a platform
           without long long, a large v will just cause us to fall
           through to the general multiplication code below. */
        if (v >= LONG_MIN && v <= LONG_MAX)
            return PyLong_FromLong((long)v);
#endif
    }

সুতরাং দুটি পূর্ণসংখ্যা যখন 30-বিট ডিজিটের সাথে খাপ খায় তখন এটি সিপিথন দোভাষী দ্বারা অ্যারে হিসাবে পূর্ণসংখ্যার সাথে কাজ না করে সরাসরি গুণক হিসাবে সম্পন্ন হয়। ( MEDIUM_VALUE()ধনাত্মক পূর্ণসংখ্যার অবজেক্টে ডাকা হলে এটি কেবল তার প্রথম 30-বিট ডিজিট পায়)) ফলাফলটি যদি কোনও একক 30-বিট ডিজিটের সাথে ফিট করে তবে এটি PyLong_FromLongLong()অপেক্ষাকৃত কম সংখ্যক ক্রিয়াকলাপে লক্ষ্য করবে এবং সংরক্ষণ করার জন্য একটি একক-সংখ্যার পূর্ণসংখ্য বস্তু তৈরি করবে এটা।

বিপরীতে, বাম শিফটগুলি এইভাবে অনুকূলিত হয় না এবং প্রতিটি বাম শিফটটি অ্যারে হিসাবে স্থানান্তরিত হওয়ার পূর্ণসংখ্যার সাথে ডিল করে। বিশেষত, যদি আপনি যদি সোর্স কোডটির দিকে লক্ষ্য করেন long_lshift()তবে একটি ছোট তবে ইতিবাচক বাম শিফটের ক্ষেত্রে, 2-সংখ্যার পূর্ণসংখ্যার অবজেক্টটি সর্বদা তৈরি হয়, যদি কেবল তার দৈর্ঘ্য 1 টি পরে কাটা হয়: (আমার মন্তব্যসমূহ /*** ***/)

static PyObject *
long_lshift(PyObject *v, PyObject *w)
{
    /*** ... ***/

    wordshift = shiftby / PyLong_SHIFT;   /*** zero for small w ***/
    remshift  = shiftby - wordshift * PyLong_SHIFT;   /*** w for small w ***/

    oldsize = Py_ABS(Py_SIZE(a));   /*** 1 for small v > 0 ***/
    newsize = oldsize + wordshift;
    if (remshift)
        ++newsize;   /*** here newsize becomes at least 2 for w > 0, v > 0 ***/
    z = _PyLong_New(newsize);

    /*** ... ***/
}

পূর্ণসংখ্যা বিভাগ

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


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

একটি দুর্দান্ত প্রশ্নের উত্তম গবেষণামূলক অ্যাডন লিখিত উত্তর। xঅনুকূলিতকরণের ব্যাপ্তির বাইরে থাকা সময়ের জন্য গ্রাফগুলি দেখানো আকর্ষণীয় হতে পারে ।
বর্মার
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.