32-বিট পূর্ণসংখ্যায় সেট বিটের সংখ্যা কীভাবে গণনা করবেন?


868

B নম্বর প্রতিনিধিত্বকারী 8 বিটগুলি এর মতো দেখতে:

00000111

তিন বিট সেট করা হয়।

32-বিট পূর্ণসংখ্যায় সেট বিটের সংখ্যা নির্ধারণের জন্য অ্যালগরিদম কী কী?


101
এটি হ্যামিং ওজন বিটিডাব্লু।
পুরফিডাস

11
এটির জন্য বাস্তব-বিশ্ব অ্যাপ্লিকেশন কী? (এটি একটি সমালোচনা হিসাবে নেওয়া হবে না - আমি কেবল কৌতূহলী।)
জোনমরগান

8
প্যারিটি বিটের গণনা (এটি সন্ধান করুন), যা যোগাযোগে সাধারণ ত্রুটি সনাক্তকরণ হিসাবে ব্যবহৃত হয়েছিল।
ডায়ালেক্টিকাস

8
@ ডায়ালেক্টিকাস, একটি প্যারিটি বিটের গণনা করা হ্যামিং ওজন গণনা করার চেয়ে কম সস্তা

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

উত্তর:


849

এটি ' হামিং ওজন ', 'পপকাউন্ট' বা 'পাশাপাশি যুক্ত হওয়া' নামে পরিচিত ।

'সেরা' অ্যালগরিদম নির্ভর করে আপনি কোন সিপিইউতে আছেন এবং আপনার ব্যবহারের ধরণটি কী তার উপর নির্ভর করে।

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

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

যদি আপনি জানেন যে আপনার বাইটগুলি বেশিরভাগ 0 এর বা বেশিরভাগ 1 এর হবে তবে এই পরিস্থিতিগুলির জন্য খুব দক্ষ অ্যালগরিদম রয়েছে are

আমি বিশ্বাস করি একটি খুব ভাল সাধারণ উদ্দেশ্য অ্যালগরিদম নিম্নলিখিত, 'সমান্তরাল' বা 'ভেরিয়েবল-নির্ভুলতা SWAR অ্যালগরিদম' হিসাবে পরিচিত। আমি এটি সি-এর মতো সিউডো ভাষায় প্রকাশ করেছি, আপনার কোনও নির্দিষ্ট ভাষার জন্য কাজ করার জন্য এটি সামঞ্জস্য করতে হতে পারে (যেমন জাভাতে সি ++ এবং >>> এর জন্য uint32_t ব্যবহার করে):

int numberOfSetBits(uint32_t i)
{
     // Java: use int, and use >>> instead of >>
     // C or C++: use uint32_t
     i = i - ((i >> 1) & 0x55555555);
     i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
     return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

জাভাস্ক্রিপ্ট জন্য: পূর্ণসংখ্যা থেকে নিগৃহীত সঙ্গে |0অভিনয়ের জন্য: থেকে প্রথম লাইন পরিবর্তনi = (i|0) - ((i >> 1) & 0x55555555);

এটি আলোচিত যে কোনও অ্যালগরিদমের সবচেয়ে খারাপ-আচরণের আচরণ করে, তাই আপনি যে কোনও ব্যবহারের প্যাটার্ন বা মান এতে নিক্ষিপ্ত করেন তা দক্ষতার সাথে মোকাবেলা করবে।


এই স্বর্গ বিট্যাক কীভাবে কাজ করে:

i = i - ((i >> 1) & 0x55555555);

প্রথম পদক্ষেপটি বিজোড় / এমনকি বিটগুলি বিচ্ছিন্ন করার জন্য তাদের মাস্কিংয়ের অপ্টিমাইজড সংস্করণ, সেগুলি সরিয়ে রাখার জন্য স্থানান্তর করা এবং যুক্ত করা। এটি কার্যকরভাবে 2-বিট সংগ্রহকারীগুলিতে 16 টি পৃথক সংযোজন করে ( একটি রেজিস্ট্রারের মধ্যে SWAR = সিমড )। লাইক (i & 0x55555555) + ((i>>1) & 0x55555555)

পরবর্তী পদক্ষেপটি 16x 2-বিট সংগ্রহকারীগুলির মধ্যে বিজোড় / এমনকি আটটি নেয় এবং 8x 4-বিট পরিমাণ যোগ করে আবার যুক্ত করে। i - ...তাই এটি ঠিক আগে মাস্ক নেই / নাড়াচাড়া পর অপ্টিমাইজেশান এই সময় সম্ভব নয়। স্থান পরিবর্তন করার আগে পরিবর্তনের 0x33...পরিবর্তে দু'বার একই ধ্রুবক ব্যবহার 0xccc...করা একটি ভাল জিনিস যখন আইএসএগুলির জন্য পৃথকভাবে 32-বিট ধ্রুবক নির্মাণ করা প্রয়োজন সংকলন করার সময়।

চূড়ান্ত শিফট এবং যুক্ত পদক্ষেপটি (i + (i >> 4)) & 0x0F0F0F0F4x 8-বিট আহরণকারীগুলিতে প্রশস্ত হয়। এটি পূর্বের পরিবর্তে যুক্ত করার পরে মুখোশ পরে , কারণ যে কোনও 4-বিট সঞ্চালকের সর্বাধিক মান 4যদি সংশ্লিষ্ট ইনপুট বিটের সমস্ত 4 বিট সেট করা থাকে। 4 + 4 = 8 যা এখনও 4 টি বিটের সাথে খাপ খায় তাই নিবল উপাদানগুলির মধ্যে বহন করা অসম্ভব i + (i >> 4)

এখনও অবধি এটি কয়েকটি চালাক অপ্টিমাইজেশনের সাথে স্বর কৌশল ব্যবহার করে মোটামুটি স্বাভাবিক সিমড। আরও 2 টি ধাপের জন্য একই প্যাটার্নটির সাথে চালিয়ে যাওয়া 2x 16-বিট এর পরে 1x 32-বিট গুনে প্রশস্ত হতে পারে। তবে দ্রুত হার্ডওয়্যার গুণিত সহ মেশিনগুলিতে আরও কার্যকর উপায় রয়েছে:

আমাদের একবার পর্যাপ্ত পরিমাণে "উপাদান" হয়ে গেলে, ম্যাজিক ধ্রুবক সহ একটি গুণটি সমস্ত উপাদানকে শীর্ষ উপাদানগুলিতে যোগ করতে পারে । এই ক্ষেত্রে বাইট উপাদান। বাম-স্থানান্তর এবং যোগ দ্বারা গুণ করা হয়, x * 0x01010101ফলস্বরূপ একটি গুণ গুণ x + (x<<8) + (x<<16) + (x<<24) আমাদের 8-বিট উপাদান যথেষ্ট চওড়া (এবং ছোট যথেষ্ট গন্য অধিষ্ঠিত) যে এই বহন উত্পাদন না হয় মধ্যে উপরের 8 বিট।

এর একটি 64-বিট সংস্করণ 0x010101010101010101 গুণক সহ একটি 64-বিট পূর্ণসংখ্যায় 8x 8-বিট উপাদান করতে পারে এবং এর সাথে উচ্চ বাইটটি বের করতে পারে >>56। সুতরাং এটি কোনও অতিরিক্ত পদক্ষেপ নেয় না, কেবল বৃহত্তর ধ্রুবক। __builtin_popcountllযখন হার্ডওয়্যার popcntনির্দেশনা সক্ষম করা না হয় তখন x86 সিস্টেমে এটি জিসিসি ব্যবহার করে । আপনি যদি এর জন্য বিল্টিনস বা ইন্টারসিনিকগুলি ব্যবহার করতে পারেন তবে কম্পাইলারকে লক্ষ্য-নির্দিষ্ট অপ্টিমাইজেশান করার সুযোগ দেওয়ার জন্য এটি করুন।


বিস্তৃত ভেক্টরগুলির জন্য সম্পূর্ণ সিমডি সহ (যেমন একটি সম্পূর্ণ অ্যারে গণনা করা)

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

তবে পপকাউন্টের জন্য ভেক্টর নির্দেশাবলীর ব্যবহারের সর্বোত্তম উপায় হ'ল সমান্তরালে প্রতিটি বাইটের এক সাথে 4 টি বিটের জন্য একটি টেবিল-সন্ধানের জন্য ভেরিয়েবল-শ্যাফেল ব্যবহার করে। (4 বিটস সূচক ভেক্টর রেজিস্টারে রাখা 16 টি এন্ট্রি টেবিল)।

ইন্টেল সিপিইউগুলিতে, হার্ডওয়্যার bit৪ বিট পপসেন্ট নির্দেশাবলী একটি এসএসএসই 3 PSHUFBবিট-সমান্তরাল বাস্তবায়নকে প্রায় 2 এর একটি ফ্যাক্টর দ্বারা কার্যকর করতে পারে , তবে কেবলমাত্র যদি আপনার সংকলকটি ঠিক এটি পায় । অন্যথায় এসএসই উল্লেখযোগ্যভাবে সামনে আসতে পারে। আরও নতুন সংকলক সংস্করণগুলি ইন্টেলের পপসেন্ট মিথ্যা নির্ভরতা সমস্যা সম্পর্কে সচেতন ।

তথ্যসূত্র:


87
হা! নাম্বার অফসেটবিটস () ফাংশনটি পছন্দ করুন তবে একটি কোড পর্যালোচনার মাধ্যমে ভাগ্য ভালো। :-)
জেসন এস

37
unsigned intসহজেই এটি ব্যবহার করা উচিত , এটি সহজেই দেখানোর জন্য যে এটি কোনও সাইন বিট জটিলতা থেকে মুক্ত। এছাড়াও uint32_tনিরাপদ হবে, যেমনটি, আপনি সমস্ত প্ল্যাটফর্মগুলিতে যা প্রত্যাশা করেন তা পাবেন?
ক্রেগ ম্যাককুইন

