<< >> গুণ এবং বিভাগের গতি


9

আপনি যখন পাই সময় পাই তখন পাইথনগুলিতে সংখ্যাগুলি ভাগ করতে <<এবং >>দ্বিখণ্ডিত করতে আপনি বাইনারি শিফট পদ্ধতিটি ব্যবহার করে নিয়মিত উপায়ে ভাগ বা গুণিত করার চেয়ে 10x দ্রুত find

কেন ব্যবহার করা হয় <<এবং >>তুলনায় অনেক দ্রুত *এবং /?

দৃশ্যের প্রক্রিয়াগুলি তৈরি করতে *এবং /এত ধীরগতির পিছনে কী রয়েছে ?


2
বিট শিফট কেবল পাইথন নয়, সমস্ত ভাষায় দ্রুত। অনেক প্রসেসরের একটি নেটিভ বিট শিফট নির্দেশনা থাকে যা এটি এক বা দুটি ঘড়ির চক্রের মধ্যে সম্পন্ন করবে।
রবার্ট হার্ভে

4
তবে এটি মাথায় রাখা উচিত, বিটশিফিং, সাধারণ বিভাগ এবং গুণন অপারেটরগুলি ব্যবহার না করে সাধারণত খারাপ অভ্যাস এবং পাঠযোগ্যতা বাধাগ্রস্ত করতে পারে।
আজর

6
@ ক্রিজলি কারণ সর্বোত্তম এটি একটি মাইক্রো-অপ্টিমাইজেশন এবং এর একটি ভাল সম্ভাবনা রয়েছে যে সংকলকটি এটিকে যাইহোক যাইহোক (যদি সম্ভব হয়) বাইটকোডে স্থানান্তরিত করে। এর ব্যতিক্রম রয়েছে যেমন কোড যখন অত্যন্ত পারফরম্যান্সের সমালোচনা করে তবে বেশিরভাগ সময় আপনি যা করছেন তা আপনার কোডটিকে অবলম্বন করে।
আজর

7
@ ক্রিজলি: কোনও শালীন অপ্টিমাইজারের সাথে যে কোনও সংকলক বিট শিফটগুলির সাহায্যে সম্পন্ন গুণগুলি এবং বিভাগগুলি সনাক্ত করতে পারে এবং সেগুলি ব্যবহার করে এমন কোড জেনারেট করে। কম্পাইলারকে আউটসামার্ট করার চেষ্টা করে আপনার কোডটিকে কুৎসিত করবেন না।
blrfl

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

উত্তর:


15

দুটি ছোট সি প্রোগ্রামের দিকে নজর দিন যা কিছুটা শিফট এবং একটি বিভাজন করে।

#include <stdlib.h>

int main(int argc, char* argv[]) {
        int i = atoi(argv[0]);
        int b = i << 2;
}
#include <stdlib.h>

int main(int argc, char* argv[]) {
        int i = atoi(argv[0]);
        int d = i / 4;
}

এর পরে gcc -Sপ্রকৃত সমাবেশটি কী হবে তা দেখার জন্য এগুলি প্রতিটি সংকলিত হয় ।

বিট শিফট সংস্করণ সহ, কল atoiথেকে ফিরে আসতে:

    callq   _atoi
    movl    $0, %ecx
    movl    %eax, -20(%rbp)
    movl    -20(%rbp), %eax
    shll    $2, %eax
    movl    %eax, -24(%rbp)
    movl    %ecx, %eax
    addq    $32, %rsp
    popq    %rbp
    ret

বিভাজন সংস্করণ:

    callq   _atoi
    movl    $0, %ecx
    movl    $4, %edx
    movl    %eax, -20(%rbp)
    movl    -20(%rbp), %eax
    movl    %edx, -28(%rbp)         ## 4-byte Spill
    cltd
    movl    -28(%rbp), %r8d         ## 4-byte Reload
    idivl   %r8d
    movl    %eax, -24(%rbp)
    movl    %ecx, %eax
    addq    $32, %rsp
    popq    %rbp
    ret

কেবল এটি দেখে বিট শিফটের তুলনায় বিভাজন সংস্করণে আরও বেশ কয়েকটি নির্দেশ রয়েছে।

মূল কী তারা কি করবে?

বিট শিফট সংস্করণে মূল নির্দেশটি হ'ল shll $2, %eaxএকটি শিফট বাম যৌক্তিক - এখানে বিভাজন রয়েছে এবং অন্য সমস্ত কিছুই কেবল মূল্যবোধকে ঘিরে।

