මම popcount
විශාල දත්ත කාණ්ඩයකට වේගවත්ම මාර්ගය සොයමින් සිටියෙමි . මම මුහුණ ඉතා අමුතු සිට පුඩුවක් විචල්ය වෙනස්: ක්රියාත්මක unsigned
කිරීමට uint64_t
මගේ පළාත් සභා මත 50% කින් කාර්ය සාධනය පහත විය.
බෙන්ච්මාර්ක්
#include <iostream>
#include <chrono>
#include <x86intrin.h>
int main(int argc, char* argv[]) {
using namespace std;
if (argc != 2) {
cerr << "usage: array_size in MB" << endl;
return -1;
}
uint64_t size = atol(argv[1])<<20;
uint64_t* buffer = new uint64_t[size/8];
char* charbuffer = reinterpret_cast<char*>(buffer);
for (unsigned i=0; i<size; ++i)
charbuffer[i] = rand()%256;
uint64_t count,duration;
chrono::time_point<chrono::system_clock> startP,endP;
{
startP = chrono::system_clock::now();
count = 0;
for( unsigned k = 0; k < 10000; k++){
// Tight unrolled loop with unsigned
for (unsigned i=0; i<size/8; i+=4) {
count += _mm_popcnt_u64(buffer[i]);
count += _mm_popcnt_u64(buffer[i+1]);
count += _mm_popcnt_u64(buffer[i+2]);
count += _mm_popcnt_u64(buffer[i+3]);
}
}
endP = chrono::system_clock::now();
duration = chrono::duration_cast<std::chrono::nanoseconds>(endP-startP).count();
cout << "unsigned\t" << count << '\t' << (duration/1.0E9) << " sec \t"
<< (10000.0*size)/(duration) << " GB/s" << endl;
}
{
startP = chrono::system_clock::now();
count=0;
for( unsigned k = 0; k < 10000; k++){
// Tight unrolled loop with uint64_t
for (uint64_t i=0;i<size/8;i+=4) {
count += _mm_popcnt_u64(buffer[i]);
count += _mm_popcnt_u64(buffer[i+1]);
count += _mm_popcnt_u64(buffer[i+2]);
count += _mm_popcnt_u64(buffer[i+3]);
}
}
endP = chrono::system_clock::now();
duration = chrono::duration_cast<std::chrono::nanoseconds>(endP-startP).count();
cout << "uint64_t\t" << count << '\t' << (duration/1.0E9) << " sec \t"
<< (10000.0*size)/(duration) << " GB/s" << endl;
}
free(charbuffer);
}
ඔබ දකින පරිදි, අපි අහඹු දත්ත බෆරයක් සාදන්නෙමු, එහි ප්රමාණය x
මෙගාබයිට් වන අතර x
විධාන රේඛාවෙන් කියවනු ලැබේ. පසුව, අපි බෆරය හරහා popcount
නැවත ක්රියා කරන අතර පොප්කවුන්ට් සිදු කිරීම සඳහා x86 අභ්යන්තරයේ අනවශ්ය අනුවාදයක් භාවිතා කරමු. වඩාත් නිවැරදි ප්රති result ලයක් ලබා ගැනීම සඳහා අපි පොප්කවුන්ට් 10,000 වතාවක් කරන්නෙමු. අපි පොප්කවුන්ට් සඳහා වේලාවන් මැන බලමු. ඉහළ නඩුවේදී, අභ්යන්තර ලූප විචල්යය unsigned
, පහළ අවස්ථාවෙහිදී, අභ්යන්තර ලූප විචල්යය වේuint64_t
. මම හිතුවේ මේකෙන් කිසිම වෙනසක් වෙන්න ඕනේ නැහැ, නමුත් ප්රතිවිරුද්ධ දෙයයි.
(නියත වශයෙන්ම පිස්සු) ප්රති .ල
මම එය මේ ආකාරයට සම්පාදනය කරමි (g ++ version: උබුන්ටු 4.8.2-19ubuntu1):
g++ -O3 -march=native -std=c++11 test.cpp -o test
මෙන්න මගේ මත මෙම ප්රතිඵලය Haswell Core i7-4770K ධාවන, @ 3.50 GHz ට CPU test 1
(එසේ 1 MB අහඹු දත්ත):
- අත්සන් නොකළ 41959360000 0.401554 තත්පර 26.113 GB / s
- uint64_t 41959360000 0.759822 තත්පර 13.8003 GB / s
ඔබ දකින විට, යන කාර්ය uint64_t
අනුවාදය වේ අඩක් පමණක් ම එක් unsigned
අනුවාදය! ගැටළුව වන්නේ විවිධ එකලස් කිරීම් ජනනය වීමයි, නමුත් ඇයි? පළමුව, මම සම්පාදක දෝෂයක් ගැන සිතුවෙමි, එබැවින් මම උත්සාහ කළෙමි clang++
(උබුන්ටු ක්ලැන්ග් අනුවාදය 3.4-1ubuntu3):
clang++ -O3 -march=native -std=c++11 teest.cpp -o test
ප්රති ult ලය: test 1
- අත්සන් නොකළ 41959360000 0.398293 තත්පර 26.3267 GB / s
- uint64_t 41959360000 0.680954 තත්පර 15.3986 GB / s
ඉතින්, එය පාහේ එකම ප්රති result ලය වන අතර තවමත් අමුතුයි. නමුත් දැන් එය සුපිරි අමුතු දෙයක් බවට පත්වේ. ආදානයෙන් කියවන ලද බෆරයේ ප්රමාණය නියතයකින් ආදේශ කරමි 1
, එබැවින් මම වෙනස් කරමි:
uint64_t size = atol(argv[1]) << 20;
වෙත
uint64_t size = 1 << 20;
මේ අනුව, සම්පාදකයා දැන් සම්පාදක වේලාවේ බෆරයේ ප්රමාණය දනී. සමහර විට එයට ප්රශස්තිකරණ කිහිපයක් එකතු කළ හැකිය! මෙන්න මේ සඳහා අංක g++
:
- අත්සන් නොකළ 41959360000 0.509156 තත්පර 20.5944 GB / s
- uint64_t 41959360000 0.508673 තත්පර 20.6139 GB / s
දැන්, අනුවාද දෙකම සමානව වේගවත් ය. කෙසේ වෙතත්, unsigned
තව තවත් මන්දගාමී විය ! එය වැටී 26
කිරීමට 20 GB/s
එලෙස, එය නිරන්තර අගය පෙරමුණ විසින් නොවන නියතය වෙනුවට, deoptimization . බරපතල ලෙස, මෙහි සිදුවන්නේ කුමක්ද යන්න පිළිබඳව මට කිසිදු හෝඩුවාවක් නොමැත! නමුත් දැන් clang++
නව අනුවාදය සමඟ:
- අත්සන් නොකළ 41959360000 0.677009 තත්පර 15.4884 GB / s
- uint64_t 41959360000 0.676909 තත්පර 15.4906 GB / s
ඔහොම ඉන්න .. මොකක්? දැන්, අනුවාද දෙකම මන්දගාමී 15 GB / s දක්වා පහත වැටුණි . මේ අනුව, නිරන්තරයෙන් අගය විසින් නොවන නියතය වෙනුවට මන්දගාමී කේතය පවා හේතු දෙකම Clang සඳහා නඩු!
අයිවි බ්රිජ් සීපීයූ සමඟ සගයකුගෙන් මගේ මිණුම් ලකුණ සම්පාදනය කරන ලෙස මම ඉල්ලා සිටියෙමි. ඔහුට සමාන ප්රති results ල ලැබුණි, එබැවින් එය හැස්වෙල් ලෙස නොපෙනේ. සම්පාදකයින් දෙදෙනෙකු මෙහි අමුතු ප්රති results ල ලබා දෙන බැවින් එය සම්පාදක දෝෂයක් ලෙස නොපෙනේ. අපට මෙහි AMD CPU නොමැත, එබැවින් අපට පරීක්ෂා කළ හැක්කේ ඉන්ටෙල් සමඟ පමණි.
තවත් පිස්සුවක්, කරුණාකර!
පළමු උදාහරණය (ඇති එක සමඟ atol(argv[1])
) ගෙන static
විචල්යයට පෙර තබන්න , එනම්:
static uint64_t size=atol(argv[1])<<20;
G ++ හි මගේ ප්රති results ල මෙන්න:
- අත්සන් නොකළ 41959360000 0.396728 තත්පර 26.4306 GB / s
- uint64_t 41959360000 0.509484 තත්පර 20.5811 GB / s
ඔව්, තවත් විකල්පයක් . අප සතුව වේගවත් 26 GB / s ඇත u32
, නමුත් u64
අවම වශයෙන් 13 GB / s සිට 20 GB / s අනුවාදය දක්වා ලබා ගැනීමට අපට හැකි විය ! මගේ සගයාගේ පරිගණකයේ, u64
අනුවාදය අනුවාදයට වඩා වේගවත් u32
වූ අතර සියල්ලන්ගේ වේගවත්ම ප්රති result ලය ලබා දෙයි. කනගාටුවට කරුණක් නම්, මෙය පමණක් ක්රියාත්මක වන අතර g++
, clang++
ඒ ගැන සැලකිලිමත් වන බවක් නොපෙනේ static
.
මගේ ප්රශ්නය
ඔබට මෙම ප්රති results ල පැහැදිලි කළ හැකිද? විශේෂයෙන්ම:
u32
සහ එවැනි වෙනසක් තිබියu64
හැක්කේ කෙසේද?- නියත බෆරයේ ප්රමාණයෙන් නියත නොවන ආදේශ කිරීම අඩු ප්රශස්ත කේතයක් අවුලුවන්නේ කෙසේද?
- ආකාරය පිළිබඳ චේදයක් හැකි
static
ඉඟි පද අතර පත්u64
පුඩුවක් වේගවත්? මගේ සගයාගේ පරිගණකයේ මුල් කේතයට වඩා වේගවත්!
ප්රශස්තිකරණය උපක්රමශීලී ප්රදේශයක් බව මම දනිමි, කෙසේ වෙතත්, එවැනි කුඩා වෙනස්කම් 100% වෙනසකට තුඩු දිය හැකි යැයි මම කිසි විටෙකත් නොසිතුවෙමි ක්රියාත්මක කිරීමේ වේලාවේ ඇති කළ හැකි බවත්, නිරන්තර බෆරයේ ප්රමාණය වැනි කුඩා සාධක මගින් ප්රති results ල මුළුමනින්ම මුසු කළ හැකි . ඇත්ත වශයෙන්ම, මට සැමවිටම අවශ්ය වන්නේ 26 GB / s උත්පතන ගණනය කළ හැකි අනුවාදය ලබා ගැනීමටයි. මට සිතිය හැකි එකම විශ්වාසදායක ක්රමය වන්නේ මෙම නඩුව සඳහා එකලස් කිරීම පිටපත් කිරීම සහ පේළිගත එකලස් කිරීම භාවිතා කිරීමයි. කුඩා වෙනස්කම් වලින් පිස්සු වැටී ඇති බව පෙනෙන සම්පාදකයින් ඉවත් කිරීමට මට ඇති එකම ක්රමය මෙයයි. ඔයා සිතන්නේ කුමක් ද? බොහෝ කාර්ය සාධනය සමඟ කේතය විශ්වසනීයව ලබා ගැනීමට වෙනත් ක්රමයක් තිබේද?
විසුරුවා හැරීම
විවිධ ප්රති results ල සඳහා විසුරුවා හැරීම මෙන්න:
G ++ / u32 / non-const bufsize වෙතින් 26 GB / s අනුවාදය :
0x400af8:
lea 0x1(%rdx),%eax
popcnt (%rbx,%rax,8),%r9
lea 0x2(%rdx),%edi
popcnt (%rbx,%rcx,8),%rax
lea 0x3(%rdx),%esi
add %r9,%rax
popcnt (%rbx,%rdi,8),%rcx
add $0x4,%edx
add %rcx,%rax
popcnt (%rbx,%rsi,8),%rcx
add %rcx,%rax
mov %edx,%ecx
add %rax,%r14
cmp %rbp,%rcx
jb 0x400af8
G ++ / u64 / non-const bufsize වෙතින් 13 GB / s අනුවාදය :
0x400c00:
popcnt 0x8(%rbx,%rdx,8),%rcx
popcnt (%rbx,%rdx,8),%rax
add %rcx,%rax
popcnt 0x10(%rbx,%rdx,8),%rcx
add %rcx,%rax
popcnt 0x18(%rbx,%rdx,8),%rcx
add $0x4,%rdx
add %rcx,%rax
add %rax,%r12
cmp %rbp,%rdx
jb 0x400c00
ක්ලැන්ග් ++ / u64 / නොනවතින බුෆ්සයිස් වෙතින් 15 GB / s අනුවාදය :
0x400e50:
popcnt (%r15,%rcx,8),%rdx
add %rbx,%rdx
popcnt 0x8(%r15,%rcx,8),%rsi
add %rdx,%rsi
popcnt 0x10(%r15,%rcx,8),%rdx
add %rsi,%rdx
popcnt 0x18(%r15,%rcx,8),%rbx
add %rdx,%rbx
add $0x4,%rcx
cmp %rbp,%rcx
jb 0x400e50
G ++ / u32 & u64 / const bufsize වෙතින් 20 GB / s අනුවාදය :
0x400a68:
popcnt (%rbx,%rdx,1),%rax
popcnt 0x8(%rbx,%rdx,1),%rcx
add %rax,%rcx
popcnt 0x10(%rbx,%rdx,1),%rax
add %rax,%rcx
popcnt 0x18(%rbx,%rdx,1),%rsi
add $0x20,%rdx
add %rsi,%rcx
add %rcx,%rbp
cmp $0x100000,%rdx
jne 0x400a68
ක්ලැන්ග් ++ / u32 සහ u64 / const bufsize වෙතින් 15 GB / s අනුවාදය :
0x400dd0:
popcnt (%r14,%rcx,8),%rdx
add %rbx,%rdx
popcnt 0x8(%r14,%rcx,8),%rsi
add %rdx,%rsi
popcnt 0x10(%r14,%rcx,8),%rdx
add %rsi,%rdx
popcnt 0x18(%r14,%rcx,8),%rbx
add %rdx,%rbx
add $0x4,%rcx
cmp $0x20000,%rcx
jb 0x400dd0
සිත්ගන්නා කරුණ නම්, වේගවත්ම (26 GB / s) අනුවාදය ද දිගම වේ! එය භාවිතා කරන එකම විසඳුම බව පෙනේ lea
. සමහර අනුවාදයන් jb
පැනීමට භාවිතා කරයි, අනෙක් ඒවා භාවිතා කරයි jne
. නමුත් ඒ හැරුණු විට, සියලු අනුවාදයන් සැසඳිය හැකි බව පෙනේ. 100% ක කාර්යසාධන පරතරයක් ඇතිවිය හැක්කේ කොතැනින්දැයි මට නොපෙනේ, නමුත් එකලස් කිරීමේ විකේතනය කිරීමේදී මම එතරම් දක්ෂ නැත. මන්දගාමී (13 GB / s) අනුවාදය පවා ඉතා කෙටි හා හොඳ පෙනුමක්. කිසිවෙකුට මෙය පැහැදිලි කළ හැකිද?
උගත් පාඩම්
මෙම ප්රශ්නයට පිළිතුර කුමක් වුවත්; උණුසුම් කේත සමඟ සෑම විස්තරයක්ම වැදගත් විය හැකි බව මම ඉගෙන ගෙන ඇත්තෙමි . ලූප් විචල්යයක් සඳහා කුමන වර්ගයක් භාවිතා කළ යුතුදැයි මම කිසි විටෙකත් නොසිතුවෙමි, නමුත් ඔබ දකින පරිදි එවැනි සුළු වෙනසක් 100% වෙනසක් කළ හැකිය ! static
ප්රමාණයේ විචල්යය ඉදිරිපිට යතුරු පදය ඇතුළත් කිරීමත් සමඟ අප දුටු පරිදි, බෆරයේ ගබඩා වර්ගය පවා විශාල වෙනසක් කළ හැකිය ! අනාගතයේ දී, පද්ධති ක්රියාකාරිත්වය සඳහා ඉතා වැදගත් වන තද හා උණුසුම් ලූප ලිවීමේදී මම සෑම විටම විවිධ සම්පාදකයින් මත විවිධ විකල්ප පරීක්ෂා කරමි.
සිත්ගන්නා කරුණ නම්, මම දැනටමත් හතර වතාවක් ලූපය මුදා හැර ඇති නමුත් කාර්ය සාධන වෙනස තවමත් ඉහළ මට්ටමක පැවතීමයි. එබැවින් ඔබ ඉවත් නොකළද, ඔබට තවමත් ප්රධාන කාර්ය සාධන අපගමනයන්ට ලක්විය හැකිය. ඉතා රසවත්.