35
@ ননব: আসলে লিখিত হিসাবে কোডটি বগি এবং তার রক্ষণাবেক্ষণের প্রয়োজন। >>নেতিবাচক মান জন্য বাস্তবায়ন সংজ্ঞায়িত হয়। যুক্তিটি পরিবর্তন করতে (বা কাস্ট করা) দরকার unsigned, এবং যেহেতু কোডটি 32-বিট-নির্দিষ্ট, সম্ভবত এটি ব্যবহার করা উচিত uint32_t
আর .. গীটহাব বন্ধ করুন ICE

6
এটা আসলে যাদু নয়। এটি বিটের সেট যুক্ত করছে তবে কিছু চালাক অপ্টিমাইজেশান সহ এটি করছে। উত্তরে প্রদত্ত উইকিপিডিয়া লিঙ্কটি কী হচ্ছে তা ব্যাখ্যা করার একটি ভাল কাজ করে তবে আমি একের পর এক লাইনে যাব। 1) বিটগুলির প্রতিটি জোড়ায় বিটের সংখ্যা গণনা করুন, সেই গণনাটিকে সেই বিটগুলিতে রেখে দিন (আপনার 00, 01 বা 10 হবে); এখানে "চতুর" বিটটি বিয়োগফল যা একটি মুখোশ এড়ায়। 2) বিটপয়ারগুলির সংখ্যক জোড়গুলি তাদের সংশ্লিষ্ট নীবলগুলিতে যুক্ত করুন; এখানে চালাক কিছুই নয় তবে প্রতিটি স্তন্যপানের এখন 0-4 এর মান হবে। (cont'd)
ড্যাশ-টম-ব্যাং

8
অন্য দ্রষ্টব্য, এটি কেবল ধ্রুবকগুলি যথাযথভাবে প্রসারিত করে 64 এবং 128 বিট রেজিস্টারে প্রসারিত হয়। মজার বিষয় (আমার কাছে), এই ধ্রুবকগুলিও 0/3, 5, 17 এবং 255; পূর্বের তিনজন হলেন 2 ^ n + 1। এগুলি আপনাকে আরও ততোধিকভাবে দেখার এবং ঝরনাতে এটি সম্পর্কে আরও চিন্তাভাবনা করে। :)
ড্যাশ-টম-ব্যাং

214

আপনার সংকলকগুলির অন্তর্নির্মিত ফাংশনগুলিও বিবেচনা করুন।

GNU সংকলক উদাহরণস্বরূপ আপনি কেবল ব্যবহার করতে পারেন:

int __builtin_popcount (unsigned int x);
int __builtin_popcountll (unsigned long long x);

সবচেয়ে খারাপ ক্ষেত্রে সংকলক একটি ফাংশনে কল উত্পন্ন করবে। সেরা ক্ষেত্রে সংকলক একই কাজটি দ্রুত করার জন্য সিপিইউ নির্দেশিকা নির্গত করবে।

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


X86-এ, আপনি সংকলকটি বলতে পারেন যে এটি একই প্রজন্মের মধ্যে যুক্ত ভেক্টর নির্দেশাবলী popcntসহ নির্দেশের জন্য -mpopcntবা -msse4.2এটি সক্ষম করার জন্য সমর্থন গ্রহণ করতে পারে । দেখুন জিসিসি এক্স 86 অপশন-march=nehalem(বা -march=আপনার কোডটি ধরে নিতে এবং এর জন্য টিউন করার জন্য আপনি যে কোনও সিপিইউ চান) ভাল পছন্দ হতে পারে। পুরানো সিপিইউতে ফলাফল বাইনারি চালানোর ফলে একটি অবৈধ-নির্দেশ ত্রুটি হবে।

আপনি যে মেশিনটি তৈরি করেন তার জন্য বাইনারিগুলি অপ্টিমাইজড করতে, -march=native (জিসিসি, ঝনঝন বা আইসিসি সহ) ব্যবহার করুন।

এমএসভিসি x86 popcntনির্দেশের জন্য একটি আন্তঃনীতি সরবরাহ করে তবে জিসিসির বিপরীতে এটি হার্ডওয়্যার নির্দেশের জন্য সত্যই অন্তর্নিহিত এবং হার্ডওয়্যার সমর্থন প্রয়োজন।


std::bitset<>::count()বিল্ট-ইন এর পরিবর্তে ব্যবহার করা হচ্ছে

তত্ত্ব অনুসারে, যে কোনও সংকলক যে কীভাবে টার্গেট সিপিইউতে দক্ষতার সাথে পপকাউন্ট করতে জানে তার কার্যকারিতাটি আইএসও সি ++ এর মাধ্যমে প্রকাশ করা উচিত std::bitset<>। অনুশীলনে, কিছু লক্ষ্যযুক্ত সিপিইউগুলির জন্য আপনি কিছু ক্ষেত্রে বিট-হ্যাক ওআর / শিফট / এডিডি দিয়ে ভাল হতে পারেন।

টার্গেট আর্কিটেকচারের জন্য যেখানে হার্ডওয়্যার পপকাউন্ট একটি alচ্ছিক এক্সটেনশন (x86 এর মতো), সমস্ত সংকলক std::bitsetউপলব্ধ থাকে না যখন এটি সুবিধা গ্রহণ করে। উদাহরণস্বরূপ, popcntসংকলনের সময় এমএসভিসির কাছে সমর্থন সক্ষম করার কোনও উপায় নেই এবং সর্বদা একটি সারণী অনুসন্ধানও ব্যবহার করে , এমনকি এটি /Ox /arch:AVX(যা এসএসই 4.2 বোঝায়, যদিও প্রযুক্তিগতভাবে আলাদা বৈশিষ্ট্যযুক্ত বিট রয়েছে is popcnt)

তবে কমপক্ষে আপনি পোর্টেবল এমন কিছু পান যা সর্বত্র কাজ করে, এবং জিসিসি / ঝাঁকুনির সাথে সঠিক লক্ষ্য বিকল্পের সাহায্যে আপনি আর্কিটেকচারের জন্য হার্ডওয়্যার পপকাউন্ট পাবেন।

#include <bitset>
#include <limits>
#include <type_traits>

template<typename T>
//static inline  // static if you want to compile with -mpopcnt in one compilation unit but not others
typename std::enable_if<std::is_integral<T>::value,  unsigned >::type 
popcount(T x)
{
    static_assert(std::numeric_limits<T>::radix == 2, "non-binary type");

    // sizeof(x)*CHAR_BIT
    constexpr int bitwidth = std::numeric_limits<T>::digits + std::numeric_limits<T>::is_signed;
    // std::bitset constructor was only unsigned long before C++11.  Beware if porting to C++03
    static_assert(bitwidth <= std::numeric_limits<unsigned long long>::digits, "arg too wide for std::bitset() constructor");

    typedef typename std::make_unsigned<T>::type UT;        // probably not needed, bitset width chops after sign-extension

    std::bitset<bitwidth> bs( static_cast<UT>(x) );
    return bs.count();
}

দেখুন জিসিসি, ঝনঝন শব্দ, আইসিসি এবং MSVC থেকে এ এস এম Godbolt কম্পাইলার এক্সপ্লোরার উপর।

x86-64 gcc -O3 -std=gnu++11 -mpopcntএটি প্রকাশ করে:

unsigned test_short(short a) { return popcount(a); }
    movzx   eax, di      # note zero-extension, not sign-extension
    popcnt  rax, rax
    ret
unsigned test_int(int a) { return popcount(a); }
    mov     eax, edi
    popcnt  rax, rax
    ret
unsigned test_u64(unsigned long long a) { return popcount(a); }
    xor     eax, eax     # gcc avoids false dependencies for Intel CPUs
    popcnt  rax, rdi
    ret

পাওয়ারপিসি 64 প্রকাশ করে gcc -O3 -std=gnu++11( intআর্গ সংস্করণের জন্য):

    rldicl 3,3,0,32     # zero-extend from 32 to 64-bit
    popcntd 3,3         # popcount
    blr

এই উত্সটি x86- নির্দিষ্ট বা GNU- নির্দিষ্ট নয়, তবে কেবল gcc / ক্ল্যাং / আইসিসি সহ x86 এর জন্য ভাল সংকলন করে।

আরও মনে রাখবেন যে একক-নির্দেশনা পপকাউন্ট ছাড়াই আর্কিটেকচারের জন্য জিসিসির ফ্যালব্যাক একটি সময়ে বাইট-এ-টাইম টেবিল লুকআপ। উদাহরণস্বরূপ এটি আর্মের পক্ষে দুর্দান্ত নয় ।


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

5
ইন্টেল আই 5 / আই 7 এর এসএসই 4 নির্দেশনা পিওপিসিএনটি রয়েছে যা সাধারণ উদ্দেশ্যে নিবন্ধগুলি ব্যবহার করে এটি করে। আমার সিস্টেমে জিসিসি এই আন্তঃনদী ব্যবহার করে সেই নির্দেশকে নির্গত করে না, আমার ধারণা এখনও -মার্চ = নেহালেম বিকল্পের কারণে নেই।
matja

3
@matja, আমার জিসিসি 4.4.1 নিঃসরণ করে popcnt নির্দেশ যদি আমি -msse4.2 সঙ্গে কম্পাইল
নিলস Pipenbrinck

74
সি ++ এর ব্যবহার করুন std::bitset::count। একটি একক __builtin_popcountকলে এই সংকলন অন্তর্ভুক্ত করার পরে ।
deft_code

1
@nlucaroni ভাল, হ্যাঁ সময় বদলে যাচ্ছে। আমি এই উত্তরটি ২০০৮ সালে লিখেছি Now আজকাল আমাদের কাছে দেশীয় পপকাউন্ট রয়েছে এবং প্ল্যাটফর্ম যদি এটির অনুমতি দেয় তবে অভ্যন্তরীণ একটি একক সমাবেশকারী বিবৃতিতে সংকলন করবে।
নীল পিপেনব্রিংক