বিভাজন সংস্করণে, আপনি দেখতে পারেন idivl %r8d- তবে এর ঠিক ওপরে একটি cltd(দীর্ঘ থেকে দ্বিগুণ রূপান্তরিত) এবং স্পিল এবং পুনরায় লোডকে ঘিরে কিছু অতিরিক্ত যুক্তি রয়েছে। এই অতিরিক্ত কাজটি জেনে যে আমরা বিটগুলির চেয়ে গণিতের সাথে কাজ করছি তা প্রায়শই বিট গণিতের দ্বারা ঘটে যাওয়া বিভিন্ন ত্রুটিগুলি এড়াতে প্রয়োজনীয়।

কিছু দ্রুত গুণ করা যাক:

#include <stdlib.h>

int main(int argc, char* argv[]) {
    int i = atoi(argv[0]);
    int b = i >> 2;
}
#include <stdlib.h>

int main(int argc, char* argv[]) {
    int i = atoi(argv[0]);
    int d = i * 4;
}

এই সমস্ত কিছুর মধ্য দিয়ে যাওয়ার পরিবর্তে একটি লাইন আলাদা:

mult বিভিন্ন মাল্ট বিট.এস
24c24
> shll $ 2,% eax
---
<sarl $ 2,% eax

এখানে সংকলক শনাক্ত করতে সক্ষম হয়েছিল যে গণিতটি একটি শিফট দিয়ে করা যায়, তবে লজিকাল শিফ্টের পরিবর্তে এটি গাণিতিক শিফট করে। এর মধ্যে পার্থক্যটি সুস্পষ্ট হবে যদি আমরা এগুলি চালিত করি - sarlচিহ্নটি সংরক্ষণ করে। যাতে না -2 * 4 = -8যখন shll

দ্রুত পার্ল স্ক্রিপ্ট এ এটি দেখতে দিন:

#!/usr/bin/perl

$foo = 4;
print $foo << 2, "\n";
print $foo * 4, "\n";

$foo = -4;
print $foo << 2, "\n";
print $foo * 4, "\n";

আউটপুট:

16
16
18446744073709551600
-16

উম ... -4 << 2হ'ল 18446744073709551600গুণ এবং বিভাজন নিয়ে কাজ করার সময় আপনি সম্ভবত এটির প্রত্যাশা করছেন। এটি সঠিক, তবে এটি পূর্ণসংখ্যার গুণ নয়।

এবং এইভাবে অকাল অপটিমাইজেশন থেকে সাবধান। সংকলকটি আপনার জন্য অনুকূলিত হয়ে উঠুন - আপনি কী করতে চাইছেন তা জানে এবং কম বাগ সহ এটি আরও ভাল কাজ করবে it


12
এটা তোলে যুক্ত করার পরিষ্কার হতে পারে << 2সঙ্গে * 4এবং >> 2সঙ্গে / 4প্রতিটি উদাহরণ মধ্যে স্থানান্তর দিকনির্দেশ একই রাখা।
গ্রেগ হিউগিল

5

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

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

সুতরাং আসুন, বিশদটি দেখুন "সম্পূর্ণ" অপারেটরগুলি বনাম গুন এবং স্থানান্তরের মতো স্থানান্তরকরণের দিকে।

নাড়াচাড়া

প্রায় সকল হার্ডওয়্যারে, একটি ধ্রুবক পরিমাণে (যেমন, সংকলক সংকলন করতে পারে এমন পরিমাণের সাহায্যে) পরিমাণ দ্রুত স্থানান্তর করা দ্রুত হয় । বিশেষত, এটি সাধারণত একক চক্রের বিলম্বের সাথে এবং প্রতি চক্র 1 বা আরও ভাল এর থ্রুটপুট সহ ঘটে। কিছু হার্ডওয়্যারে (যেমন, কিছু ইন্টেল এবং এআরএম চিপস), ধ্রুবক দ্বারা নির্দিষ্ট শিফট এমনকি "ফ্রি" হতে পারে কারণ এগুলি অন্য নির্দেশিকায় তৈরি করা যেতে পারে ( leaইন্টেলের উপর, এআরএমের প্রথম উত্সের বিশেষ স্থানান্তর ক্ষমতা)।

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

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

তারপরে এটির ফলাফলটি হ'ল সাম্প্রতিক ইন্টেল আর্কিটেকচারে, একটি চলক পরিমাণে শিফটটি তিনটি "মাইক্রো-অপারেশন" লাগে যখন অন্যান্য সাধারণ সরল ক্রিয়াকলাপগুলি (যোগ, বিটওয়াইস অপস, এমনকি গুণ) কেবল ১ গ্রহণ করে sh এই ধরনের শিফটগুলি প্রতি 2 চক্রে একবারে কার্যকর করা যেতে পারে ।

গুণ

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

