අංක 7 නියෝජනය කරන බිටු 8 ක් මේ වගේ ය:
00000111
බිටු තුනක් සකසා ඇත.
බිටු 32 ක පූර්ණ සංඛ්යාවක් තුළ ඇති කට්ටල බිටු ගණන තීරණය කිරීම සඳහා ඇල්ගොරිතම මොනවාද?
අංක 7 නියෝජනය කරන බිටු 8 ක් මේ වගේ ය:
00000111
බිටු තුනක් සකසා ඇත.
බිටු 32 ක පූර්ණ සංඛ්යාවක් තුළ ඇති කට්ටල බිටු ගණන තීරණය කිරීම සඳහා ඇල්ගොරිතම මොනවාද?
Answers:
මෙය ' හැමිං බර ', 'පොප්කවුන්ට්' හෝ 'පසෙකට එකතු කිරීම' ලෙස හැඳින්වේ.
'හොඳම' ඇල්ගොරිතම සැබවින්ම රඳා පවතින්නේ ඔබ සිටින්නේ කුමන CPU මතද යන්න සහ ඔබේ භාවිත රටාව කුමක්ද යන්න මතය.
සමහර CPU වලට එය කිරීමට තනි නිමැවුම් උපදෙස් ඇති අතර අනෙක් ඒවාට සමාන්තර උපදෙස් ඇත, ඒවා බිට් දෛශික මත ක්රියා කරයි. සමාන්තර උපදෙස් (x86 වැනි popcnt
, එය සහාය දක්වන CPU වල) නිසැකවම පාහේ වේගවත් වනු ඇත. තවත් සමහර ගෘහ නිර්මාණ ශිල්පයට මයික්රෝ කේත ලූපයක් සමඟ මන්දගාමී උපදෙස් ක්රියාත්මක කළ හැකි අතර එය එක් චක්රයකට මඳක් පරීක්ෂා කරයි ( උපුටා දැක්වීම අවශ්ය වේ ).
ඔබේ CPU හි විශාල හැඹිලියක් තිබේ නම් සහ / හෝ ඔබ මෙම උපදෙස් බොහොමයක් තද පුඩුවක් තුළ කරන්නේ නම් පූර්ව ජනගහනය සහිත වගු බැලීමේ ක්රමයක් ඉතා වේගවත් විය හැකිය. කෙසේවෙතත්, 'හැඹිලි මිස්' හි වියදම නිසා එය දුක් විඳිය හැකිය, එහිදී CPU විසින් ප්රධාන මතකයෙන් වගු කිහිපයක් ලබා ගත යුතුය. (මේසය කුඩා ලෙස තබා ගැනීම සඳහා එක් එක් බයිටය වෙන වෙනම බලන්න.)
ඔබේ බයිට් බොහෝ විට 0 හෝ 1 ට වඩා වැඩි වනු ඇති බව ඔබ දන්නේ නම්, මෙම අවස්ථා සඳහා ඉතා කාර්යක්ෂම ඇල්ගොරිතම ඇත.
'සමාන්තර' හෝ 'විචල්ය-නිරවද්ය 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);
පළමු පියවර වන්නේ අමුතු / පවා බිටු හුදකලා කිරීම, ඒවා පෙළගැස්වීමට මාරුවීම සහ එකතු කිරීම සඳහා වෙස්මුහුණු කිරීමේ ප්රශස්ත අනුවාදයකි. මෙය effectively ලදායි ලෙස ද්වි -බිටු සමුච්චය තුළ වෙනම එකතු කිරීම් 16 ක් සිදු කරයි ( ලේඛනයක් තුළ SWAR = SIMD ). වගේ (i & 0x55555555) + ((i>>1) & 0x55555555)
.
මීලඟ පියවර එම 16x 2-bit සමුච්චකාරකවල අමුතු / අටක් ගෙන නැවත 8x 4-bit එකතුවක් නිපදවයි. මෙම අවස්ථාවේදී i - ...
ප්රශස්තිකරණය කළ නොහැකි බැවින් එය මාරුවීමට පෙර / පසු ආවරණ කරයි. මාරුවීමට පෙර 0x33...
වෙනුවට එකම නියතය දෙවරක් භාවිතා 0xccc...
කිරීම අයිඑස්ඒ සඳහා සම්පාදනය කිරීමේදී වෙන වෙනම ලේඛනයේ බිටු 32 නියතයන් සෑදිය යුතුය.
(i + (i >> 4)) & 0x0F0F0F0F
4x 8-bit සමුච්චය දක්වා පුළුල් කිරීමේ අවසාන මාරුව සහ එකතු කිරීමේ පියවර . අනුරූප ආදාන බිටු වල බිටු 4 ම සකසා ඇත්නම්, එය පෙර වෙනුවට වෙනුවට එකතු කිරීමෙන් පසුව ආවරණය 4
කරයි. 4 + 4 = 8 එය තවමත් බිටු 4 කට ගැලපේ, එබැවින් නිබ්බල් මූලද්රව්ය අතර ගෙන යා නොහැක i + (i >> 4)
.
මෙතෙක් මෙය තරමක් සාමාන්ය සිම්පතක් වන අතර දක්ෂ ප්රශස්තිකරණ කිහිපයක් සහිත SWAR ශිල්පීය ක්රම භාවිතා කරයි. තවත් පියවර 2 ක් සඳහා එකම රටාවකින් ඉදිරියට යාම 2x 16-bit දක්වා පුළුල් කළ හැකි අතර 1x 32-bit ගණන් කළ හැකිය. නමුත් වේගවත් දෘඩාංග ගුණනය සහිත යන්ත්රවල වඩාත් කාර්යක්ෂම ක්රමයක් ඇත:
අපට ප්රමාණවත් “මූලද්රව්ය” කිහිපයක් ඇති විට, මැජික් නියතයක් සමඟ ගුණ කිරීමෙන් සියලු මූලද්රව්යයන් ඉහළ මූලද්රව්යයට එකතු කළ හැකිය. මෙම අවස්ථාවේ දී බයිට් මූලද්රව්ය. ගුණ කිරීම සිදු කරනු ලබන්නේ වම් මාරුව සහ එකතු කිරීමෙනි, එබැවින් ප්රති results ල ගුණ කිරීම . x * 0x01010101
x + (x<<8) + (x<<16) + (x<<24)
අපගේ බිටු 8 මූලද්රව්යයන් ප්රමාණවත් තරම් පුළුල් වන අතර (ප්රමාණවත් තරම් කුඩා ප්රමාණයක් තබාගෙන) මෙම ඉහළම බිටු 8 තුළට ගෙන නොයයි .
මෙහි 64-බිට් අනුවාදයකට 0x0101010101010101 ගුණකයක් සහිත 64-බිටු නිඛිලයක 8x 8-bit මූලද්රව්ය කළ හැකි අතර ඉහළ බයිට් සමඟ උපුටා ගන්න >>56
. එබැවින් එය අතිරේක පියවරයන් නොගනී, පුළුල් නියතයන් පමණි. __builtin_popcountll
දෘඩාංග popcnt
උපදෙස් සක්රිය කර නොමැති විට xCC පද්ධති සඳහා GCC භාවිතා කරන්නේ මෙයයි. මේ සඳහා ඔබට බිල්ඩින් හෝ සහජයෙන්ම භාවිතා කළ හැකි නම්, ඉලක්කගත විශේෂිත ප්රශස්තිකරණ සිදු කිරීමට සම්පාදකයාට අවස්ථාවක් ලබා දෙන්න.
මෙම බිට්වේස්-එස්ඩබ්ලිව්ආර් ඇල්ගොරිතමය සමාන්තරගත කළ හැක්කේ එක් පූර්ණ සංඛ්යා ලේඛනයක් වෙනුවට එකවර දෛශික මූලද්රව්ය කිහිපයකින් සිදු කිරීමට ය. (උදා: x86-64 කේතය නෙහෙලම් හෝ පසුව පමණක් නොව ඕනෑම CPU මත ධාවනය කළ යුතුය.)
කෙසේ වෙතත්, පොප්කවුන්ට් සඳහා දෛශික උපදෙස් භාවිතා කිරීමට ඇති හොඳම ක්රමය නම් සාමාන්යයෙන් විචල්ය-මාරු කිරීමක් භාවිතා කරමින් එක් එක් බයිට් එකකට සමාන්තරව බිටු 4 ක් සඳහා වගු බැලීමක් කිරීමයි. (බිටු 4 දර්ශකය දෛශික ලේඛනයේ තබා ඇති 16 ප්රවේශ වගුවක්).
Intel CPUs හි, දෘඩාංග 64bit popcnt උපදෙස් මඟින් SSSE3 PSHUFB
බිට්-සමාන්තර ක්රියාත්මක කිරීම 2 ක සාධකයකින් ඉක්මවා යා හැක , නමුත් ඔබේ සම්පාදකයා එය නිවැරදිව ලබා ගන්නේ නම් පමණි . එසේ නොමැතිනම් SSE සැලකිය යුතු ලෙස ඉදිරියට පැමිණිය හැකිය. නව සම්පාදක අනුවාදයන් ඉන්ටෙල් හි පොප්කන්ට් ව්යාජ පරායත්තතා ගැටලුව පිළිබඳව දැනුවත් ය .
යොමුව:
unsigned int
, එය කිසිදු සං bit ා බිට් සංකූලතා වලින් තොර බව පහසුවෙන් පෙන්වීමට. එසේම uint32_t
සියලු වේදිකාවලින් ඔබ අපේක්ෂා කරන දේ ඔබට ආරක්ෂිතද?
>>
negative ණ අගයන් සඳහා ක්රියාත්මක කිරීම-අර්ථ දක්වා ඇත. තර්කය වෙනස් කිරීමට (හෝ වාත්තු කිරීමට) අවශ්ය unsigned
වන අතර කේතය 32-බිටු-විශේෂිත බැවින් එය බොහෝ විට භාවිතා කළ යුතුය uint32_t
.
ඔබේ සම්පාදකයින්ගේ සාදන ලද කාර්යයන් ද සලකා බලන්න.
උදාහරණයක් ලෙස ග්නූ සම්පාදකයේ ඔබට භාවිතා කළ හැකිය:
int __builtin_popcount (unsigned int x);
int __builtin_popcountll (unsigned long long x);
නරකම අවස්ථාවකදී සම්පාදකයා ශ්රිතයකට ඇමතුමක් ජනනය කරයි. හොඳම අවස්ථාවේ දී සම්පාදකයා එකම කාර්යය වේගයෙන් සිදු කිරීම සඳහා cpu උපදෙස් විමෝචනය කරයි.
ජීසීසී අභ්යන්තරය විවිධ වේදිකා හරහා පවා ක්රියාත්මක වේ. X86 ගෘහ නිර්මාණ ශිල්පයේ පොප්කවුන්ට් ප්රධාන ධාරාව බවට පත්වනු ඇත, එබැවින් දැන් සහජයෙන්ම භාවිතා කිරීම ආරම්භ කිරීම අර්ථවත් කරයි. වෙනත් ගෘහ නිර්මාණ ශිල්පයට වසර ගණනාවක් තිස්සේ පොප්කවුන්ට් ඇත.
X86 මත, ඔබ ඒ සඳහා සහයෝගය භාර ගත නොහැකි බව සම්පාදකවරයා කියන්න පුළුවන් popcnt
සමග උපදෙස් -mpopcnt
හෝ -msse4.2
ද එම පරම්පරාව තුළ එකතු කරන ලද දෛශික උපදෙස් සක්රීය කිරීම. GCC x86 විකල්ප බලන්න . -march=nehalem
(හෝ -march=
ඔබේ කේතය උපකල්පනය කර සුසර කිරීමට ඔබට අවශ්ය ඕනෑම CPU) හොඳ තේරීමක් විය හැකිය. එහි ප්රති ing ලයක් ලෙස ද්විමය පැරණි CPU මත ධාවනය කිරීමෙන් නීති විරෝධී උපදෙස් දෝෂයක් ඇති වේ.
ඔබ ඒවා සාදන යන්ත්රය සඳහා ද්විමය ප්රශස්තිකරණය කිරීමට, භාවිතා කරන්න -march=native
(gcc, clang, හෝ ICC සමඟ).
MSVC x86 popcnt
උපදෙස් සඳහා සහජයෙන්ම සපයයි , නමුත් gcc මෙන් නොව එය සැබවින්ම දෘඩාංග උපදෙස් සඳහා සහජයෙන්ම වන අතර දෘඩාංග සහාය අවශ්ය වේ.
std::bitset<>::count()
සාදන ලද එකක් වෙනුවට භාවිතා කිරීම
න්යායට අනුව, ඉලක්කගත CPU සඳහා කාර්යක්ෂමව පොප් ගණනය කරන්නේ කෙසේදැයි දන්නා ඕනෑම සම්පාදකයෙකු එම ක්රියාකාරිත්වය ISO C ++ හරහා හෙළි කළ යුතුය std::bitset<>
. ප්රායෝගිකව, සමහර ඉලක්කගත CPU සඳහා සමහර අවස්ථාවල ඔබ bit-hack AND / shift / ADD සමඟ වඩා හොඳ විය හැකිය.
දෘඩාංග පොප්කවුන්ට් යනු විකල්ප දිගුවක් (x86 වැනි) ඉලක්කගත ගෘහ නිර්මාණ සඳහා, සෑම සම්පාදකයෙකුටම std::bitset
ලබා ගත හැකි විට එයින් ප්රයෝජන ගත හැකි නොවේ. නිදසුනක් ලෙස, popcnt
සම්පාදනය කරන වේලාවේදී MSVC හට සහය සක්රීය කිරීමට ක්රමයක් නොමැති අතර (SSE4.2 යන්නෙන් ගම්ය වේ, තාක්ෂණිකව වෙනම විශේෂාංග බිට් එකක් තිබුණද) සෑම විටම වගු විමසුමක් භාවිතා කරයි ./Ox /arch:AVX
popcnt
නමුත් අවම වශයෙන් ඔබට සෑම තැනකම ක්රියා කළ හැකි අතේ ගෙන යා හැකි යමක් ලැබෙනු ඇති අතර නිවැරදි ඉලක්ක විකල්ප සමඟ gcc / clang සමඟින් ඔබට සහාය දක්වන ගෘහ නිර්මාණ සඳහා දෘඩාංග පොප්කවුන්ට් ලැබේ.
#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();
}
ගොඩ්බෝල්ට් සම්පාදක ගවේෂකයේ gcc, clang, icc, සහ MSVC වෙතින් asm බලන්න .
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
PowerPC64 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- විශේෂිත නොවේ, නමුත් x86 සඳහා gcc / clang / icc සමඟ පමණක් සම්පාදනය කරයි.
තනි උපදෙස් පොප්කවුන්ට් නොමැතිව ගෘහ නිර්මාණ ශිල්පය සඳහා gcc හි පසුබෑම බයිට්-ඇට්-ටයිම් මේස විමසුමකි. උදාහරණයක් ලෙස ARM සඳහා මෙය අපූරු නොවේ .
std::bitset::count
. මෙය එක් __builtin_popcount
ඇමතුමකට සම්පාදනය කිරීමෙන් පසුව .
මගේ මතය අනුව, “හොඳම” විසඳුම වන්නේ වෙනත් ක්රමලේඛකයෙකුට (හෝ වසර දෙකකට පසුව මුල් ක්රමලේඛකයාට) විශාල අදහස් දැක්වීමකින් තොරව කියවිය හැකි විසඳුමයි. සමහරු දැනටමත් ලබා දී ඇති වේගවත්ම හෝ දක්ෂතම විසඳුම ඔබට අවශ්ය විය හැකි නමුත් ඕනෑම වේලාවක දක්ෂතාවයට වඩා කියවීමේ හැකියාව මම කැමැත්තෙමි.
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);
}
මේවා නිශ්චිත දත්ත වර්ග ප්රමාණ මත රඳා පැවතුනද ඒවා එතරම් අතේ ගෙන යා නොහැක. කෙසේ වෙතත්, බොහෝ කාර්ය සාධන ප්රශස්තිකරණය කෙසේ වෙතත් අතේ ගෙන යා නොහැකි බැවින් එය ගැටළුවක් නොවනු ඇත. ඔබට අතේ ගෙන යා හැකි නම්, මම කියවිය හැකි විසඳුමට ඇලී සිටිමි.
if ((value & 1) == 1) { count++; }
සමග count += value & 1
?
හැකර්ස් ඩයිලයිට් වෙතින්, පි. 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;
}
~ 20-ඊෂ් උපදෙස් වලින් ක්රියාත්මක වේ (ආරුක්කු මත රඳා පවතී), අතු නැත.
හැකර් ගේ ප්රීති වේ ප්රීතිමත්! බෙහෙවින් නිර්දේශිතයි.
Integer.bitCount(int)
මෙයම හරියටම ක්රියාත්මක කරයි.
pop
වෙනුවට එය හඳුන්වන ඕනෑම කෙනෙකුට හොඳ පයින් ගසමි population_count
(හෝ pop_cnt
ඔබට සංක්ෂිප්තයක් තිබිය යුතුය). Ar මාකෝබොලිස් ජාවා හි සියලුම සංස්කරණ සම්බන්ධයෙන් එය සත්යයක් වනු ඇතැයි මම සිතමි, නමුත් නිල වශයෙන් එය ක්රියාත්මක කිරීම මත රඳා පවතී :)
බැලීමේ වගු සහ පොප්කවුන්ට් භාවිතා නොකර වේගවත්ම ක්රමය පහත දැක්වේ. එය මෙහෙයුම් 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
ඉන්පසු අපි ඉහත ප්රති result ලය සාරාංශ කොට, බිට් 4 කින් සකස් කළ බිට් ගණන ගණනය කරමු. අන්තිම ප්රකාශය වඩාත් උපක්රමශීලී ය.
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
අපි එය තවදුරටත් බිඳ දමමු ...
v + (v >> 4)
එය දෙවන ප්රකාශයට සමාන ය; අපි ඒ වෙනුවට 4 කාණ්ඩවල කට්ටල බිටු ගණන් කරමු. අපි දන්නවා our අපගේ පෙර මෙහෙයුම් නිසා every සෑම නිබ්බයකම එහි සැකසූ බිටු ගණන ඇති බව. උදාහරණයක් බලමු. අපිට බයිට් තියෙනවා යැයි සිතමු 0b01000010
. එහි අර්ථය වන්නේ පළමු නිබ්ල්ට එහි 4bit කට්ටලයක් ඇති අතර දෙවැන්න එහි 2bit කට්ටලයක් ඇත. දැන් අපි එම නිබ්බල් එකට එකතු කරමු.
0b01000010 + 0b01000000
එය අපට පළමු නයිබලය තුළ බයිට් එකක සැකසූ බිටු ගණන ලබා දෙයි, 0b01100010
එබැවින් අපි අංකයේ ඇති සියලුම බයිට් වල අවසාන බයිට් හතර ආවරණය කරමු (ඒවා ඉවතලමින්).
0b01100010 & 0xF0 = 0b01100000
දැන් සෑම බයිටයකම එහි කට්ටල බිටු ගණනක් ඇත. අපි ඒවා සියල්ලම එකතු කළ යුතුයි. උපක්රමය නම් 0b10101010
සිත්ගන්නාසුලු දේපලක් ඇති ප්රති result ලය ගුණ කිරීමයි . අපගේ අංකයට බයිට් හතරක් තිබේ නම් A B C D
, එය මෙම බයිට් සමඟ නව අංකයක් ලබා දෙනු A+B+C+D B+C+D C+D D
ඇත. 4 බයිට් අංකයකට උපරිම බිටු 32 ක් තිබිය හැකි අතර එය නිරූපණය කළ හැකිය 0b00100000
.
අපට දැන් අවශ්ය වන්නේ සියලුම බයිට් වල ඇති සියලුම බිටු වල එකතුව ඇති පළමු බයිටය වන අතර, අපි එය ලබා ගනිමු >> 24
. මෙම ඇල්ගොරිතම 32 bit
වචන සඳහා නිර්මාණය කර ඇති නමුත් වචන සඳහා පහසුවෙන් වෙනස් කළ හැකිය 64 bit
.
c =
ගැන ද? පෙනෙන ආකාරයට ඉවත් කළ යුතුය. තවද, සමහර සම්භාව්ය අනතුරු ඇඟවීම් වලක්වා ගැනීම සඳහා A "(((v + (v >> 4)) සහ 0xF0F0F0F) * 0x1010101) >> 24" යන අමතර පරෙවියන් කට්ටලයක් යෝජනා කරන්න.
popcount(int v)
සහ දෙකම සඳහා ක්රියා කිරීමයි popcount(unsigned v)
. popcount(uint32_t v)
අතේ ගෙන යාහැකි , සලකා බලන්න , ආදිය. ඇත්ත වශයෙන්ම * 0x1010101 කොටසට කැමතියි.
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
එබැවින් ඔබ සැබවින්ම කරන්නේ කුමක්දැයි බැලීමට අපට ලිපි ගණන් කිරීම අවශ්ය නොවේ (ඔබ පළමුවැන්න ඉවත දැමූ බැවින්, ඔබ 0
වැරදීමකින් (පෙරළා දැමූ) බිට් රටාව වෙස්මුහුණක් ලෙස භාවිතා කර ඇතැයි මම සිතුවෙමි. - එනම් මා සඳහන් කරන තෙක් ඇත්තේ අක්ෂර 7 ක් මිස 8 ක් නොවේ).
මම කම්මැලියි, සහ ප්රවේශ තුනක පුනරාවර්තන බිලියනයක් ගත කළෙමි. සම්පාදකයා gcc -O3 වේ. CPU යනු ඔවුන් 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 ක් ගත වූ බිට්-ට්විඩ්ලිං 'පැති දෙපස එකතු කිරීම' ප්රවේශයට යයි.
සිව්වන ස්ථානය ජීසීසී හි __builtin_popcount () වෙත ලජ්ජා සහගත තත්පර 11 කින් ගමන් කරයි.
එක් වරකට ගණන් කිරීමේ ප්රවේශය ඉතා මන්දගාමී වූ අතර එය අවසන් වන තෙක් බලා සිටීම මට කම්මැලි විය.
එබැවින් ඔබ සියල්ලටම වඩා කාර්ය සාධනය ගැන සැලකිලිමත් වන්නේ නම් පළමු ප්රවේශය භාවිතා කරන්න. ඔබ සැලකිලිමත් නමුත් 64Kb RAM ප්රමාණයක් ඒ සඳහා වැය කිරීමට ප්රමාණවත් නොවේ නම්, දෙවන ප්රවේශය භාවිතා කරන්න. එසේ නොමැතිනම් කියවිය හැකි (නමුත් මන්දගාමී) එක්-වරකට ප්රවේශයක් භාවිතා කරන්න.
ඔබට බිටු විකෘති කිරීමේ ප්රවේශය භාවිතා කිරීමට අවශ්ය තත්වයක් ගැන සිතීම දුෂ්කර ය.
සංස්කරණය කරන්න: සමාන ප්රති results ල මෙහි ඇත.
ඔබ ජාවා භාවිතා කරන්නේ නම්, සාදන ලද ක්රමය Integer.bitCount
එය කරයි.
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;
}
මෙම ඇල්ගොරිතම පැහැදිලි කිරීමට මට ඉඩ දෙන්න.
මෙම ඇල්ගොරිතම පදනම් වී ඇත්තේ බෙදීම සහ යටත් කිරීමේ ඇල්ගොරිතම මත ය. 8bit නිඛිල 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)
+-------------------------------+
මෙය ඔබේ ක්ෂුද්ර ගෘහ නිර්මාණ ශිල්පය දැන ගැනීමට උපකාරී වන ප්රශ්න වලින් එකකි. ක්රියාකාරී ඇමතුම් උඩිස්, පුනරාවර්තන බිලියනයක් ඉවත් කිරීම සඳහා සී ++ ඉන්ලයින් භාවිතා කරමින් -ඕ 3 සමඟ සම්පාදනය කරන ලද ජීසීසී 4.3.3 යටතේ ප්රභේද දෙකක් මා විසින් කාලානුරූපව සකසා ඇති අතර, සම්පාදකයා වැදගත් කිසිවක් ඉවත් නොකරන බව සහතික කිරීම සඳහා සියලු ගණන්වල ධාවන එකතුව තබා ගනිමින්, වේලාව සඳහා rdtsc භාවිතා කරමින් ( ඔරලෝසු චක්රය හරියටම).
inline int pop2 (අත්සන් නොකළ x, අත්සන් නොකළ 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 ක් ගත්තේය. මගේ සමාන්තර අනුවාදය (බිටු මෙන් දෙගුණයක් ගණන් කිරීම) ගිගාසයිල් 13.0 කින් ධාවනය වේ. 2.4GHz Core Duo එකකින් දෙකම සඳහා 10.5s එකතුව. ගිගාසයිල් 25 ක් = මෙම ඔරලෝසු සංඛ්යාතයට තත්පර 10 කට වඩා වැඩිය, එබැවින් මගේ වේලාව නිවැරදි බව මට විශ්වාසයි.
මෙම ඇල්ගොරිතම සඳහා ඉතා නරක වන උපදෙස් පරායත්ත දාමයන් සමඟ මෙය සම්බන්ධ වේ. බිට් 64 රෙජිස්ටර් යුගලයක් භාවිතා කිරීමෙන් මට නැවත වේගය දෙගුණයක් කළ හැකිය. ඇත්ත වශයෙන්ම, මම දක්ෂ නම් සහ x + y ටිකක් එකතු කළහොත් මට මාරුවීම් කිහිපයක් කපා දැමිය හැකිය. 64-බිට් අනුවාදය කුඩා කරකැවීම් කිහිපයක් සමඟ පවා එළියට එනු ඇත, නමුත් නැවත බිටු මෙන් දෙගුණයක් ගණන් කරන්න.
බිට් 128 සිම්ඩ් රෙජිස්ටර් සමඟ, තවත් සාධක දෙකක් වන අතර, එස්එස්ඊ උපදෙස් කට්ටලවල බොහෝ විට දක්ෂ කෙටි කප්පාදුවක් ද ඇත.
කේතය විශේෂයෙන් විනිවිද පෙනෙන වීමට හේතුවක් නැත. අතුරුමුහුණත සරලයි, ඇල්ගොරිතම බොහෝ ස්ථානවල මාර්ගගතව සඳහන් කළ හැකි අතර එය පුළුල් ඒකක පරීක්ෂණයකට සුදුසු ය. එය පැකිලෙන ක්රමලේඛකයාට යමක් ඉගෙන ගැනීමට පවා පුළුවන. මෙම බිට් මෙහෙයුම් යන්ත්ර මට්ටමින් අතිශයින්ම ස්වාභාවිකය.
හරි, මම තීරණය කළා නවීකරණය කරන ලද 64-බිට් අනුවාදය. මේ සඳහා එක් ප්රමාණයක් (අත්සන් නොකල දිගු) == 8
inline int pop2 (අත්සන් නොකළ දිගු x, අත්සන් නොකළ දිගු y) { x = x - ((x >> 1) & 0x5555555555555555); y = y - ((y >> 1) & 0x5555555555555555); x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333); y = (y & 0x3333333333333333) + ((y >> 2) & 0x3333333333333333); x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F; y = (y + (y >> 4)) & 0x0F0F0F0F0F0F0F0F; x = x + y; x = x + (x >> 8); x = x + (x >> 16); x = x + (x >> 32); ආපසු x & 0xFF; }
එය හරි යැයි පෙනේ (මම පරිස්සමින් පරික්ෂා නොකරමි). දැන් වේලාව ගිගාසයිල් 10.70 / ගිගාසයිල් 14.1 ට එළියට එයි. එම පසුකාලීන සංඛ්යාව බිට් බිලියන 128 ක් වූ අතර මෙම යන්ත්රයේ ගතවූ 5.9 ට අනුරූප වේ. සමාන්තර නොවන අනුවාදය ඉතා සුළු වේගයකින් වේගවත් වන නිසා මම 64-බිට් මාදිලියේ ධාවනය වන අතර එය බිට් 64 රෙජිස්ටරයට බිට් 32 රෙජිස්ටරයට වඩා තරමක් හොඳය.
මෙහි OOO නල මාර්ගයක් තව ටිකක් තිබේදැයි බලමු. මෙය තව ටිකක් සම්බන්ධ වූ නිසා මම ඇත්ත වශයෙන්ම ටිකක් පරීක්ෂා කළෙමි. එක් එක් පදය පමණක් 64 ක් වන අතර සියල්ල එකතුව 256 කි.
inline int pop4 (අත්සන් නොකළ දිගු x, අත්සන් නොකළ දිගු y, අත්සන් නොකළ දිගු u, අත්සන් නොකළ දිගු v) { enum {m1 = 0x5555555555555555, m2 = 0x3333333333333333, m3 = 0x0F0F0F0F0F0F0F0F, m4 = 0x000000FF000000FF}; x = x - ((x >> 1) & m1); y = y - ((y >> 1) & m1); u = u - ((u >> 1) & m1); v = v - ((v >> 1) & m1); x = (x & m2) + ((x >> 2) & m2); y = (y & m2) + ((y >> 2) & m2); u = (u & m2) + ((u >> 2) & m2); v = (v & m2) + ((v >> 2) & m2); x = x + y; u = u + v; x = (x & m3) + ((x >> 4) & m3); u = (u & m3) + ((u >> 4) & m3); x = x + u; x = x + (x >> 8); x = x + (x >> 16); x = x & m4; x = x + (x >> 32); ආපසු x & 0x000001FF; }
මම මොහොතකට කලබල වූ නමුත් සමහර පරීක්ෂණ වලදී මම ඉන්ලයින් යතුරු පදය භාවිතා නොකලද gcc -O3 සමඟ පේළිගත උපක්රම සෙල්ලම් කරන බව පෙනේ. මම gcc සෙල්ලම් උපක්රම වලට ඉඩ දෙන විට, pop4 () වෙත බිලියනයක ඇමතුම් ගිගාසයිල් 12.56 ක් ගත වේ, නමුත් එය නියත ප්රකාශන ලෙස තර්ක නැවීම බව මම තීරණය කළෙමි. තවත් 30% වේගවත් කිරීම සඳහා වඩාත් යථාර්ථවාදී සංඛ්යාවක් 19.6gc ලෙස පෙනේ. මගේ ටෙස්ට් ලූපය දැන් මේ ආකාරයට පෙනේ, සෑම තර්කයක්ම gcc උපක්රම නැවැත්වීමට තරම් වෙනස් බව සහතික කර ගන්න.
hitime b4 = rdtsc (); සඳහා (අත්සන් නොකළ දිගු i = 10L * 1000 * 1000 * 1000; i <11L * 1000 * 1000 * 1000; ++ i) sum + = pop4 (i, i ^ 1, ~ i, i | 1); hitime e4 = rdtsc ();
බිටු බිලියන 256 ක් 8.17s හි සාරාංශගත විය. බිට් 16 වගු බැලීමේදී මිණුම් සලකුණු කර ඇති පරිදි බිට් මිලියන 32 ක් සඳහා 1.02s දක්වා ක්රියා කරයි. කෙලින්ම සංසන්දනය කළ නොහැක, මන්ද අනෙක් බංකුව ඔරලෝසු වේගයක් ලබා නොදේ, නමුත් 64KB වගු සංස්කරණයෙන් මම කුඩා කැබැල්ල කපා දැමූ බවක් පෙනේ, එය L1 හැඹිලිය ඛේදජනක ලෙස භාවිතා කිරීමකි.
යාවත්කාලීන කිරීම: පැහැදිලිව පෙනෙන දේ කිරීමට තීරණය කර තවත් අනුපිටපත් පේළි හතරක් එකතු කර pop6 () නිර්මාණය කරන්න. ග්රෑම් 22.8 ක් දක්වා ඉහළ ගිය අතර, බිට් බිලියන 384 ක් 9.5s හි සාරාංශගත විය. ඉතින් තවත් 20% ක් දැන් බිට් බිලියන 32 ක් සඳහා 800ms දී ඇත.
නැවත 2 කින් බෙදන්නේ නැත්තේ ඇයි?
ගණන් = 0 අතර n> 0 if (n% 2) == 1 ගණන් + = 1 n / = 2
මෙය වේගවත්ම නොවන බව මම එකඟ වෙමි, නමුත් "හොඳම" තරමක් අපැහැදිලි ය. මම තර්ක කරන්නේ “හොඳම” යන්නට පැහැදිලි බවක් තිබිය යුතු බවයි
ඔබ බිට් රටා ලියන විට හැකර්ගේ ඩිලයිට් බිට්-ට්විඩ්ලිං වඩාත් පැහැදිලි වේ.
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;
}
පළමු පියවර අමුතු බිටු වලට ඉරට්ටේ බිටු එකතු කරන අතර, සෑම දෙකකින්ම බිටු එකතුවක් නිපදවයි. අනෙක් පියවරයන් අඩු ඇණවුම් කුට්ටි වලට ඉහළ ඇණවුම් කුට්ටි එකතු කරන අතර, කැබැල්ලේ ප්රමාණය දෙගුණ කරයි.
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;
}
O(floor(log2(num))/4)
, num
හැකි තරම් අත්තනෝමතික ලෙස විශාල විය හැකි යැයි උපකල්පනය කරන්නේද? මෙම නිසා while
ක්රියාවලිය සඳහා nibble තියෙනවා තාක් කල් පුඩුවක් ක්? ඇත floor(log2(num))
බිටු හා floor(log2(num)) / 4
නිබලය. තර්කය නිවැරදිද?
මෙය කළ හැක්කේ O(k)
, k
බිට් ගණන සකසා ඇති ස්ථානයෙනි .
int NumberOfSetBits(int n)
{
int count = 0;
while (n){
++ count;
n = (n - 1) & n;
}
return count;
}
n &= (n-1)
වේ.
එය වේගවත්ම හෝ හොඳම විසඳුම නොවේ, නමුත් මම එකම ප්රශ්නය මගේ මාර්ගයෙන් සොයා ගත් අතර, මම සිතීමට හා සිතීමට පටන් ගතිමි. ඔබ ගණිතමය පැත්තෙන් ගැටලුව ලබාගෙන ප්රස්ථාරයක් අඳින්නේ නම් එය මේ ආකාරයට කළ හැකි බව අවසාන වශයෙන් මට වැටහුණි, එවිට එය යම් ආවර්තිතා කොටසක් ඇති ශ්රිතයක් බව ඔබට පෙනී යනු ඇත, එවිට කාල පරිච්ඡේද අතර වෙනස ඔබට වැටහේ ... ඉතින් හියර් යූ ගෝ:
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);
}
}
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))()
ඔබ සොයන ශ්රිතය බොහෝ විට ද්විමය සංඛ්යාවක “පැත්තක එකතුව” හෝ “ජනගහන ගණන” ලෙස හැඳින්වේ. නුත් එය පූර්ව ෆැසිකල් 1 ඒ, පි 11-12 හි සාකච්ඡා කරයි (2 වන වෙළුමේ කෙටි සඳහනක් තිබුණද, 4.6.3- (7).)
මෙම ඉඩකඩක් classicus සිට පේදුරු Wegner ලිපිය "ද්විමය පරිගණක දී තාක්ෂණය ගණන් අය ගැන", යනු ද ඒ.සී.එම් සන්නිවේදන , වෙළුම 3 (1960) අංකය 5, 322 පිටුව . ඔහු එහිදී විවිධ ඇල්ගොරිතම දෙකක් ලබා දෙයි, එකක් “විරල” යැයි අපේක්ෂා කරන සංඛ්යා සඳහා ප්රශස්තිකරණය කර ඇත (එනම්, කුඩා සංඛ්යාවක් ඇත) සහ එකක් ප්රතිවිරුද්ධ අවස්ථාව සඳහා.
විවෘත ප්රශ්න කිහිපයක්: -
පහත දැක්වෙන පරිදි negative ණ අංකයට සහය දැක්වීම සඳහා අපට ඇල්ගෝ වෙනස් කළ හැකිය: -
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
මම හිතන්නේ බ්රයන් කර්නිගන් ගේ ක්රමය ද ප්රයෝජනවත් වනු ඇත ... එය සැකසූ බිටු ඇති තරම් පුනරාවර්තන හරහා ගමන් කරයි. ඉතින් අපට ඉහළ බිට් කට්ටලයක් සහිත 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 ව්යායාමයේදී මෙය සඳහන් කරයි. 2006 අප්රියෙල් 19 වන දින ඩොන් නූත් මට පෙන්වා දුන්නේ මෙම ක්රමය "පීටර් වෙග්නර් විසින් CACM 3 (1960), 322 දී ප්රථම වරට ප්රකාශයට පත් කරන ලද බවයි.
මම පහත කේතය භාවිතා කරන්නේ එය වඩාත් බුද්ධිමත් ය.
int countSetBits(int n) {
return !n ? 0 : 1 + countSetBits(n & (n-1));
}
තර්කනය: n & (n-1) අවසාන කට්ටලය n නැවත සකසයි.
PS: මෙය O (1) විසඳුමක් නොවන බව මම දනිමි.
O(ONE-BITS)
. එය ඇත්ත වශයෙන්ම ඕ (1) වන බැවින් උපරිම බිටු 32 ක් ඇත.
"හොඳම ඇල්ගොරිතම" සමඟ ඔබ අදහස් කරන්නේ කුමක්ද? කෙටි කේතය හෝ නිරාහාර කේතය? ඔබේ කේතය ඉතා අලංකාර පෙනුමක් ඇති අතර එය නිරන්තරයෙන් ක්රියාත්මක කිරීමේ කාලයක් ඇත. කේතය ද ඉතා කෙටි ය.
නමුත් වේගය ප්රධාන සාධකය මිස කේත ප්රමාණය නොවේ නම් මම සිතන්නේ අනුගමනය කිරීම වේගවත් විය හැකිය:
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 ක අගයක් වේගවත් විය හැකිය.
මම 1990 දී පමණ RISC යන්ත්ර සඳහා වේගවත් බිට්කවුන්ට් මැක්රෝවක් ලිව්වෙමි. එය උසස් ගණිතය (ගුණ කිරීම, බෙදීම,%), මතක ලබා ගැනීම (මාර්ගය ඉතා මන්දගාමී), ශාඛා (මාර්ගය ඉතා මන්දගාමී) භාවිතා නොකරයි, නමුත් එය උපකල්පනය කරන්නේ CPU සතුව 32-බිට් බැරල් මාරුව (වෙනත් වචන වලින් කිවහොත්, >> 1 සහ >> 32 එකම චක්ර ප්රමාණයක් ගනී.) කුඩා නියතයන්ට (6, 12, 24 වැනි) ලේඛනයට පැටවීමට කිසිවක් වැය නොවන බව හෝ ගබඩා කර ඇති බව උපකල්පනය කරයි. තාවකාලිකව සහ නැවත නැවත භාවිතා කරයි.
මෙම උපකල්පන සමඟ, එය බොහෝ RISC යන්ත්රවල චක්ර 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 වන තීරුව (A) ගෙන, එය බිට් 1 ක් දකුණට ගෙන AB වෙතින් අඩු කළහොත්, මට ප්රතිදානය (CD) ලැබේ. බිටු 3 දක්වා දිගුව සමාන වේ; ඔබට අවශ්ය නම් මගේ වැනි පේළි 8 කින් යුත් බූලියන් වගුවකින් ඔබට එය පරීක්ෂා කළ හැකිය.
ඔබ C ++ භාවිතා කරන්නේ නම් තවත් විකල්පයක් වන්නේ අච්චු පරිවෘත්තීයකරණය භාවිතා කිරීමයි:
// 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 )
ඔබට විවිධ වර්ගවල (ස්වයංක්රීයව හඳුනාගැනීමේ බිට් ප්රමාණය පවා) භාවිතා කිරීමට මෙම අච්චුව තවදුරටත් පුළුල් කළ හැකි නමුත් පැහැදිලි කිරීම සඳහා මම එය සරලව තබා ගතිමි.
සංස්කරණය කරන්න: මෙය හොඳ යැයි සඳහන් කිරීමට අමතක වී ඇති බැවින් එය ඕනෑම C ++ සම්පාදකයක වැඩ කළ යුතු අතර එය මූලික වශයෙන් බිට් ගණන් කිරීම සඳහා නියත අගයක් භාවිතා කරන්නේ නම් එය ඔබේ ලූපය මුදා හරිනු ඇත (වෙනත් වචන වලින් කිවහොත්, එය වේගවත්ම සාමාන්ය ක්රමය බව මට හොඳටම විශ්වාසයි ඔබ සොයා ගනීවි)
constexpr
පුළුවන්.
වාසනාවන්ත ගොනුවෙන් මෙම උදාහරණයට මම විශේෂයෙන් කැමතියි:
# BITCOUNT (x) නිර්වචනය කරන්න (((BX_ (x) + (BX_ (x) >> 4)) & 0x0F0F0F0F)% 255) # BX_ (x) ((x) - (((x) >> 1) සහ 0x77777777) යන්න අර්ථ දක්වන්න - (((x) >> 2) & 0x33333333) - (((x) >> 3) & 0x11111111))
මම එයට වඩාත්ම කැමතියි එය ඉතා ලස්සන නිසා!
ජාවා JDK1.5
Integer.bitCount (n);
මෙහි 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);
}
සිම්ඩ් උපදෙස් (SSSE3 සහ AVX2) භාවිතා කරමින් අරාව තුළ බිට් ගණන් කිරීම ක්රියාත්මක කිරීමක් මට හමු විය. එය __popcnt64 සහජ ශ්රිතය භාවිතා කරනවාට වඩා 2-2.5 ගුණයකින් හොඳ කාර්ය සාධනයක් ඇත.
SSSE3 අනුවාදය:
#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];
}
ඔබට කළ හැක්කේ එයයි
while(n){
n=n&(n-1);
count++;
}
මෙහි පිටුපස ඇති තර්කනය වන්නේ n-1 හි බිටු දකුණු කෙළවරේ සිට n දක්වා හරවා යැවීමයි. n = 6 එනම් 110 නම් 5 යනු 101 වේ නම් බිටු දකුණට සකසන ලද බිට් n සිට ප්රතිලෝම වේ. එබැවින් අපි සහ මේ දෙක නම් අපි සෑම පුනරාවර්තනයකදීම නිවැරදිම බිට් 0 කර සෑම විටම ඊළඟ දකුණු කෙළවරේ බිට් වෙතට යමු. හෙන්ස්, සෙට් බිට් ගණන් කිරීම. සෑම බිට් එකක්ම සකසන විට නරකම කාල සංකීර්ණතාව ඕ (ලොග්) වනු ඇත.
කට්ටල බිටු ගණනය කිරීම සඳහා බොහෝ ඇල්ගොරිතම ඇත; නමුත් මම හිතන්නේ හොඳම එක වේගවත් එකක්! මෙම පිටුවේ සවිස්තරාත්මකව ඔබට දැක ගත හැකිය:
මම මෙය යෝජනා කරමි:
බිටු 64, උපදෙස් භාවිතා කරමින් බිට් 14, 24, හෝ 32-බිට් වචන වලින් ගණන් කර ඇත
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-බිට් CPU අවශ්ය වේ. පළමු විකල්පය අවශ්ය වන්නේ මෙහෙයුම් 3 ක් පමණි; දෙවන විකල්පය 10 ක් ගනී; තෙවන විකල්පය 15 ක් ගනී.
ආදාන ප්රමාණයෙන් අතු බෙදීම් සමඟ බයිට් බිට් ගණනය කිරීම් කලින් ගණනය කළ වගුව භාවිතා කරමින් වේගවත් සී # විසඳුම.
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
};
}
(0xe994 >>(k*2))&3
මතක ප්රවේශයකින් තොරව ඔබට වරකට බිටු 3 ක් 'බැලීමට' හැකිය ...
ඕනෑම ගෘහ නිර්මාණ ශිල්පයක් පිළිබඳ ඔබේ එක් එක් ඇල්ගොරිතම මිණුම් සලකුණු කළ හැකි අතේ ගෙන යා හැකි මොඩියුලයක් (ANSI-C) මෙන්න.
ඔබේ CPU එකට බිට් බයිට් 9 ක් තිබේද? ගැටළුවක් නැත :-) මේ මොහොතේ එය ඇල්ගොරිතම 2 ක්, කේ ඇන්ඩ් ආර් ඇල්ගොරිතම සහ බයිට් නැණවත් බැලීමේ වගුවක් ක්රියාත්මක කරයි. බැලීමේ වගුව සාමාන්යයෙන් K&R ඇල්ගොරිතමයට වඩා 3 ගුණයක් වේගවත් ය. "හැකර්ස් ඩයිලයිට්" ඇල්ගොරිතම අතේ ගෙන යා හැකි ක්රමයක් බවට පත් කිරීමට යමෙකුට හැකි නම් එය ඇතුලත් කිරීමට නිදහස්ව සිටින්න.
#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