183

আমার মতে, "সেরা" সমাধানটি হ'ল এক যা অন্য প্রোগ্রামার (বা মূল প্রোগ্রামার দুই বছর পরে) প্রচুর মন্তব্য ছাড়াই পড়তে পারে। আপনি ভাল বা দ্রুততম সমাধানটি ভালভাবে চাইতে পারেন যা কিছু ইতিমধ্যে সরবরাহ করেছে তবে আমি যে কোনও সময় চতুরতার চেয়ে পাঠযোগ্যতা পছন্দ করি।

unsigned int bitCount (unsigned int value) {
    unsigned int count = 0;
    while (value > 0) {           // until all bits are zero
        if ((value & 1) == 1)     // check lower bit
            count++;
        value >>= 1;              // shift bits, removing lower bit
    }
    return count;
}

আপনি যদি আরও গতি চান (এবং আপনার উত্তরসূরিদের সহায়তা করার জন্য এটি নথিকে ভালভাবে ধরেছেন) তবে আপনি একটি সারণী অনুসন্ধান ব্যবহার করতে পারেন:

// Lookup table for fast calculation of bits set in 8-bit unsigned char.

static unsigned char oneBitsInUChar[] = {
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F (<- n)
//  =====================================================
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, // 0n
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, // 1n
    : : :
    4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, // Fn
};

// Function for fast calculation of bits set in 16-bit unsigned short.

unsigned char oneBitsInUShort (unsigned short x) {
    return oneBitsInUChar [x >>    8]
         + oneBitsInUChar [x &  0xff];
}

// Function for fast calculation of bits set in 32-bit unsigned int.

unsigned char oneBitsInUInt (unsigned int x) {
    return oneBitsInUShort (x >>     16)
         + oneBitsInUShort (x &  0xffff);
}

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


21
2 দ্বারা বিভক্ত হয়ে এটিকে "শিফট বিট ..." হিসাবে মন্তব্য করার পরিবর্তে আপনার কেবল শিফট অপারেটর (>>) ব্যবহার করা উচিত এবং মন্তব্যটি ছেড়ে দেওয়া উচিত।
indiv

9
এটি if ((value & 1) == 1) { count++; }দিয়ে প্রতিস্থাপন করা আরও বোধগম্য হবে না count += value & 1?
পঙ্কডুডল

21
না, সবচেয়ে ভাল সমাধান এক্ষেত্রে সবচেয়ে পঠনযোগ্য নয়। এখানে সেরা অ্যালগরিদম দ্রুততম।
নিকিসি

21
এটি সম্পূর্ণরূপে আপনার মতামত, @নিকিক, যদিও আপনি আমাকে নির্মূল করতে পারেন, স্পষ্টতই। "সেরা" কীভাবে পরিমাপ করা যায় সে সম্পর্কে প্রশ্নের কোনও উল্লেখ ছিল না, "পারফরম্যান্স" বা "দ্রুত" শব্দটি কোথাও দেখা যায় না। এজন্যই আমি পঠনযোগ্য বেছে নিয়েছি।
paxdiablo

3
আমি এই উত্তরটি 3 বছর পরে পড়ছি এবং আমি এটি সেরা উত্তর হিসাবে খুঁজে পাই কারণ এটি পাঠযোগ্য এবং আরও মন্তব্য রয়েছে। সময়কাল।
waka-waka-waka

98

হ্যাকার্স ডিলাইট থেকে, পি। 66, চিত্র 5-2

int pop(unsigned x)
{
    x = x - ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    x = x + (x >> 16);
    return x & 0x0000003F;
}

Branch 20-ইশ নির্দেশাবলী (আর্চ নির্ভর) কার্যকর করে, কোনও শাখা ছাড়াই।

হ্যাকার কল্লোল হয় আনন্দদায়ক! অত্যন্ত বাঞ্ছনীয়.


8
জাভা পদ্ধতিতে Integer.bitCount(int)একই একই বাস্তবায়ন ব্যবহার করা হয়।
মার্কো বলিস

এটি অনুসরণ করতে কিছুটা সমস্যা হচ্ছে - যদি আমরা কেবল 32-বিটের পরিবর্তে 16-বিট মানগুলির যত্ন নিই তবে এটি কীভাবে পরিবর্তিত হবে?
জেরেমি ব্লাম

হতে পারে হ্যাকাররা আনন্দিত, তবে আমি এর popপরিবর্তে population_count(বা pop_cntযদি আপনার কোনও অবসন্নতা থাকতেই পারে) এর পরিবর্তে যে কাউকে ফোন দিলে আমি একটি ভাল লাথি দেব । @ মারকোবোলিস আমি অনুমান করি যে এটি জাভার সমস্ত সংস্করণের ক্ষেত্রে সত্য হবে, তবে আনুষ্ঠানিকভাবে এটি বাস্তবায়নের উপর নির্ভরশীল হবে :)
মার্টেন বোদেউইস

এবং, এর কোনও গৃহীত উত্তরের কোডের মতো কোনও গুণনের প্রয়োজন নেই।
অ্যালেক্স

নোট করুন যে izing৪-বিটকে সাধারণ করার ক্ষেত্রে একটি সমস্যা আছে। মাস্কের কারণে ফলাফলটি 64 হতে পারে না।
অ্যালবার্ট ভ্যান ডার হোর্স্ট

76

আমি মনে করি দ্রুততম উপায়ে - অনুসন্ধানের সারণী এবং পপকাউন্ট ব্যবহার না করে — নীচের এটি। এটি মাত্র 12 টি ক্রিয়াকলাপ সহ সেট বিট গণনা করে।

int popcount(int v) {
    v = v - ((v >> 1) & 0x55555555);                // put count of each 2 bits into those 2 bits
    v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // put count of each 4 bits into those 4 bits  
    return c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
}

এটি কাজ করে কারণ আপনি দুটি ভাগে ভাগ করে সেট বিটের মোট সংখ্যা গণনা করতে পারবেন, উভয় অংশে সেট বিটের সংখ্যা গণনা করুন এবং তারপরে এগুলি যুক্ত করুন। Divide and Conquerদৃষ্টান্ত হিসাবেও জানেন । আসুন বিশদে আসুন ..

v = v - ((v >> 1) & 0x55555555); 

দুটি বিটের বিটের সংখ্যা হতে পারে 0b00, 0b01বা 0b10। এটি 2 বিট নিয়ে কাজ করার চেষ্টা করি ...

 ---------------------------------------------
 |   v    |   (v >> 1) & 0b0101   |  v - x   |
 ---------------------------------------------
   0b00           0b00               0b00   
   0b01           0b00               0b01     
   0b10           0b01               0b01
   0b11           0b01               0b10

এটি যা প্রয়োজন ছিল: শেষ কলামটি প্রতি দুটি বিট জোড়ায় সেট বিটের গণনা দেখায়। যদি দুটি বিট সংখ্যা হয় >= 2 (0b10)তবে andউত্পাদন করে 0b01, অন্যথায় এটি উত্পাদন করে 0b00

v = (v & 0x33333333) + ((v >> 2) & 0x33333333); 

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

v & 0b00110011         //masks out even two bits
(v >> 2) & 0b00110011  // masks out odd two bits

তারপরে আমরা উপরের ফলাফলটি সংযুক্ত করি, আমাদেরকে 4 বিটে সেট বিটের মোট গণনা প্রদান করে। সর্বশেষ বিবৃতিটি সবচেয়ে জটিল।

c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;

আসুন এটি আরও ভেঙে দিন ...

v + (v >> 4)

এটি দ্বিতীয় বিবৃতি অনুরূপ; পরিবর্তে আমরা 4 টি গ্রুপে সেট বিটগুলি গণনা করছি। আমরা জানি our আমাদের পূর্ববর্তী ক্রিয়াকলাপগুলির কারণে n যে প্রতিটি স্তন্যপায়ী এতে সেট বিটের সংখ্যা রয়েছে। একটি উদাহরণ তাকান। ধরুন আমাদের বাইট আছে 0b01000010। এর অর্থ হ'ল প্রথম স্তন্যপানটির 4 বিট সেট রয়েছে এবং দ্বিতীয়টির 2 বিট সেট রয়েছে। এখন আমরা এই নিবলগুলি একসাথে যুক্ত করব।

0b01000010 + 0b01000000

এটি আমাদের প্রথম বিড়ালের মধ্যে একটি বাইটে সেট বিটের গণনা দেয় 0b01100010 এবং অতএব আমরা সংখ্যার সমস্ত বাইটের শেষ চারটি বাইটকে মুখোশ করি (সেগুলি ত্যাগ করে)।

0b01100010 & 0xF0 = 0b01100000

এখন প্রতিটি বাইটে এতে সেট বিটের গণনা রয়েছে। আমাদের এগুলি সমস্ত একসাথে যুক্ত করতে হবে। কৌশলটি হ'ল ফলাফলটির গুণন করা 0b10101010যার দ্বারা একটি আকর্ষণীয় সম্পত্তি রয়েছে। যদি আমাদের সংখ্যার চারটি বাইট থাকে, A B C Dতবে এই বাইটগুলির সাথে এটি একটি নতুন সংখ্যার ফলাফল হবেA+B+C+D B+C+D C+D D । একটি 4 বাইট সংখ্যায় সর্বাধিক 32 বিট সেট থাকতে পারে, যা হিসাবে উপস্থাপিত হতে পারে 0b00100000

আমাদের এখন যা দরকার তা হ'ল প্রথম বাইট যা সমস্ত বাইটে সমস্ত সেট বিটের সমষ্টি করে এবং আমরা এটি পাই >> 24। এই অ্যালগরিদম 32 bitশব্দের জন্য ডিজাইন করা হয়েছিল তবে শব্দের জন্য সহজেই পরিবর্তন করা যেতে পারে 64 bit