তাদের মূল গ্রহণযোগ্যতাটি হ'ল আধুনিক ল্যাপটপ চিপগুলিতে (বা আরও ভাল), গুণগুলি একটি দ্রুত অপারেশন, এবং সম্ভবত 3 বা 4 নির্দেশক্রমের চেয়ে দ্রুততর হতে পারে যে কোনও সংকলক শক্তি হ্রাস শিফটগুলির জন্য "রাউন্ডিং" পেতে দিতে পারে। পরিবর্তনশীল শিফটগুলির জন্য, ইন্টেল-এ, উল্লিখিত ইস্যুগুলির কারণে সাধারণত গুনকেও অগ্রাধিকার দেওয়া হবে।

ছোট ফর্ম-ফ্যাক্টর প্ল্যাটফর্মে, গুণগুলি এখনও ধীর হতে পারে, কারণ একটি পূর্ণ এবং দ্রুত 32-বিট বা বিশেষত -৪-বিট গুণক তৈরি করতে প্রচুর ট্রানজিস্টর এবং শক্তি লাগে। কেউ যদি সাম্প্রতিক মোবাইল চিপগুলিতে গুণনের পারফরম্যান্সের বিশদটি পূরণ করতে পারে তবে এটি অনেক প্রশংসা হবে।

বিভক্ত করা

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

বিভাজনগুলি এড়াতে এবং শিফ্টগুলির সাথে প্রতিস্থাপন করুন (বা সংকলকটি এটি করতে দিন, তবে আপনার সমাবেশটি পরীক্ষা করার প্রয়োজন হতে পারে) আপনি যদি পারেন!


2

BINARY_LSHIFT এবং BINARY_RSHIFT BINARY_MULTIPLY এবং BINARY_FLOOR_DIVIDE এর তুলনায় অ্যালগোরিদমিকভাবে সহজ প্রক্রিয়া এবং কম ঘড়ি-চক্র নিতে পারে। এটি যদি আপনার কোনও বাইনারি নম্বর থাকে এবং এন দ্বারা বিট শিফ্টের প্রয়োজন হয়, আপনাকে কেবলমাত্র অনেকগুলি স্থানের উপরের অঙ্কগুলি স্থানান্তর করতে হবে এবং জিরো দিয়ে প্রতিস্থাপন করতে হবে। বাইনারি গুণগুলি সাধারণভাবে আরও জটিল , যদিও দাদদা গুণকের মতো কৌশলগুলি এটিকে বেশ দ্রুত করে তোলে।

মঞ্জুর, একটি অপ্টিমাইজ করা সংকলক ক্ষেত্রে কেসগুলি সনাক্ত করতে পারে যখন আপনি দু'জনের দ্বারা গুণিত / ভাগ করে উপযুক্ত বাম / ডান স্থানান্তর করে প্রতিস্থাপন করেন। অযৌক্তিকভাবে বাইট কোডটি দেখলে পাইথন দৃশ্যত এটি করে না:

>>> dis.dis(lambda x: x*4)
  1           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (4)
              6 BINARY_MULTIPLY     
              7 RETURN_VALUE        

>>> dis.dis(lambda x: x<<2)
  1           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (2)
              6 BINARY_LSHIFT       
              7 RETURN_VALUE        


>>> dis.dis(lambda x: x//2)
  1           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (2)
              6 BINARY_FLOOR_DIVIDE 
              7 RETURN_VALUE        

>>> dis.dis(lambda x: x>>1)
  1           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (1)
              6 BINARY_RSHIFT       
              7 RETURN_VALUE        

যাইহোক, আমার প্রসেসরে, আমি গুণ এবং বাম / ডান শিফ্ট একই টাইমিং আছে এবং মেঝে বিভাগ (দুটি দ্বারা একটি শক্তি) প্রায় 25% ধীর:

>>> import timeit

>>> timeit.repeat("z=a + 4", setup="a = 37")
[0.03717184066772461, 0.03291916847229004, 0.03287005424499512]

>>> timeit.repeat("z=a - 4", setup="a = 37")
[0.03534698486328125, 0.03207516670227051, 0.03196907043457031]

>>> timeit.repeat("z=a * 4", setup="a = 37")
[0.04594111442565918, 0.0408930778503418, 0.045324087142944336]

>>> timeit.repeat("z=a // 4", setup="a = 37")
[0.05412912368774414, 0.05091404914855957, 0.04910898208618164]

>>> timeit.repeat("z=a << 2", setup="a = 37")
[0.04751706123352051, 0.04259490966796875, 0.041903018951416016]

>>> timeit.repeat("z=a >> 2", setup="a = 37")
[0.04719185829162598, 0.04201006889343262, 0.042105913162231445]
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.