কি c = সম্পর্কে? দেখে মনে হচ্ছে বাদ দেওয়া উচিত। আরও কিছু ক্লাসিক সতর্কতা এড়াতে অতিরিক্ত "পেরেন" এ (((ভি + (ভি >> 4)) এবং 0xF0F0F0F) * 0x1010101) >> 24 "প্রস্তাব দিন।
chux - মনিকা

4
একটি গুরুত্বপূর্ণ বৈশিষ্ট্য যা এই 32 বিট রুটিন উভয়ের জন্য কাজ করে popcount(int v)এবং popcount(unsigned v)। বহনযোগ্যতার জন্য, বিবেচনা করুন popcount(uint32_t v), ইত্যাদি সত্যিই * 0x1010101 অংশটি পছন্দ করুন।
chux -

সস? (বই, লিঙ্ক, উদ্ভাবকদের নাম ইত্যাদি) খুব স্বাগত জানানো হবে। কারণ তারপরে আমরা এটিকে কোথা থেকে আসে সে সম্পর্কে একটি মন্তব্য দিয়ে আমাদের কোডবাসে এটি আটকে দিতে পারি।
v.oddou

1
আমি মনে করি আরও ভাল স্বচ্ছতার জন্য শেষ পংক্তিটি এই হিসাবে লেখা উচিত: return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;সুতরাং আপনি আসলে কী করছেন তা দেখার জন্য আমাদের চিঠিগুলি গণনা করার দরকার নেই (যেহেতু আপনি প্রথমটি বাতিল করেছেন 0, আমি ভুলক্রমে ভেবেছিলাম যে আপনি ভুল (উল্টানো) বিট প্যাটার্নটি মাস্ক হিসাবে ব্যবহার করেছেন - এটি উল্লেখ না করা পর্যন্ত কেবলমাত্র 7 টি বর্ণ রয়েছে এবং 8 টি নয়)।
ইমাম

যে গুণ 0x01010101 দ্বারা ধীর হতে পারে, প্রসেসর উপর নির্ভর করে। উদাহরণস্বরূপ, আমার পুরাতন পাওয়ারবুক জি 4-তে, 1 গুণ 4 টি সংযোজন হিসাবে প্রায় ধীর ছিল (বিভাগের মতো খারাপ নয়, যেখানে 1 বিভাগটি 23 টি সংযোজন হিসাবে ধীর ছিল)।
জর্জ কোহলার

54

আমি বিরক্ত হয়েছি, এবং তিনটি পদ্ধতির এক বিলিয়ন পুনরাবৃত্তি করেছি। সংকলকটি জিসিসি -ও 3। সিপিইউ হ'ল যা তারা 1 ম জেনার ম্যাকবুক প্রোতে রেখেছিল।

দ্রুততমটি নিম্নলিখিতটি হল, 3.7 সেকেন্ডে:

static unsigned char wordbits[65536] = { bitcounts of ints between 0 and 65535 };
static int popcount( unsigned int i )
{
    return( wordbits[i&0xFFFF] + wordbits[i>>16] );
}

দ্বিতীয় স্থান একই কোডে যায় তবে 2 হাফওয়ার্ডের পরিবর্তে 4 বাইট সন্ধান করে। এটি প্রায় 5.5 সেকেন্ড সময় নিয়েছে।

তৃতীয় স্থানটি বিট-টুইডলিংয়ের 'পাশের পাশের সংযোজন' পদ্ধতির দিকে যায়, এটি 8..6 সেকেন্ড সময় নেয়।

চতুর্থ স্থানটি জিসিসির __ বিল্টিন_পপ্যাক্ট () -এ যায়, লজ্জাজনকভাবে 11 সেকেন্ডে।

এক-সময়ে-সময়ে গণনাটি ওয়াআআএএই ধীর ছিল এবং এটি শেষ হওয়ার অপেক্ষায় আমি বিরক্ত হয়ে পড়েছি।

সুতরাং আপনি যদি সর্বোপরি পারফরম্যান্সের বিষয়ে যত্নশীল হন তবে প্রথম পদ্ধতির ব্যবহার করুন। আপনি যদি যত্নশীল হন তবে এটিতে K৪ কেবি র‌্যাম ব্যয় করার পক্ষে পর্যাপ্ত পরিমাণ নেই, দ্বিতীয় পদ্ধতির ব্যবহার করুন। অন্যথায় পাঠযোগ্য (তবে ধীর) এক-বিট-এ-এ-সময় পদ্ধতির ব্যবহার করুন।

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

সম্পাদনা করুন: এখানে অনুরূপ ফলাফল ।


49
@ মাইক, টেবিলটি ক্যাশে থাকলে টেবিল ভিত্তিক পদ্ধতিটি অপরাজেয়। এটি মাইক্রো-বেঞ্চমার্কগুলিতে ঘটে (যেমন টান লুপে কয়েক মিলিয়ন পরীক্ষা করুন)। যাইহোক, একটি ক্যাশে মিস প্রায় 200 চক্র গ্রহণ করে এবং এমনকি সবচেয়ে নিষ্পাপ পপকাউন্ট এখানে দ্রুত হবে। এটি সর্বদা প্রয়োগের উপর নির্ভর করে।
নীল পিপেনব্রিংক

10
যদি আপনি এই রুটিনটিকে কয়েক মিলিয়ন বার শক্ত আঁটকে কল করেন না তবে এর পারফরম্যান্সটি মোটেও যত্ন নেওয়ার আপনার কোনও কারণ নেই এবং পারফরম্যান্স ক্ষতি হ্রাসহীন হওয়ায় নিখরচায়-তবে-পঠনযোগ্য পদ্ধতি ব্যবহার করতে পারেন। এবং এফডব্লিউআইডাব্লু, 8 বিট এলটিটি 10-20 কলের মধ্যে ক্যাশে-গরম হয়ে যায়।

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

ঝাঁকুনি দিয়ে এটি ব্যবহার করে দেখুন, এটি বিল্টিনগুলি বাস্তবায়নে যথেষ্ট স্মার্ট।
ম্যাট যোগদানকারী

3
'পার্শ্ববর্তী রাস্তা সংযোজন' এর চেয়ে দ্রুততর ক্ষেত্রে-এমএসই 4.2 দিয়ে কল না করা হলে জিসিসি পপকন্টের নির্দেশ ছাড়বে না।
lvela

54

আপনি যদি জাভা ব্যবহার করে যাচ্ছেন, অন্তর্নির্মিত পদ্ধতিটি এটি Integer.bitCountকরবে।


যখন সূর্য বিভিন্ন এপিআই সরবরাহ করে, তখন অবশ্যই এটি ব্যাকগ্রাউন্ডে কিছু যুক্তি ব্যবহার করা উচিত?
বল্লভ পাতাদে

2
পার্শ্ব নোট হিসাবে, জাভা প্রয়োগে কেভিন লিটল দ্বারা নির্দেশিত একই অ্যালগরিদম ব্যবহার করে ।
মার্কো বলিস

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

31
unsigned int count_bit(unsigned int x)
{
  x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
  x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
  x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
  x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
  x = (x & 0x0000FFFF) + ((x >> 16)& 0x0000FFFF);
  return x;
}

আমাকে এই অ্যালগরিদম ব্যাখ্যা করুন।

এই অ্যালগরিদম বিভাজন এবং বিজয়ী অ্যালগোরিদমের উপর ভিত্তি করে। ধরুন এখানে 8 বিট পূর্ণসংখ্যা 213 (বাইনারিতে 11010101) রয়েছে, অ্যালগরিদম এইভাবে কাজ করে (প্রতিটি সময় দুটি প্রতিবেশী ব্লককে একীভূত করে):

+-------------------------------+
| 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |  <- x
|  1 0  |  0 1  |  0 1  |  0 1  |  <- first time merge
|    0 0 1 1    |    0 0 1 0    |  <- second time merge
|        0 0 0 0 0 1 0 1        |  <- third time ( answer = 00000101 = 5)
+-------------------------------+

7
এই অ্যালগরিদম হ'ল ম্যাট হাওয়েলস যে সংস্করণটি পড়েছে তা অপছন্দ হওয়ার আগেই এটি অপ্টিমাইজ হওয়ার আগে পোস্ট করা সংস্করণ।
লেফটারিস ই

29

এটি সেই প্রশ্নগুলির মধ্যে একটি যেখানে এটি আপনার মাইক্রো-আর্কিটেকচারটি জানতে সহায়তা করে। আমি সেক্ষেত্রে জিসিসি ৪.৩.৩ এর অধীনে দুটি বৈকল্পিক টাইম করেছি - ফাংশন কল ওভারহেড নির্মূল করতে সি ++ ইনলাইন ব্যবহার করে এক বিলিয়ন পুনরাবৃত্তি, টাইপিংয়ের জন্য আরডিএসসি ব্যবহার করে গুরুত্বপূর্ণ কিছু মুছে ফেলছে না তা নিশ্চিত করার জন্য সমস্ত গুনের চলমান যোগফল রেখেছি ( ঘড়ি চক্র সুনির্দিষ্ট)।

ইনলাইন ইন পপ 2 (স্বাক্ষরযুক্ত এক্স, স্বাক্ষরযুক্ত y)
{
    x = x - ((x >> 1) & 0x55555555);
    y = y - ((y >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    y = (y & 0x33333333) + ((y >> 2) & 0x33333333);
    x = (x + (x >> 4)) এবং 0x0F0F0F0F;
    y = (y + (y >> 4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    y = y + (y >> 8);
    x = x + (x >> 16);
    y = y + (y >> 16);
    রিটার্ন (x + y) & 0x000000FF;
}

অশোধিত হিকারের আনন্দ 12.2 গিগ্যাসিকেল নিয়েছিল। আমার সমান্তরাল সংস্করণ (বহু বিটের দ্বিগুণ গণনা) ১৩.০ গিগ্যাসিচলে চলে। 10.5s মোট ২.৪ গিগাহার্টজ কোর ডুওয়ে দুজনের জন্যই কেটে গেছে। এই ঘড়ির ফ্রিকোয়েন্সিতে 25 গিগ্যাসিলেস = মাত্র 10 সেকেন্ডের বেশি, তাই আমি নিশ্চিত যে আমার সময় সঠিক।

এটি নির্দেশ নির্ভরতা শৃঙ্খলাগুলির সাথে করতে হবে যা এই অ্যালগরিদমের পক্ষে খুব খারাপ। আমি এক জোড়া 64৪-বিট রেজিস্টার ব্যবহার করে আবার গতি প্রায় দ্বিগুণ করতে পারি। প্রকৃতপক্ষে, আমি যদি চালাক এবং এক্স + ইয়া যুক্ত করি তবে খুব শীঘ্রই আমি কিছু শিফট শেভ করতে পারতাম। কিছু ছোট টুইটের সাথে -৪-বিট সংস্করণটি প্রায় প্রকাশিত হবে তবে আবার দ্বিগুণ হিসাবে বিটগুলি গণনা করুন।

128 বিট সিমডি রেজিস্টার সহ, দুটি আরও একটি কারণ, এবং এসএসই নির্দেশিকা সেটগুলিতে প্রায়শই চতুর শর্ট-কাট রয়েছে।

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

ঠিক আছে, আমি টুইট করা -৪-বিট সংস্করণটি বেঞ্চ করার সিদ্ধান্ত নিয়েছি। এটির জন্য এক আকারের (স্বাক্ষরযুক্ত দীর্ঘ) == 8

ইনলাইন ইন পপ 2 (স্বাক্ষরবিহীন দীর্ঘ এক্স, স্বাক্ষরযুক্ত দীর্ঘ y)
{
    x = x - ((x >> 1) & 0x5555555555555555);
    y = y - ((y >> 1) & 0x5555555555555555);
    x = (x এবং 0x333333333333333333) + ((x >> 2) & 0x333333333333333333);
    y = (y & 0x333333333333333333) + ((y >> 2) & 0x333333333333333333);
    x = (x + (x >> 4)) এবং 0x0F0F0F0F0F0F0F0F0F;
    y = (y + (y >> 4)) এবং 0x0F0F0F0F0F0F0F0F0F;
    x = x + y; 
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = x + (x >> 32); 
    এক্স & 0xFF ফিরে;
}

এটি সঠিক সম্পর্কে দেখায় (যদিও আমি সাবধানে পরীক্ষা করছি না)। এখন সময়গুলি 10.70 গিগ্যাসিলে / 14.1 গিগ্যাসিলে বের হয়। পরে এটির সংখ্যা 128 বিলিয়ন বিট এবং এই মেশিনে বিগত 5.9 এর সাথে মিলে যায়। অ সমান্তরাল সংস্করণটি একটি সামান্য বিট বাড়ায় কারণ আমি 64৪-বিট মোডে চলছি এবং এটি 32৪-বিট রেজিস্টারের চেয়ে 64৪-বিট রেজিস্টারগুলিকে পছন্দ করে।

আসুন দেখুন এখানে আরও কিছু OO পাইপলাইনিং রয়েছে কিনা। এটি কিছুটা বেশি জড়িত ছিল, তাই আমি আসলে কিছুটা পরীক্ষা করেছি। প্রতিটি পদ এককভাবে 64৪ এর সমষ্টি, সমস্ত মিলিত পরিমাণ 256।

ইনলাইন ইন পপ 4 (স্বাক্ষরবিহীন দীর্ঘ এক্স, স্বাক্ষরযুক্ত লম্বা y, 
                স্বাক্ষরবিহীন দীর্ঘ ইউ, স্বাক্ষরযুক্ত দীর্ঘ ভি)
{
  এনাম {এম 1 = 0x5555555555555555, 
         এম 2 = 0x3333333333333333, 
         এম 3 = 0x0F0F0F0F0F0F0F0F, 
         m4 = 0x000000FF000000FF};

    x = x - ((x >> 1) & এম 1);
    y = y - ((y >> 1) & এম 1);
    u = u - ((u >> 1) & এম 1);
    v = v - ((v >> 1) & এম 1);
    x = (x & m2) + ((x >> 2) & এম 2);
    y = (y & m2) + ((y >> 2) & এম 2);
    u = (u & m2) + ((u >> 2) & এম 2);
    v = (v & m2) + ((v >> 2) & এম 2);
    x = x + y; 
    u = u + v; 
    x = (x & m3) + ((x >> 4) & এম 3);
    u = (u & m3) + ((u >> 4) & এম 3);
    x = x + u; 
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = এক্স & এম 4; 
    x = x + (x >> 32);
    এক্স & 0x000001FF রিটার্ন করুন;
}

আমি এক মুহুর্তের জন্য উচ্ছ্বসিত ছিলাম, তবে দেখা যাচ্ছে যে আমি কিছু পরীক্ষায় ইনলাইন কীওয়ার্ডটি ব্যবহার করছি না, যদিও জিসিসি -O3 এর সাথে ইনলাইন কৌশলগুলি খেলছে। আমি যখন জিসিসি কৌশল খেলি, পপ 4 এ এক বিলিয়ন কল () 12.56 গিগ্যাসিকেল নেয়, কিন্তু আমি স্থির করেছিলাম যে এটি আর্গুমেন্টগুলি স্থির মত প্রকাশ হিসাবে ভাঁজ করা হয়েছিল। আরও 30% স্পিড-আপের জন্য আরও বাস্তবসম্মত সংখ্যা 19.6gc বলে মনে হয়। আমার পরীক্ষার লুপটি এখন দেখতে এরকম, এটি নিশ্চিত করে ট্র্যাক খেলতে জিসিসি থামাতে প্রতিটি যুক্তি যথেষ্ট আলাদা।

   হিটাইম বি 4 = আরডিটিএসসি (); 
   (স্বাক্ষরযুক্ত দীর্ঘ i = 10L * 1000 * 1000 * 1000; i <11L * 1000 * 1000 * 1000; ++ আমি) 
      যোগ + = পপ 4 (আমি, আমি ^ 1, ~ i, i | 1); 
   হিটাইম e4 = rdtsc (); 

8.17 সেকেন্ডে 256 বিলিয়ন বিট যোগ হয়েছে। 16-বিট সারণী অনুসন্ধানে বেঞ্চমার্ক হিসাবে 32 মিলিয়ন বিটের জন্য 1.02s এ কাজ করে। সরাসরি তুলনা করতে পারছি না, কারণ অন্য বেঞ্চটি একটি ঘড়ির গতি দেয় না, তবে দেখে মনে হচ্ছে যে আমি 64 কেবি টেবিল সংস্করণে সটকে বাইরে বের করে দিয়েছি, এটি প্রথম স্থানে এল 1 ক্যাশের করুণ ব্যবহার।

আপডেট: আরও চারটি নকল লাইন যুক্ত করে সুস্পষ্ট করার এবং পপ 6 () তৈরি করার সিদ্ধান্ত নিয়েছে। 22.8gc এ এসেছিল, 9.5 সেকেন্ডে 384 বিলিয়ন বিট যোগ হয়েছে। সুতরাং 32 মিলিয়ন বিটের জন্য 800ms এ এখন আরও 20% রয়েছে।


2
এর মতো সেরা নন-এসেম্বলারের ফর্মটি আমি একবারে আনারোলড 24 32 বিট শব্দ দেখেছি। dalkescientific.com/writings/diary/popcnt.c , stackoverflow.com/questions/3693981/... , dalkescientific.com/writings/diary/archive/2008/07/05/...
ম্যাট যোজক

28

কেন পুনরাবৃত্তভাবে 2 দ্বারা বিভক্ত হয় না?

গণনা = 0
যখন এন> 0
  যদি (n% 2) == 1
    গণনা + = 1
  এন / = 2  

আমি সম্মত হই যে এটি দ্রুততম নয়, তবে "সেরা" কিছুটা অস্পষ্ট। আমি যুক্তি দিচ্ছি যদিও "সেরা" এর স্পষ্টতার একটি উপাদান থাকা উচিত


এটি কাজ করবে এবং বোঝা সহজ, তবে দ্রুত পদ্ধতি রয়েছে।
ম্যাট হাওয়েলস

2
যতক্ষণ না আপনি এই একটি কি অনেক কর্মক্ষমতা প্রভাব তুচ্ছ হবে। সুতরাং সমস্ত জিনিস সমান, আমি ড্যানিয়েলের সাথে একমত যে 'সেরা' বোঝায় "গীব্রিশের মতো পড়া হয় না"।

2
আমি ইচ্ছাকৃতভাবে 'সেরা' সংজ্ঞা দিই না, বিভিন্ন পদ্ধতি অর্জনের জন্য। যদি আমরা এই ধরণের বিট-টুইডলিংয়ের স্তরে পৌঁছে যাই তবে আমরা এটির মুখোমুখি হতে পারি আমরা সম্ভবত একটি উবার-দ্রুত এমন কিছু সন্ধান করছি যা দেখে মনে হচ্ছে কোনও শিম্প এটি টাইপ করেছে।
ম্যাট হাওয়েলস

6
খারাপ কোড। একটি সংকলক এটি থেকে ভাল উপার্জন করতে পারে, কিন্তু আমার পরীক্ষায় জিসিসি তা করেনি। (N & 2) এর সাথে (n% 2) প্রতিস্থাপন করুন; এবং মোডুলোর চেয়ে অনেক দ্রুত হচ্ছে। (N / = 2) এর সাথে (n >> = 1) প্রতিস্থাপন করুন; বিভাগের চেয়ে অনেক দ্রুত বিটশিটিং।
মেকি

6
@Mecki: আমার পরীক্ষার সালে জিসিসি (4.0, -O3) করেনি সুস্পষ্ট optimisations না।

26

আপনি বিট নিদর্শনগুলি লিখলে হ্যাকারের আনন্দের বিট-টুইডলিং এত বেশি স্পষ্ট হয়ে ওঠে becomes

unsigned int bitCount(unsigned int x)
{
  x = ((x >> 1) & 0b01010101010101010101010101010101)
     + (x       & 0b01010101010101010101010101010101);
  x = ((x >> 2) & 0b00110011001100110011001100110011)
     + (x       & 0b00110011001100110011001100110011); 
  x = ((x >> 4) & 0b00001111000011110000111100001111)
     + (x       & 0b00001111000011110000111100001111); 
  x = ((x >> 8) & 0b00000000111111110000000011111111)
     + (x       & 0b00000000111111110000000011111111); 
  x = ((x >> 16)& 0b00000000000000001111111111111111)
     + (x       & 0b00000000000000001111111111111111); 
  return x;
}

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


3
এই সমাধানটিতে অপারেটর অগ্রাধিকার সম্পর্কিত, সামান্য সমস্যা বলে মনে হচ্ছে। প্রতিটি টার্মের জন্য এটি বলা উচিত: x = (((x >> 1) & 0b0101010101010101010101010101010) + (x & 0b010101010101010101010101010101)); (অর্থাত্ অতিরিক্ত প্যারেন্স যুক্ত)।
নপিক

21

2 32 দেখার টেবিলের মধ্যে একটি সুখী মাধ্যমের জন্য এবং প্রতিটি বিট দিয়ে স্বতন্ত্রভাবে পুনরাবৃত্তি করা:

int bitcount(unsigned int num){
    int count = 0;
    static int nibblebits[] =
        {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
    for(; num != 0; num >>= 4)
        count += nibblebits[num & 0x0f];
    return count;
}

Http://ctips.pbwiki.com/CountBits থেকে


বহনযোগ্য নয়। সিপিইউতে 9 বিট বাইট থাকলে কী হবে? হ্যাঁ, সেখানে আসল সিপিইউ'র মতোই রয়েছে ...
রবার্ট এস বার্নেস

15
@ রবার্ট এস বার্নস, এই ফাংশনটি এখনও কাজ করবে। এটি স্থানীয় শব্দের আকার সম্পর্কে কোনও ধারণা তৈরি করে না এবং "বাইটস" এর কোনও উল্লেখ নেই।
ফিনউইউ

19

এটি করা যেতে পারে O(k), যেখানে kবিটের সংখ্যা সেট রয়েছে।

int NumberOfSetBits(int n)
{
    int count = 0;

    while (n){
        ++ count;
        n = (n - 1) & n;
    }

    return count;
}

এটি মূলত ব্রায়ান কর্নিগানের (তাকে মনে রাখবেন?) অ্যালগোরিদম, এই ছোটখাটো পরিবর্তন সহ যে তিনি আরও সংক্ষিপ্ত n &= (n-1)রূপটি ব্যবহার করেছিলেন ।
অ্যাড্রিয়ান মোল

17

এটি দ্রুত বা সর্বোত্তম সমাধান নয়, তবে আমি আমার পথে একই প্রশ্নটি পেয়েছি এবং আমি ভাবতে এবং ভাবতে শুরু করি। অবশেষে আমি বুঝতে পেরেছিলাম যে এটি গাণিতিক দিক থেকে সমস্যাটি পেয়ে যদি এটি এমনভাবে করা যায় এবং একটি গ্রাফ আঁকেন, তবে আপনি দেখতে পাবেন এটি একটি ফাংশন যার কিছু পর্যায়ক্রমিক অংশ রয়েছে, এবং তারপরে আপনি পিরিয়ডের মধ্যে পার্থক্য বুঝতে পারবেন ... তাই আপনি এখানে যান:

unsigned int f(unsigned int x)
{
    switch (x) {
        case 0:
            return 0;
        case 1:
            return 1;
        case 2:
            return 1;
        case 3:
            return 2;
        default:
            return f(x/4) + f(x%4);
    }
}

4
ওহ আমি পছন্দ করি কীভাবে অজগর সংস্করণটি:def f(i, d={0:lambda:0, 1:lambda:1, 2:lambda:1, 3:lambda:2}): return d.get(i, lambda: f(i//4) + f(i%4))()
আন্ডারআরনে গেছে un

10

আপনি যে ফাংশনটির জন্য সন্ধান করছেন তা প্রায়ই বাইনারি সংখ্যার "পাশের সমষ্টি" বা "জনসংখ্যা গণনা" নামে পরিচিত। নথ এটিকে প্রাক-ফ্যাসিক্যাল 1 এ, পিপি 11-12-তে আলোচনা করেছেন (যদিও খণ্ড 2, 4.6.3- (7) এ সংক্ষিপ্ত রেফারেন্স ছিল))

রুম classicus পিটার Wegner এর নিবন্ধ "একটি বাইনারি কম্পিউটার কাউন্টিং ব্যক্তিদের একটি কৌশল", থেকে এসিএম এর কমিউনিকেশনস , ভলিউম 3 (1960) নম্বর 5, পৃষ্ঠা 322 । তিনি সেখানে দুটি পৃথক পৃথক অ্যালগরিদম দিয়েছেন, একটি "স্পারস" বলে প্রত্যাশিত সংখ্যার জন্য অপ্টিমাইজড (অর্থাত, একটি সংখ্যক সংখ্যক রয়েছে) এবং একটি বিপরীত মামলার জন্য।



9

কয়েকটি মুক্ত প্রশ্ন: -

  1. সংখ্যাটি যদি নেতিবাচক হয় তবে?
  2. যদি সংখ্যাটি 1024 হয়, তবে "পুনরাবৃত্তভাবে 2 দ্বারা ভাগ করুন" পদ্ধতিটি 10 ​​বার পুনরাবৃত্তি হবে।

followsণাত্মক সংখ্যাকে সমর্থন করার জন্য আমরা আলগো পরিবর্তন করতে পারি: -

count = 0
while n != 0
if ((n % 2) == 1 || (n % 2) == -1
    count += 1
  n /= 2  
return count

এখন দ্বিতীয় সমস্যাটি কাটিয়ে ওঠার জন্য আমরা আলগো লিখতে পারি: -

int bit_count(int num)
{
    int count=0;
    while(num)
    {
        num=(num)&(num-1);
        count++;
    }
    return count;
}

সম্পূর্ণ রেফারেন্সের জন্য দেখুন:

http://goursaha.freeoda.com/Miscellaneous/IntegerBitCount.html


9

আমি মনে করি ব্রায়ান কর্নিগানের পদ্ধতিটিও কার্যকর হবে ... সেট বিট রয়েছে যতটা পুনরাবৃত্তির মধ্য দিয়ে যায়। সুতরাং যদি আমাদের কাছে কেবলমাত্র উচ্চ বিট সেট সহ 32-বিট শব্দ থাকে তবে এটি কেবল একবার লুপের মধ্য দিয়ে যাবে।

int countSetBits(unsigned int n) { 
    unsigned int n; // count the number of bits set in n
    unsigned int c; // c accumulates the total bits set in n
    for (c=0;n>0;n=n&(n-1)) c++; 
    return c; 
}

1988 সালে প্রকাশিত, সি প্রোগ্রামিং ভাষা 2 য় এড। (ব্রায়ান ডাব্লু। কর্নিগান এবং ডেনিস এম। রিচি) অনুশীলনে এটি উল্লেখ করেছেন 2-9। ১৯ এপ্রিল, ২০০ On-এ ডন নুথ আমাকে ইঙ্গিত করেছিলেন যে এই পদ্ধতিটি "পিটার ওয়েগনার দ্বারা প্রথম প্রকাশিত হয়েছিল সিএসিএম 3 (1960), 322 সালে। এছাড়াও ডেরিক লেহারের দ্বারা স্বাধীনভাবে আবিষ্কার করা হয়েছিল এবং 1964 সালে বেকেনবাচের সম্পাদিত একটি বইতে প্রকাশিত হয়েছিল।"


8

আমি নীচের কোডটি ব্যবহার করি যা আরও স্বজ্ঞাত।

int countSetBits(int n) {
    return !n ? 0 : 1 + countSetBits(n & (n-1));
}

যুক্তি: এন এবং (এন -1) এন এর শেষ সেট বিট পুনরায় সেট করে।

পিএস: আমি জানি এটি একটি ও (1) সমাধান নয়, তবে একটি আকর্ষণীয় সমাধান।


এটি কম বিট সহ "স্পার্স" সংখ্যার পক্ষে ভাল O(ONE-BITS)। এটি হ'ল ও (1) যেহেতু সর্বাধিক 32 টি বিট রয়েছে।
ealfonso

7

"সেরা অ্যালগরিদম" এর অর্থ কী? সংক্ষিপ্ত কোড বা দ্রুত কোড? আপনার কোডটি খুব মার্জিত দেখাচ্ছে এবং এটির একটি ধ্রুবক প্রয়োগের সময় রয়েছে। কোডটিও খুব ছোট।

তবে যদি গতিটি প্রধান ফ্যাক্টর এবং কোডের আকার না হয় তবে আমি মনে করি অনুসরণটি আরও দ্রুত হতে পারে:

       static final int[] BIT_COUNT = { 0, 1, 1, ... 256 values with a bitsize of a byte ... };
        static int bitCountOfByte( int value ){
            return BIT_COUNT[ value & 0xFF ];
        }

        static int bitCountOfInt( int value ){
            return bitCountOfByte( value ) 
                 + bitCountOfByte( value >> 8 ) 
                 + bitCountOfByte( value >> 16 ) 
                 + bitCountOfByte( value >> 24 );
        }

আমি মনে করি যে এটি একটি 64 বিট মানের জন্য আরও দ্রুত হবে না তবে 32 বিট মানটি আরও দ্রুত হতে পারে।


আমার কোডটিতে 10 টি অপারেশন রয়েছে। আপনার কোডে 12 টি অপারেশন রয়েছে। আপনার লিঙ্কটি ছোট অ্যারে (5) এর সাথে কাজ করে। আমি 256 উপাদান ব্যবহার করি। ক্যাচিংয়ের সাথে সমস্যা হতে পারে। তবে আপনি যদি খুব ঘন ঘন এটি ব্যবহার করেন তবে এটি কোনও সমস্যা নয়।
Horcrux7

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

7

আমি প্রায় 1990 সালে আরআইএসসি মেশিনগুলির জন্য একটি দ্রুত বিটক্টন ম্যাক্রো লিখেছিলাম It এটি উন্নত পাটিগণিত (গুণ, বিভাগ,%), মেমরি ফেচগুলি (খুব ধীরগতির), শাখা (খুব ধীরে ধীরে) ব্যবহার করে না তবে এটি ধরে নেয় যে সিপিইউতে একটি রয়েছে 32-বিট ব্যারেল শিফটার (অন্য কথায়, >> 1 এবং >> 32 একই পরিমাণে চক্র গ্রহণ করে)) এটি ধরে নিয়েছে যে ছোট ধ্রুবকগুলি (যেমন 6, 12, 24) নিবন্ধগুলিতে লোড করার জন্য কিছুই খরচ করে না বা সংরক্ষণ করা হয় অস্থায়ী মধ্যে এবং বার বার পুনরায় ব্যবহৃত।

এই অনুমানগুলি সহ, এটি বেশিরভাগ আরআইএসসি মেশিনে প্রায় 16 চক্র / নির্দেশিকায় 32 বিট গণনা করে। নোট করুন যে 15 টি নির্দেশাবলী / চক্রগুলি চক্র বা নির্দেশাবলীর সংখ্যার উপরের নীচে আবদ্ধ হওয়ার কাছাকাছি, কারণ মনে হয় অর্ধেক সংখ্যক সংযোজন সংখ্যা কমিয়ে আনতে কমপক্ষে 3 টি নির্দেশ (মাস্ক, শিফট, অপারেটর) লাগে, সুতরাং লগ 2 (32) = 5, 5 x 3 = 15 নির্দেশাবলী একটি আধা-নিম্নমানের।

#define BitCount(X,Y)           \
                Y = X - ((X >> 1) & 033333333333) - ((X >> 2) & 011111111111); \
                Y = ((Y + (Y >> 3)) & 030707070707); \
                Y =  (Y + (Y >> 6)); \
                Y = (Y + (Y >> 12) + (Y >> 24)) & 077;

এখানে প্রথম এবং সবচেয়ে জটিল পদক্ষেপের গোপনীয়তা রয়েছে:

input output
AB    CD             Note
00    00             = AB
01    01             = AB
10    01             = AB - (A >> 1) & 0x1
11    10             = AB - (A >> 1) & 0x1

সুতরাং আমি যদি উপরে 1 ম কলাম (এ) গ্রহণ করি, তবে এটি ডান 1 বিটকে স্থানান্তর করুন এবং এ বি থেকে বিয়োগ করলে আমি আউটপুট (সিডি) পাই। 3 বিট এক্সটেনশন একই; আপনি যদি চান তবে উপরের মতো 8-সারির বুলিয়ান টেবিল দিয়ে এটি পরীক্ষা করতে পারেন।

  • ডন গিলিজ

7

আপনি যদি সি ++ ব্যবহার করেন তবে অন্য বিকল্পটি হ'ল টেমপ্লেট মেটাগ্রোগ্রামিং ব্যবহার করা:

// recursive template to sum bits in an int
template <int BITS>
int countBits(int val) {
        // return the least significant bit plus the result of calling ourselves with
        // .. the shifted value
        return (val & 0x1) + countBits<BITS-1>(val >> 1);
}

// template specialisation to terminate the recursion when there's only one bit left
template<>
int countBits<1>(int val) {
        return val & 0x1;
}

ব্যবহার হবে:

// to count bits in a byte/char (this returns 8)
countBits<8>( 255 )

// another byte (this returns 7)
countBits<8>( 254 )

// counting bits in a word/short (this returns 1)
countBits<16>( 256 )

আপনি অবশ্যই বিভিন্ন ধরণের (এমনকি স্বয়ং-সনাক্তকরণ বিট আকার) ব্যবহার করতে এই টেমপ্লেটটি আরও প্রসারিত করতে পারেন তবে আমি স্পষ্টতার জন্য এটি সহজ রেখেছি।

সম্পাদনা: এটি উল্লেখ করতে ভুলে গেছেন কারণ এটি যে কোনও সি ++ কম্পাইলারে কাজ করা উচিত এবং বিট কাউন্টের জন্য যদি একটি ধ্রুবক মান ব্যবহৃত হয় তবে এটি মূলত আপনার লুপটি আনারোল করে দেয় (অন্য কথায়, আমি খুব নিশ্চিত যে এটি দ্রুততম সাধারণ পদ্ধতি) তুমি খুঁজে পাবে)


দুর্ভাগ্যক্রমে, বিট গণনা সমান্তরালভাবে সম্পন্ন হয় নি, সুতরাং এটি সম্ভবত ধীর। constexprযদিও একটি সুন্দর করতে পারে ।
imallett

সম্মত - এটি সি ++ টেমপ্লেট পুনরাবৃত্তিতে একটি মজাদার অনুশীলন ছিল, তবে অবশ্যই একটি নিখুঁত সমাধান।
পেন্টাফোবি

6

আমি ভাগ্য ফাইল থেকে এই উদাহরণটি বিশেষভাবে পছন্দ করি:

# বিট অ্যাকাউন্ট (x) (((বিএক্স_ (এক্স) + (বিএক্স_ (এক্স) >> 4))) এবং 0x0F0F0F0F)% 255)
# ডিফাইন বিএক্স_ (এক্স) ((এক্স) - (((এক্স) >> 1) এবং 0x77777777)
                             - (((x) >> 2) এবং 0x33333333)
                             - (((x) >> 3) এবং 0x11111111))

আমি এটি সবচেয়ে ভাল কারণ এটি খুব সুন্দর!


1
অন্যান্য পরামর্শের তুলনায় এটি কীভাবে সম্পাদন করে?
06

6

জাভা জেডিকে ১.৫

Integer.bitCount (ঢ);

যেখানে n হল সেই সংখ্যাটি যার 1 টি গণনা করতে হবে।

পরীক্ষা করে দেখুন,

Integer.highestOneBit(n);
Integer.lowestOneBit(n);
Integer.numberOfLeadingZeros(n);
Integer.numberOfTrailingZeros(n);

//Beginning with the value 1, rotate left 16 times
     n = 1;
         for (int i = 0; i < 16; i++) {
            n = Integer.rotateLeft(n, 1);
            System.out.println(n);
         }

আসলে একটি অ্যালগরিদম নয়, এটি কেবল একটি লাইব্রেরি কল। জাভা জন্য দরকারী, অন্য সবার জন্য এত কিছু না।
বেনজাদো

2
@ বেনজাদো ঠিক তবে তবে +1, কারণ কিছু জাভা বিকাশকারীরা হয়ত পদ্ধতি সম্পর্কে অবগত নন
11:25 এ ফিনিউ করুন

@ ফিনবু, আমি সেই বিকাশকারীদের মধ্যে একজন। :)
neevek

6

আমি সিমডি নির্দেশাবলী (এসএসএসই 3 এবং এভিএক্স 2) ব্যবহার করে একটি অ্যারেতে বিট কাউন্টিংয়ের একটি বাস্তবায়ন পেয়েছি। এটি __popcnt64 অভ্যন্তরীণ ফাংশনটি ব্যবহার করবে তার চেয়ে এটি 2-2.5 গুণ ভাল পারফরম্যান্সে রয়েছে।

এসএসএসই 3 সংস্করণ:

#include <smmintrin.h>
#include <stdint.h>

const __m128i Z = _mm_set1_epi8(0x0);
const __m128i F = _mm_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m128i T = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m128i _sum =  _mm128_setzero_si128();
    for (size_t i = 0; i < size; i += 16)
    {
        //load 16-byte vector
        __m128i _src = _mm_loadu_si128((__m128i*)(src + i));
        //get low 4 bit for every byte in vector
        __m128i lo = _mm_and_si128(_src, F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m128i hi = _mm_and_si128(_mm_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, hi)));
    }
    uint64_t sum[2];
    _mm_storeu_si128((__m128i*)sum, _sum);
    return sum[0] + sum[1];
}

AVX2 সংস্করণ:

#include <immintrin.h>
#include <stdint.h>

const __m256i Z = _mm256_set1_epi8(0x0);
const __m256i F = _mm256_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m256i T = _mm256_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 
                                   0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m256i _sum =  _mm256_setzero_si256();
    for (size_t i = 0; i < size; i += 32)
    {
        //load 32-byte vector
        __m256i _src = _mm256_loadu_si256((__m256i*)(src + i));
        //get low 4 bit for every byte in vector
        __m256i lo = _mm256_and_si256(_src, F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m256i hi = _mm256_and_si256(_mm256_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, hi)));
    }
    uint64_t sum[4];
    _mm256_storeu_si256((__m256i*)sum, _sum);
    return sum[0] + sum[1] + sum[2] + sum[3];
}

6

আমি এটি প্রতিযোগিতামূলক প্রোগ্রামিংয়ে সর্বদা ব্যবহার করি এবং এটি লেখার পক্ষে সহজ এবং দক্ষ:

#include <bits/stdc++.h>

using namespace std;

int countOnes(int n) {
    bitset<32> b(n);
    return b.count();
}

5

সেট বিট গণনা করার জন্য অনেক অ্যালগরিদম রয়েছে; তবে আমি মনে করি সেরাটি দ্রুততর! আপনি এই পৃষ্ঠায় বিস্তারিত দেখতে পারেন:

বিট টুইডলিং হ্যাকস

আমি এটি একটি পরামর্শ:

বিট গণনা 14, 24, বা 32-বিট শব্দের মধ্যে 64-বিট নির্দেশাবলী ব্যবহার করে সেট করা হয়েছে

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v

// option 1, for at most 14-bit values in v:
c = (v * 0x200040008001ULL & 0x111111111111111ULL) % 0xf;

// option 2, for at most 24-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) 
     % 0x1f;

// option 3, for at most 32-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) % 
     0x1f;
c += ((v >> 24) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;

এই পদ্ধতির জন্য দ্রুত মডুলাস বিভাগ দক্ষ হওয়ার জন্য একটি 64-বিট সিপিইউ প্রয়োজন। প্রথম বিকল্পটি লাগে মাত্র 3 টি অপারেশন; দ্বিতীয় বিকল্পটি 10 ​​লাগে; এবং তৃতীয় বিকল্পটি লাগে 15।


5

বাইট বিটের পূর্ব-গণনাযুক্ত টেবিল ব্যবহার করে দ্রুত সি # দ্রষ্টব্য ইনপুট আকারের শাখা প্রশাখার সাথে গণনা করে।

public static class BitCount
{
    public static uint GetSetBitsCount(uint n)
    {
        var counts = BYTE_BIT_COUNTS;
        return n <= 0xff ? counts[n]
             : n <= 0xffff ? counts[n & 0xff] + counts[n >> 8]
             : n <= 0xffffff ? counts[n & 0xff] + counts[(n >> 8) & 0xff] + counts[(n >> 16) & 0xff]
             : counts[n & 0xff] + counts[(n >> 8) & 0xff] + counts[(n >> 16) & 0xff] + counts[(n >> 24) & 0xff];
    }

    public static readonly uint[] BYTE_BIT_COUNTS = 
    {
        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
    };
}

হাস্যকরভাবে, এই টেবিলটি এই থ্রেডে পোস্ট করা কোনও অ্যালগরিদম দ্বারা তৈরি করা যেতে পারে! তবুও, এই জাতীয় সারণী ব্যবহার করা মানে ধ্রুবক সময় পারফরম্যান্স। আরও এক ধাপ এগিয়ে গিয়ে 64৪ কে অনুবাদ টেবিল তৈরি করা এন্ড, শিফট এবং সংযোজন প্রয়োজনীয় অপারেশনকে অর্ধেক করে দেবে। বিট ম্যানিপুলেটরগুলির জন্য একটি আকর্ষণীয় বিষয়!
ব্যবহারকারী 924272

ক্যাশের সমস্যার কারণে বড় টেবিলগুলি ধীর হতে পারে (এবং ধ্রুবক নয়)। (0xe994 >>(k*2))&3স্মৃতি অ্যাক্সেস ছাড়াই আপনি একবারে 3 টি বিট 'অনুসন্ধান' করতে পারেন ...
গ্রেগগো

5

এখানে একটি পোর্টেবল মডিউল (এএনএসআই-সি) দেওয়া হয়েছে যা আপনার প্রতিটি অ্যালগরিদমকে কোনও আর্কিটেকচারে বেনমার্ক করতে পারে।

আপনার সিপিইউতে 9 বিট বাইট রয়েছে? কোনও সমস্যা নেই :-) এই মুহুর্তে এটি 2 টি অ্যালগরিদম, কেএন্ডআর অ্যালগরিদম এবং একটি বাইট অনুসারে অনুসন্ধানের সারণী প্রয়োগ করে। দেখার টেবিলটি কে ও আর অ্যালগরিদমের তুলনায় গড়ে তিনগুণ দ্রুত। যদি কেউ "হ্যাকারস ডিলাইট" অ্যালগরিদম বহনযোগ্য কোনও বানাতে কোনও উপায় আবিষ্কার করতে পারে তবে তা এটিকে বিনা দ্বিধায় যোগ করতে পারেন।

#ifndef _BITCOUNT_H_
#define _BITCOUNT_H_

/* Return the Hamming Wieght of val, i.e. the number of 'on' bits. */
int bitcount( unsigned int );

/* List of available bitcount algorithms.  
 * onTheFly:    Calculate the bitcount on demand.
 *
 * lookupTalbe: Uses a small lookup table to determine the bitcount.  This
 * method is on average 3 times as fast as onTheFly, but incurs a small
 * upfront cost to initialize the lookup table on the first call.
 *
 * strategyCount is just a placeholder. 
 */
enum strategy { onTheFly, lookupTable, strategyCount };

/* String represenations of the algorithm names */
extern const char *strategyNames[];

/* Choose which bitcount algorithm to use. */
void setStrategy( enum strategy );

#endif

#include <limits.h>

#include "bitcount.h"

/* The number of entries needed in the table is equal to the number of unique
 * values a char can represent which is always UCHAR_MAX + 1*/
static unsigned char _bitCountTable[UCHAR_MAX + 1];
static unsigned int _lookupTableInitialized = 0;

static int _defaultBitCount( unsigned int val ) {
    int count;

    /* Starting with:
     * 1100 - 1 == 1011,  1100 & 1011 == 1000
     * 1000 - 1 == 0111,  1000 & 0111 == 0000
     */
    for ( count = 0; val; ++count )
        val &= val - 1;

    return count;
}

/* Looks up each byte of the integer in a lookup table.
 *
 * The first time the function is called it initializes the lookup table.
 */
static int _tableBitCount( unsigned int val ) {
    int bCount = 0;

    if ( !_lookupTableInitialized ) {
        unsigned int i;
        for ( i = 0; i != UCHAR_MAX + 1; ++i )
            _bitCountTable[i] =
                ( unsigned char )_defaultBitCount( i );

        _lookupTableInitialized = 1;
    }

    for ( ; val; val >>= CHAR_BIT )
        bCount += _bitCountTable[val & UCHAR_MAX];

    return bCount;
}

static int ( *_bitcount ) ( unsigned int ) = _defaultBitCount;

const char *strategyNames[] = { "onTheFly", "lookupTable" };

void setStrategy( enum strategy s ) {
    switch ( s ) {
    case onTheFly:
        _bitcount = _defaultBitCount;
        break;
    case lookupTable:
        _bitcount = _tableBitCount;
        break;
    case strategyCount:
        break;
    }
}

/* Just a forwarding function which will call whichever version of the
 * algorithm has been selected by the client 
 */
int bitcount( unsigned int val ) {
    return _bitcount( val );
}

#ifdef _BITCOUNT_EXE_

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* Use the same sequence of pseudo random numbers to benmark each Hamming
 * Weight algorithm.
 */
void benchmark( int reps ) {
    clock_t start, stop;
    int i, j;
    static const int iterations = 1000000;

    for ( j = 0; j != strategyCount; ++j ) {
        setStrategy( j );

        srand( 257 );

        start = clock(  );

        for ( i = 0; i != reps * iterations; ++i )
            bitcount( rand(  ) );

        stop = clock(  );

        printf
            ( "\n\t%d psudoe-random integers using %s: %f seconds\n\n",
              reps * iterations, strategyNames[j],
              ( double )( stop - start ) / CLOCKS_PER_SEC );
    }
}

int main( void ) {
    int option;

    while ( 1 ) {
        printf( "Menu Options\n"
            "\t1.\tPrint the Hamming Weight of an Integer\n"
            "\t2.\tBenchmark Hamming Weight implementations\n"
            "\t3.\tExit ( or cntl-d )\n\n\t" );

        if ( scanf( "%d", &option ) == EOF )
            break;

        switch ( option ) {
        case 1:
            printf( "Please enter the integer: " );
            if ( scanf( "%d", &option ) != EOF )
                printf
                    ( "The Hamming Weight of %d ( 0x%X ) is %d\n\n",
                      option, option, bitcount( option ) );
            break;
        case 2:
            printf
                ( "Please select number of reps ( in millions ): " );
            if ( scanf( "%d", &option ) != EOF )
                benchmark( option );
            break;
        case 3:
            goto EXIT;
            break;
        default:
            printf( "Invalid option\n" );
        }

    }

 EXIT:
    printf( "\n" );

    return 0;
}

#endif

1
আমি আপনার প্লাগ-ইন, পলিমারফিক পন্থাগুলি, সেইসাথে পুনরায় ব্যবহারযোগ্য লাইব্রেরি বা স্ট্যান্ড-একা, পরীক্ষার সম্পাদনযোগ্য হিসাবে গড়ে তোলা পছন্দ করি। খুব ভালভাবে ভাবা =)

5

আপনি যা করতে পারেন তা হ'ল

while(n){
    n=n&(n-1);
    count++;
}

এর পিছনে যুক্তিটি হ'ল এন -1 এর বিটগুলি এন এর ডানদিকের সেট বিট থেকে উল্টানো হয়। যদি এন = ie অর্থাৎ ১১০ হয় তবে ৫ টি 101 এর বিটগুলি এন এর ডানদিকের সেট বিট থেকে উল্টানো হবে। সুতরাং আমরা এবং এই দুটি যদি আমরা প্রতিটি পুনরাবৃত্তিতে ডানদিকের বিট 0 করব এবং সর্বদা পরবর্তী ডানদিকের সেট বিটটিতে যাব, সেট বিটটি গণনা করছি worst প্রতিটা বিট সেট হয়ে গেলে সবচেয়ে খারাপ সময় জটিলতা ও (লগইন) হবে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.