32-බිට් ලූප් කවුන්ටරය 64-බිට් සමඟ ප්‍රතිස්ථාපනය කිරීමෙන් ඉන්ටෙල් සීපීයූවල _mm_popcnt_u64 සමඟ පිස්සු කාර්ය සාධන අපගමනය හඳුන්වා දෙයි.


1434

මම 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ප්‍රමාණයේ විචල්‍යය ඉදිරිපිට යතුරු පදය ඇතුළත් කිරීමත් සමඟ අප දුටු පරිදි, බෆරයේ ගබඩා වර්ගය පවා විශාල වෙනසක් කළ හැකිය ! අනාගතයේ දී, පද්ධති ක්‍රියාකාරිත්වය සඳහා ඉතා වැදගත් වන තද හා උණුසුම් ලූප ලිවීමේදී මම සෑම විටම විවිධ සම්පාදකයින් මත විවිධ විකල්ප පරීක්ෂා කරමි.

සිත්ගන්නා කරුණ නම්, මම දැනටමත් හතර වතාවක් ලූපය මුදා හැර ඇති නමුත් කාර්ය සාධන වෙනස තවමත් ඉහළ මට්ටමක පැවතීමයි. එබැවින් ඔබ ඉවත් නොකළද, ඔබට තවමත් ප්‍රධාන කාර්ය සාධන අපගමනයන්ට ලක්විය හැකිය. ඉතා රසවත්.


9
බොහෝ අදහස්! ඔබට ඒවා චැට් මගින් නැරඹිය හැකි අතර ඔබට අවශ්‍ය නම් ඔබේම දෑ එහි තබන්න, නමුත් කරුණාකර මෙහි තවත් එකතු නොකරන්න!
ෂොග් 9

3
ද බලන්න popcnt උපදෙස් දී ගල්ෆ් නිකුත් 62011, බොරු දත්ත සත්කාර . වෙනත් අයෙකු එය සපයා ඇති නමුත් පිරිසිදු කිරීමේ දී එය නැති වී ඇති බව පෙනේ.
jww

මට කිව නොහැකි නමුත් ස්ථිතික සමඟ අනුවාදය සඳහා විසුරුවා හැරීම් වලින් එකක් ද? එසේ නොවේ නම්, ඔබට ලිපිය සංස්කරණය කර එය එකතු කළ හැකිද?
කෙලී එස්. ප්‍රංශ

Answers:


1563

වැරදිකරු: ව්‍යාජ දත්ත යැපීම (සහ සම්පාදකයා ඒ ගැන නොදැන සිටියි)

සැන්ඩි / අයිවි බ්‍රිජ් සහ හැස්වෙල් ප්‍රොසෙසර මත, උපදෙස්:

popcnt  src, dest

ගමනාන්ත ලේඛනය මත ව්‍යාජ යැපීමක් ඇති බව පෙනේ dest. උපදෙස් එයට පමණක් ලිවුවද, destක්‍රියාත්මක කිරීමට පෙර උපදෙස් සූදානම් වන තෙක් බලා සිටිනු ඇත . මෙම ව්‍යාජ පරායත්තතාවය (දැන්) ඉන්ටෙල් විසින් erratum HSD146 (Haswell) සහ SKL029 (Skylake) ලෙස ලේඛනගත කර ඇත.

ස්කයිලේක් මෙය සඳහා lzcntසහtzcnt .
කැනන් විල (සහ අයිස් විල) මේ සඳහා සවි කර popcntඇත.
bsf/ bsrසත්‍ය නිමැවුම් පරායත්තතාවයක් ඇත: ආදානය = 0 සඳහා වෙනස් නොකළ. (නමුත් සහජයෙන්ම එයින් ප්‍රයෝජන ගැනීමට ක්‍රමයක් නැත - AMD පමණක් එය ලේඛනගත කරන අතර සම්පාදකයින් එය නිරාවරණය නොකරයි.)

(ඔව්, මෙම උපදෙස් සියල්ලම එකම ක්‍රියාත්මක කිරීමේ ඒකකයේ ක්‍රියාත්මක වේ ).


මෙම පරායත්තතාවය popcntතනි ලූප පුනරාවර්තනයකින් තත්පර 4 ක් රඳවා නොගනී . ප්‍රොසෙසරයට විවිධ ලූප පුනරාවර්තන සමාන්තරගත කිරීමට නොහැකි වන පරිදි එය ලූප පුනරාවර්තන හරහා ගෙන යා හැකිය.

මෙම unsignedඑදිරිව uint64_tසහ අනෙකුත් වෙනදාටත් වඩා සෘජුවම ගැටලූවට බලපාන නැහැ. නමුත් ඒවා විචල්‍යයන්ට රෙජිස්ටර් පවරන රෙජිස්ටර් විබෙදන්නාට බලපෑම් කරයි.

ඔබගේ නඩුවේදී, වේගය යනු (ව්‍යාජ) පරායත්ත දාමයට ඇලී ඇති දෙයෙහි සෘජු ප්‍රති result ලයකි.

  • 13 GB / s දාමයක් ඇත: popcnt- add-popcnt - popcnt→ ඊළඟ ක්‍රියාවලිය
  • 15 GB / s දාමයක් ඇත: popcnt- add-popcnt - add→ ඊළඟ ක්‍රියාවලිය
  • 20 GB / s දාමයක් ඇත: popcnt - popcnt→ ඊළඟ ක්‍රියාවලිය
  • 26 GB / s දාමයක් ඇත: popcnt- popcnt→ ඊළඟ ක්‍රියාවලිය

20 GB / s සහ 26 GB / s අතර වෙනස වක්‍ර ඇමතීමේ සුළු පුරාවස්තුවක් ලෙස පෙනේ. කෙසේ හෝ ඔබ මෙම වේගයට ළඟා වූ පසු ප්‍රොසෙසරය වෙනත් බාධක වලට පහර දීමට පටන් ගනී.


මෙය පරීක්ෂා කිරීම සඳහා, මම සම්පාදකයා මග හැරීමට සහ මට අවශ්‍ය එකලස් කිරීම සඳහා පේළිගත එකලස් කිරීම භාවිතා කළෙමි. countමිණුම් සලකුණු සමඟ පටලවා ගත හැකි අනෙකුත් සියලු පරායත්තතා බිඳ දැමීමට මම විචල්‍යය බෙදුවෙමි.

ප්‍රති results ල මෙන්න:

සැන්ඩි බ්‍රිජ් ෂියොන් G 3.5 GHz: (සම්පූර්ණ පරීක්ෂණ කේතය පතුලේ සොයාගත හැකිය)

  • GCC 4.6.3: g++ popcnt.cpp -std=c++0x -O3 -save-temps -march=native
  • උබුන්ටු 12

විවිධ ලේඛණ: 18.6195 GB / s

.L4:
    movq    (%rbx,%rax,8), %r8
    movq    8(%rbx,%rax,8), %r9
    movq    16(%rbx,%rax,8), %r10
    movq    24(%rbx,%rax,8), %r11
    addq    $4, %rax

    popcnt %r8, %r8
    add    %r8, %rdx
    popcnt %r9, %r9
    add    %r9, %rcx
    popcnt %r10, %r10
    add    %r10, %rdi
    popcnt %r11, %r11
    add    %r11, %rsi

    cmpq    $131072, %rax
    jne .L4

එකම ලේඛනය: 8.49272 GB / s

.L9:
    movq    (%rbx,%rdx,8), %r9
    movq    8(%rbx,%rdx,8), %r10
    movq    16(%rbx,%rdx,8), %r11
    movq    24(%rbx,%rdx,8), %rbp
    addq    $4, %rdx

    # This time reuse "rax" for all the popcnts.
    popcnt %r9, %rax
    add    %rax, %rcx
    popcnt %r10, %rax
    add    %rax, %rsi
    popcnt %r11, %rax
    add    %rax, %r8
    popcnt %rbp, %rax
    add    %rax, %rdi

    cmpq    $131072, %rdx
    jne .L9

කැඩුණු දාමය සහිත එකම ලේඛනය: 17.8869 GB / s

.L14:
    movq    (%rbx,%rdx,8), %r9
    movq    8(%rbx,%rdx,8), %r10
    movq    16(%rbx,%rdx,8), %r11
    movq    24(%rbx,%rdx,8), %rbp
    addq    $4, %rdx

    # Reuse "rax" for all the popcnts.
    xor    %rax, %rax    # Break the cross-iteration dependency by zeroing "rax".
    popcnt %r9, %rax
    add    %rax, %rcx
    popcnt %r10, %rax
    add    %rax, %rsi
    popcnt %r11, %rax
    add    %rax, %r8
    popcnt %rbp, %rax
    add    %rax, %rdi

    cmpq    $131072, %rdx
    jne .L14

ඉතින් සම්පාදකයාගේ වැරැද්ද කුමක්ද?

popcntඑවැනි ව්‍යාජ යැපීමක් ඇති බව GCC හෝ Visual Studio නොදන්නා බව පෙනේ . එසේ වුවද, මෙම ව්‍යාජ පරායත්තතා සාමාන්‍ය දෙයක් නොවේ. එය සම්පාදකයා දැනුවත්ව සිටීද යන්න ප්‍රශ්නයක් පමණි.

popcntහරියටම වැඩිපුරම භාවිතා කරන උපදෙස් නොවේ. එබැවින් ප්‍රධාන සම්පාදකයෙකුට මේ වගේ දෙයක් මග හැරීම පුදුමයක් නොවේ. මෙම ගැටළුව සඳහන් කරන කිසිදු ලේඛනයක් කොතැනකවත් නොමැති බව පෙනේ. ඉන්ටෙල් එය හෙළි නොකළේ නම්, යමෙකු අහම්බෙන් ඒ තුළට දිව යන තුරු පිටත කිසිවෙකු දැන නොගනී.

( යාවත්කාලීන කලේ: 4.9.2 අනුවාදය වන විට ., ගල්ෆ් මෙම ව්යාජ-පරායත්ත දැනුවත් වන අතර ප්රතිස්තිකරණය සක්රීයව ඇති විට එය වන්දි ගෙවීම සඳහා කේතය ජනනය Clang, MSVC ඇතුළු වෙනත් වෙළඳුන්, සිට මේජර් සම්පාදනය, සහ Intel® ම ක්රිකට් පවා තවමත් නොදන්නා මෙම ක්ෂුද්‍ර වාස්තු විද්‍යාත්මක දෝෂය සහ ඒ සඳහා වන්දි ලබා දෙන කේත විමෝචනය නොකරනු ඇත.)

CPU ට එවැනි ව්‍යාජ යැපීමක් ඇත්තේ ඇයි?

අපි අනුමාන කළ හැකිය: එය එකම ක්රියාත්මක ඒකකය මත ධාවනය bsf/ bsrකරන දේ නිමැවුම් පරායත්ත ඇත. ( දෘඩාංග තුළ POPCNT ක්‍රියාත්මක වන්නේ කෙසේද? ). එම උපදෙස් සඳහා, ඉන්ටෙල් = 0 සඳහා පූර්ණ සංඛ්‍යා ප්‍රති result ලය "නිර්වචනය නොකළ" (ZF = 1 සමඟ) ලෙස ලේඛනගත කරයි, නමුත් ඉන්ටෙල් දෘඩාංග ඇත්ත වශයෙන්ම පැරණි මෘදුකාංග බිඳ දැමීම වළක්වා ගැනීමට වඩා ශක්තිමත් සහතිකයක් ලබා දෙයි: ප්‍රතිදානය වෙනස් නොකළ. AMD මෙම හැසිරීම ලේඛනගත කරයි.

මෙම ක්‍රියාත්මක කිරීමේ ඒකකය සඳහා නිමැවුම් මත යැපීම යම් ආකාරයකින් අපහසුතාවයට පත්විය හැකි නමුත් අනෙක් ඒවා එසේ නොවේ.

AMD සකසනයන්ට මෙම ව්‍යාජ යැපීම ඇති බවක් නොපෙනේ.


යොමු කිරීම සඳහා සම්පූර්ණ පරීක්ෂණ කේතය පහතින්:

#include <iostream>
#include <chrono>
#include <x86intrin.h>

int main(int argc, char* argv[]) {

   using namespace std;
   uint64_t size=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;
   {
      uint64_t c0 = 0;
      uint64_t c1 = 0;
      uint64_t c2 = 0;
      uint64_t c3 = 0;
      startP = chrono::system_clock::now();
      for( unsigned k = 0; k < 10000; k++){
         for (uint64_t i=0;i<size/8;i+=4) {
            uint64_t r0 = buffer[i + 0];
            uint64_t r1 = buffer[i + 1];
            uint64_t r2 = buffer[i + 2];
            uint64_t r3 = buffer[i + 3];
            __asm__(
                "popcnt %4, %4  \n\t"
                "add %4, %0     \n\t"
                "popcnt %5, %5  \n\t"
                "add %5, %1     \n\t"
                "popcnt %6, %6  \n\t"
                "add %6, %2     \n\t"
                "popcnt %7, %7  \n\t"
                "add %7, %3     \n\t"
                : "+r" (c0), "+r" (c1), "+r" (c2), "+r" (c3)
                : "r"  (r0), "r"  (r1), "r"  (r2), "r"  (r3)
            );
         }
      }
      count = c0 + c1 + c2 + c3;
      endP = chrono::system_clock::now();
      duration=chrono::duration_cast<std::chrono::nanoseconds>(endP-startP).count();
      cout << "No Chain\t" << count << '\t' << (duration/1.0E9) << " sec \t"
            << (10000.0*size)/(duration) << " GB/s" << endl;
   }
   {
      uint64_t c0 = 0;
      uint64_t c1 = 0;
      uint64_t c2 = 0;
      uint64_t c3 = 0;
      startP = chrono::system_clock::now();
      for( unsigned k = 0; k < 10000; k++){
         for (uint64_t i=0;i<size/8;i+=4) {
            uint64_t r0 = buffer[i + 0];
            uint64_t r1 = buffer[i + 1];
            uint64_t r2 = buffer[i + 2];
            uint64_t r3 = buffer[i + 3];
            __asm__(
                "popcnt %4, %%rax   \n\t"
                "add %%rax, %0      \n\t"
                "popcnt %5, %%rax   \n\t"
                "add %%rax, %1      \n\t"
                "popcnt %6, %%rax   \n\t"
                "add %%rax, %2      \n\t"
                "popcnt %7, %%rax   \n\t"
                "add %%rax, %3      \n\t"
                : "+r" (c0), "+r" (c1), "+r" (c2), "+r" (c3)
                : "r"  (r0), "r"  (r1), "r"  (r2), "r"  (r3)
                : "rax"
            );
         }
      }
      count = c0 + c1 + c2 + c3;
      endP = chrono::system_clock::now();
      duration=chrono::duration_cast<std::chrono::nanoseconds>(endP-startP).count();
      cout << "Chain 4   \t"  << count << '\t' << (duration/1.0E9) << " sec \t"
            << (10000.0*size)/(duration) << " GB/s" << endl;
   }
   {
      uint64_t c0 = 0;
      uint64_t c1 = 0;
      uint64_t c2 = 0;
      uint64_t c3 = 0;
      startP = chrono::system_clock::now();
      for( unsigned k = 0; k < 10000; k++){
         for (uint64_t i=0;i<size/8;i+=4) {
            uint64_t r0 = buffer[i + 0];
            uint64_t r1 = buffer[i + 1];
            uint64_t r2 = buffer[i + 2];
            uint64_t r3 = buffer[i + 3];
            __asm__(
                "xor %%rax, %%rax   \n\t"   // <--- Break the chain.
                "popcnt %4, %%rax   \n\t"
                "add %%rax, %0      \n\t"
                "popcnt %5, %%rax   \n\t"
                "add %%rax, %1      \n\t"
                "popcnt %6, %%rax   \n\t"
                "add %%rax, %2      \n\t"
                "popcnt %7, %%rax   \n\t"
                "add %%rax, %3      \n\t"
                : "+r" (c0), "+r" (c1), "+r" (c2), "+r" (c3)
                : "r"  (r0), "r"  (r1), "r"  (r2), "r"  (r3)
                : "rax"
            );
         }
      }
      count = c0 + c1 + c2 + c3;
      endP = chrono::system_clock::now();
      duration=chrono::duration_cast<std::chrono::nanoseconds>(endP-startP).count();
      cout << "Broken Chain\t"  << count << '\t' << (duration/1.0E9) << " sec \t"
            << (10000.0*size)/(duration) << " GB/s" << endl;
   }

   free(charbuffer);
}

සමානවම සිත්ගන්නාසුලු මිණුම් ලකුණක් මෙහි සොයාගත හැකිය: http://pastebin.com/kbzgL8si
මෙම මිණුම් ලකුණ popcnt(ව්‍යාජ) පරායත්ත දාමයේ ඇති s ගණන වෙනස් වේ .

False Chain 0:  41959360000 0.57748 sec     18.1578 GB/s
False Chain 1:  41959360000 0.585398 sec    17.9122 GB/s
False Chain 2:  41959360000 0.645483 sec    16.2448 GB/s
False Chain 3:  41959360000 0.929718 sec    11.2784 GB/s
False Chain 4:  41959360000 1.23572 sec     8.48557 GB/s

4
ආයුබෝවන්! මෙහි අතීත අදහස් ගොඩක්; නව එකක් හැර යාමට පෙර, කරුණාකර සංරක්ෂිතය සමාලෝචනය කරන්න .
ෂොග් 9

1
@ JustinL.it මෙම විශේෂිත ගැටළුව ක්ලැන්ග් හි 7.0 වන විට නිරාකරණය කර ඇති බව පෙනේ
ඩෑන් එම්.

EtPeterCordes මම හිතන්නේ නැහැ එය ක්‍රියාත්මක කිරීමේ ඒකකය උපලේඛකයා තරම්ම කියා. පරායත්තතාවයන් නිරීක්ෂණය කරන උපලේඛකයා එයයි. එය සිදු කිරීම සඳහා, උපදෙස් "උපදෙස් පන්ති" ගණනාවකට කාණ්ඩ කර ඇති අතර, ඒ සෑම එකක්ම උපලේඛකයා විසින් එක හා සමානව සලකනු ලැබේ. මේ අනුව, 3-චක්‍රයේ “මන්දගාමී” උපදෙස් උපදෙස් උපලේඛනගත කිරීම සඳහා එකම “පන්තියට” විසි කරන ලදී.
ගුප්ත

Y ගුප්ත විද්‍යාව: ඔබ තවමත් එසේ සිතනවාද? එය පිළිගත හැකි නමුත් imul dst, src, immප්‍රතිදාන පරායත්තතාවයක් නොමැති අතර මන්දගාමී නොවේ lea. එසේ නොවේ pdep, නමුත් එය ආදාන මෙහෙයුම් 2 ක් සමඟ කේතනය කර ඇත. ව්‍යාජ නියෝජිතයාට හේතු වන්නේ එය ක්‍රියාත්මක කිරීමේ ඒකකය නොවන බව එකඟ විය ; එය RAT දක්වා වන අතර වාස්තු විද්‍යාත්මක ලේඛනය භෞතික ලේඛණ වෙත නැවත නම් කරන බැවින් එය නිකුත් කිරීමේ / නැවත නම් කිරීමේ අදියර වේ. අනුමාන වශයෙන් එයට uop-code -> පරායත්ත රටාව සහ වරාය තේරීම් වගුවක් අවශ්‍ය වන අතර එකම ක්‍රියාත්මක කිරීමේ ඒකකය සඳහා සියලු uops එකට කාණ්ඩ කිරීම එම වගුව සරල කරයි. ඒක තමයි මම වඩාත් විස්තරාත්මකව අදහස් කළේ.
පීටර් කෝර්ඩ්ස්

ඔබේ පිළිතුරට මා එය සංස්කරණය කිරීමට අවශ්‍ය නම් හෝ උපලේඛකයා ගැන ඔබ මුලින් කී දේ වැනි දෙයක් පැවසීමට ඔබට අවශ්‍ය නම් මට දන්වන්න. SKL විසින් lzcnt / tzcnt සඳහා ව්‍යාජ ඩෙප් එක අතහැර දැමූ නමුත් පොප්කන්ට් නොවන නිසා අපට යමක් පැවසිය යුතුය, නමුත් IDK යනු කුමක්ද. එය නැවත නම් කිරීම / RAT හා සම්බන්ධ විය හැකි තවත් ලකුණක් නම්, SKL විසින් සුචිගත කරන ලද ලිපින මාදිලිය lzcnt / tzcnt සඳහා මතක ප්‍රභවයක් ලෙස ප්‍රකාශයට පත් කරන නමුත් පොප්කන්ට් නොවේ. නිසැකවම නැවත නම් කිරීමේ ඒකකය පසුපස අන්තය නිරූපණය කළ හැකි යූප්ස් නිර්මාණය කළ යුතුය.
පීටර් කෝර්ඩ්ස්

50

මම අත්හදා බැලීමට සමාන C වැඩසටහනක් කේතනය කළ අතර මට මෙම අමුතු හැසිරීම සනාථ කළ හැකිය. තව ද, gcc(බොහෝ විට විය යුතු 64-bit පූර්ණ සංඛ්යාමය විශ්වාස size_tභාවිතය, හොඳ විය කෙසේ හෝ ...) uint_fast32_t64-bit uint භාවිතා කිරීමට හේතු gcc.

මම එකලස් කිරීම සමඟ මඳක් විහිළු කළෙමි:
හුදෙක් 32-බිට් අනුවාදය ගන්න, වැඩසටහනේ අභ්‍යන්තර පොප්කවුන්ට්-ලූප් හි ඇති බිට් 32 උපදෙස් / රෙජිස්ටර් 64-බිට් 64 අනුවාදය සමඟ ආදේශ කරන්න. නිරීක්ෂණය: කේතය 32-බිට් අනුවාදය තරම් වේගවත් ය!

විචල්‍යයේ ප්‍රමාණය සැබවින්ම බිට් 64 නොවන බැවින් මෙය පැහැදිලිවම හැක් එකක් වන අතර, වැඩසටහනේ අනෙක් කොටස් තවමත් 32-බිට් අනුවාදය භාවිතා කරයි, නමුත් අභ්‍යන්තර පොප්කවුන්ට්-ලූප් කාර්ය සාධනය කෙරෙහි ආධිපත්‍යය දරන තාක් කල් මෙය හොඳ ආරම්භයකි .

මම පසුව වැඩසටහනේ 32-බිට් අනුවාදයෙන් අභ්‍යන්තර ලූප් කේතය පිටපත් කර එය බිට් 64 ක් ලෙස හැක් කර, රෙජිස්ටර් සමඟ සවි කර 64-බිට් අනුවාදයේ අභ්‍යන්තර ලූපය වෙනුවට ආදේශකයක් බවට පත් කළෙමි. මෙම කේතය බිට් 32 අනුවාදය තරම් වේගයෙන් ධාවනය වේ.

මගේ නිගමනය නම් මෙය සම්පාදකයා විසින් නරක උපදෙස් උපලේඛනගත කිරීම මිස බිට් 32 උපදෙස්වල සැබෑ වේගය / ප්‍රමාද වාසිය නොවේ.

(Caveat: මම එකලස් කිරීම හැක් කළා, නොදැන යමක් කඩා දැමිය හැකියි. මම හිතන්නේ නැහැ.)


1
“එපමණක්ද නොව, uint_fast32_t භාවිතා කිරීමෙන් gcc 64-bit uint භාවිතා කිරීමට හේතු වන බැවින් 64-බිටු නිඛිලය […] වඩා හොඳ යැයි gcc විශ්වාස කරයි.” අවාසනාවකට මෙන්, මගේ කනගාටුවට කරුණක් නම්, මෙම වර්ග පිටුපස මැජික් හා ගැඹුරු කේත ස්වයං විමර්ශනයක් නොමැත. මුළු වේදිකාවේම ඇති සෑම ස්ථානයකටම සහ සෑම වැඩසටහනකටම තනි ටයිප් ඩීෆ් ලෙස හැර වෙනත් ක්‍රමයක් ඔවුන් විසින් ලබා දී ඇති බව මම තවම දැක නැත. නිශ්චිත වර්ග තෝරා ගැනීම පිටුපස යම්කිසි සිතුවිල්ලක් තබා ඇති නමුත්, ඒ සෑම එකක් සඳහාම එක් අර්ථ දැක්වීමක් එහි ඇති සෑම යෙදුමකටම නොගැලපේ. වැඩිදුර කියවීම: stackoverflow.com/q/4116297 .
කෙනෝ

2
En කෙනෝ එයට හේතුව sizeof(uint_fast32_t)නිර්වචනය කළ යුතු බැවිනි. ඔබ එය නොකිරීමට ඉඩ දෙන්නේ නම්, ඔබට එම උපක්‍රමය කළ හැකිය, නමුත් එය කළ හැක්කේ සම්පාදක දිගුවකින් පමණි.
wizzwizz4

26

මෙය පිළිතුරක් නොවේ, නමුත් මම ප්‍රති .ල අදහස් දැක්වුවහොත් කියවීම දුෂ්කර ය.

මට මෙම ප්‍රති results ල ලැබෙන්නේ මැක් ප්‍රෝ ( වෙස්ට්මියර් 6-කෝර් සීන් 3.33 GHz) සමඟ ය. මම එය සම්පාදනය කළේ clang -O3 -msse4 -lstdc++ a.cpp -o a(-O2 එකම ප්‍රති result ලය ලබා ගන්න).

සමඟ ගැට ගසන්න uint64_t size=atol(argv[1])<<20;

unsigned    41950110000 0.811198 sec    12.9263 GB/s
uint64_t    41950110000 0.622884 sec    16.8342 GB/s

සමඟ ගැට ගසන්න uint64_t size=1<<20;

unsigned    41950110000 0.623406 sec    16.8201 GB/s
uint64_t    41950110000 0.623685 sec    16.8126 GB/s

මම ද උත්සාහ කළේ:

  1. පරීක්ෂණ අනුපිළිවෙල ආපසු හරවන්න, ප්‍රති result ලය එක හා සමාන බැවින් එය හැඹිලි සාධකය බැහැර කරයි.
  2. තිබිය forපසුපසට ප්රකාශය: for (uint64_t i=size/8;i>0;i-=4). මෙය එකම ප්‍රති result ලයක් ලබා දෙන අතර සෑම ක්‍රියාවක්ම (අපේක්ෂිත පරිදි) ප්‍රමාණයෙන් 8 න් බෙදීමට නොහැකි තරම් සම්පාදනය දක්ෂ බව ඔප්පු කරයි.

මෙන්න මගේ වල් අනුමානය:

වේග සාධකය කොටස් තුනකින් පැමිණේ:

  • කේත හැඹිලිය: uint64_tඅනුවාදයේ විශාල කේත ප්‍රමාණයක් ඇත, නමුත් මෙය මගේ Xeon CPU කෙරෙහි බලපෑමක් ඇති නොකරයි. මෙය 64-බිට් අනුවාදය මන්දගාමී කරයි.

  • භාවිතා කරන උපදෙස්. ලූප් ගණන පමණක් නොව, බෆරයට අනුවාද දෙකේ 32-බිට් සහ 64-බිට් දර්ශකයක් සමඟ ප්‍රවේශ වේ. 64-බිට් ඕෆ්සෙට් එකක් සහිත දර්ශකයකට ප්‍රවේශ වීම සඳහා කැපවූ 64-බිට් ලේඛනයක් සහ ලිපිනයක් ඉල්ලා සිටින අතර ඔබට බිට් 32 ඕෆ්සෙට් සඳහා ක්ෂණිකව භාවිතා කළ හැකිය. මෙය බිට් 32 අනුවාදය වේගවත් කරයි.

  • උපදෙස් විමෝචනය වන්නේ බිට් 64 සම්පාදනය මත පමණි (එනම් පෙරවදන). මෙය 64-බිට් වේගවත් කරයි.

නිරීක්ෂණය කරන ලද පරස්පර විරෝධී ප්‍රති .ල සමඟ සාධක තුනම ගැලපේ.


4
සිත්ගන්නාසුලු, ඔබට සම්පාදක අනුවාදය සහ සම්පාදක ධජ එක් කළ හැකිද? හොඳම දේ නම් ඔබේ පරිගණකයේ ප්‍රති results ල හැරී යාමයි, එනම් u64 භාවිතා කිරීම වේගවත් වීමයි. මේ වන තෙක්, මගේ ලූප් විචල්‍යය කුමන වර්ගයක් දැයි මම කිසි විටෙකත් නොසිතුවෙමි, නමුත් ඊළඟ වතාවේ දෙවරක් සිතා බැලිය යුතු බව පෙනේ :).
gexicide

2
@gexicide: මම 16.8201 සිට 16.8126 දක්වා පැනීම “වේගවත්” යැයි නොකියමි.
user541686

2
@Mehrdad: මම අදහස් මෙම පැනීම අතර එක් 12.9හා 16.8, එම නිසා unsignedවේගවත් මෙහි ඇත. මගේ මිණුම් unsigneduint64_t
ලකුණට

xicxicide බෆරය ඇමතීමේ වෙනස ඔබ දැක තිබේද?
වෙස්මුහුණු නොවන බාධා කිරීම්

Al කැල්වින්: නැහැ, ඔබ අදහස් කළේ කුමක්ද?
gexicide

10

මට බලයලත් පිළිතුරක් දිය නොහැක, නමුත් සිදුවිය හැකි හේතුවක් පිළිබඳ දළ විශ්ලේෂණයක් සපයන්න. මෙම සඳහන ඉතා පැහැදිලිව පෙන්නුම් කරන්නේ ඔබේ පුඩුවේ ශරීරයේ ඇති උපදෙස් සඳහා ප්‍රමාදය සහ ප්‍රතිදානය අතර 3: 1 අනුපාතයක් ඇති බවයි. බහුවිධ යැවීම්වල බලපෑම ද එය පෙන්වයි. නූතන x86 ප්‍රොසෙසරවල පූර්ණ සංඛ්‍යා ඒකක තුනක් (ලබා දීමට හෝ ගැනීමට) ඇති බැවින්, සාමාන්‍යයෙන් එක් චක්‍රයකට උපදෙස් තුනක් යැවිය හැකිය.

එබැවින් උපරිම නල මාර්ගය සහ බහු යැවීමේ කාර්ය සාධනය සහ මෙම යාන්ත්‍රණවල අසාර්ථකත්වය අතර, අපට කාර්ය සාධනයේ හය සාධක ඇත. X86 උපදෙස් කට්ටලයේ සංකීර්ණතාව නිසා විචක්ෂණශීලී බිඳවැටීම් සිදුවීම පහසු බව හොඳින් දන්නා කරුණකි. ඉහත ලේඛනයට හොඳ උදාහරණයක් තිබේ:

64-බිට් නිවැරදි මාරුව සඳහා පෙන්ටියම් 4 කාර්ය සාධනය ඇත්තෙන්ම දුර්වලයි. 64-බිට් වම් මාරුව මෙන්ම 32-බිට් මාරුවීම් සියල්ලම පිළිගත හැකි කාර්ය සාධනයක් ඇත. ALU හි ඉහළ බිටු 32 සිට පහළ 32 දක්වා දත්ත මාර්ගය හොඳින් සැලසුම් කර නොමැති බව පෙනේ.

මම පුද්ගලිකව අමුතු අවස්ථාවකට දිව ගිය අතර, උණුසුම් හරයක් සතර-හර චිපයක නිශ්චිත හරයක් මත සැලකිය යුතු ලෙස මන්දගාමී විය (මට මතක නම් AMD). සිතියම අඩු කිරීමේ ගණනය කිරීම් වලදී එම හරය ක්‍රියා විරහිත කිරීමෙන් අපට වඩා හොඳ කාර්ය සාධනයක් ලැබුණි.

මෙන්න මගේ අනුමානය පූර්ණ සංඛ්‍යා ඒකක සඳහා popcntවන විවාදයකි:, ලූප් කවුන්ටරය සහ ලිපින ගණනය කිරීම් සියල්ලම බිට් 32 පළල කවුන්ටරය සමඟ සම්පූර්ණ වේගයෙන් ධාවනය කළ හැකි නමුත් 64-බිට් කවුන්ටරය මතභේදයට හා නල මාර්ග කුටි වලට හේතු වේ. මුළු චක්‍ර 12 ක් පමණක් ඇති බැවින්, බහුවිධ පිටත් කිරීම් සහිත චක්‍ර 4 ක්, ලූප් බොඩි ක්‍රියාවට නැංවීම සඳහා, එක් කුටියක් 2 ක සාධකයකින් ධාවන වේලාවට සාධාරණ ලෙස බලපායි.

ස්ථිතික විචල්‍යයක් භාවිතා කිරීමෙන් ඇතිවන වෙනස, සුළු අනුපිළිවෙලක් නැවත සකස් කිරීමට හේතු වන බව මම අනුමාන කරමි, 32-බිට් කේතය මතභේදයට තුඩු දෙන ස්ථානයක ඇති තවත් ඉඟියක් වේ.

මම මේ දැඩි විශ්ලේෂණය නොවන බව, නමුත් එය වේ ඉතා පැසසිය යුතු කරුණු පැහැදිලි කළා.


2
අවාසනාවට, එතැන් සිට (Core 2?) ගුණ කිරීම / බෙදීම හැරුණු විට 32-bit සහ 64-bit පූර්ණ සංඛ්‍යා මෙහෙයුම් අතර පාහේ කාර්ය සාධන වෙනස්කම් නොමැත - ඒවා මෙම කේතයේ නොමැත.
ගුප්ත

Ene ජීන්: සියලුම අනුවාදවල ප්‍රමාණය ලේඛනයක ගබඩා කර ඇති අතර එය කිසි විටෙකත් ලූපයේ සිට කියවන්නේ නැති බව සලකන්න . මේ අනුව, ලිපින ගණනය කිරීම මිශ්‍ර විය නොහැක, අවම වශයෙන් ලූපය තුළ නොවේ.
ඔක්සිකාරක

Ene ජීන්: සිත්ගන්නාසුලු පැහැදිලි කිරීමක්! නමුත් එය ප්‍රධාන ඩබ්ලිව්ටීඑෆ් කරුණු පැහැදිලි නොකරයි: නල මාර්ග කුටි නිසා 64bit 32bit ට වඩා මන්දගාමී වීම එක දෙයකි. නමුත් මෙය එසේ නම්, 64bit අනුවාදය 32bit එකට වඩා විශ්වාසදායක ලෙස මන්දගාමී විය යුතු නොවේද? ඒ වෙනුවට, සම්පාදක-කාල නියත බෆර් ප්‍රමාණය භාවිතා කරන විට විවිධ සම්පාදකයින් තිදෙනෙක් 32bit අනුවාදය සඳහා මන්දගාමී කේත විමෝචනය කරති; බෆරයේ ප්‍රමාණය ස්ථිතික බවට වෙනස් කිරීමෙන් නැවත දේවල් සම්පූර්ණයෙන්ම වෙනස් වේ. මගේ සගයන්ගේ යන්ත්‍රයේ (සහ කැල්වින්ගේ පිළිතුරෙහි) 64bit අනුවාදය සැලකිය යුතු වේගයකින් යුත් සිද්ධියක් පවා තිබුණි! එය
කිසිසේත්ම

Y ගුප්ත විද්‍යාව එය මගේ අදහසයි. IU, බස් වේලාව යනාදිය සඳහා ශුන්‍ය විවාදයක් ඇති විට උපරිම කාර්යසාධන වෙනසක් නොමැත. අවධානය යොමු කිරීම සෑම දෙයක්ම වෙනස් කරයි. ඉන්ටෙල් කෝර් සාහිත්‍යයෙන් උදාහරණයක් මෙහි දැක්වේ: “සැලසුමට ඇතුළත් කර ඇති එක් නව තාක්‍ෂණයක් වන්නේ මැක්‍රෝ-ඕප්ස් ෆියුෂන් ය, එය x86 උපදෙස් දෙකක් එක් ක්ෂුද්‍ර මෙහෙයුමකට ඒකාබද්ධ කරයි. අවාසනාවකට මෙන්, මෙම තාක්ෂණය බිට් 64 මාදිලියේ ක්‍රියා නොකරයි. එබැවින් අපට ක්‍රියාත්මක කිරීමේ වේගයෙහි 2: 1 අනුපාතයක් ඇත.
ජීන්

@gexicide ඔබ කියන දේ මට පෙනේ, නමුත් ඔබ මා අදහස් කළ ප්‍රමාණයට වඩා අනුමාන කරයි. මම කියන්නේ වේගයෙන් ධාවනය වන කේතය නල මාර්ගය තබා පෝලිම් පූර්ණ ලෙස යැවීමයි. මෙම තත්වය බිඳෙන සුළුය. සමස්ත දත්ත ප්‍රවාහයට බිටු 32 ක් එකතු කිරීම සහ උපදෙස් නැවත සකස් කිරීම වැනි සුළු වෙනස්කම් එය බිඳ දැමීමට ප්‍රමාණවත් වේ. කෙටියෙන් කිවහොත්, ඉදිරියට යා හැකි එකම මාර්ගය විග්‍රහ කිරීම සහ පරීක්ෂා කිරීම බව OP ප්‍රකාශය නිවැරදි ය.
ජීන්

10

මම මෙය විෂුවල් ස්ටුඩියෝ 2013 එක්ස්ප්‍රස් සමඟ අත්හදා බැලුවෙමි , දර්ශකයක් වෙනුවට දර්ශකයක් භාවිතා කරමින් ක්‍රියාවලිය ටිකක් වේගවත් කළෙමි . මා සිතන්නේ මෙය ඇමතීම ඕෆ්සෙට් + රෙජිස්ටර් වෙනුවට ඕෆ්සෙට් + රෙජිස්ටර් + (රෙජිස්ටර් << 3) නිසාය. සී ++ කේතය.

   uint64_t* bfrend = buffer+(size/8);
   uint64_t* bfrptr;

// ...

   {
      startP = chrono::system_clock::now();
      count = 0;
      for (unsigned k = 0; k < 10000; k++){
         // Tight unrolled loop with uint64_t
         for (bfrptr = buffer; bfrptr < bfrend;){
            count += __popcnt64(*bfrptr++);
            count += __popcnt64(*bfrptr++);
            count += __popcnt64(*bfrptr++);
            count += __popcnt64(*bfrptr++);
         }
      }
      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;
   }

එකලස් කිරීමේ කේතය: r10 = bfrptr, r15 = bfrend, rsi = count, rdi = buffer, r13 = k:

$LL5@main:
        mov     r10, rdi
        cmp     rdi, r15
        jae     SHORT $LN4@main
        npad    4
$LL2@main:
        mov     rax, QWORD PTR [r10+24]
        mov     rcx, QWORD PTR [r10+16]
        mov     r8, QWORD PTR [r10+8]
        mov     r9, QWORD PTR [r10]
        popcnt  rdx, rax
        popcnt  rax, rcx
        add     rdx, rax
        popcnt  rax, r8
        add     r10, 32
        add     rdx, rax
        popcnt  rax, r9
        add     rsi, rax
        add     rsi, rdx
        cmp     r10, r15
        jb      SHORT $LL2@main
$LN4@main:
        dec     r13
        jne     SHORT $LL5@main

9

ඔබ -funroll-loops -fprefetch-loop-arraysGCC වෙත යාමට උත්සාහ කර තිබේද?

මෙම අතිරේක ප්‍රශස්තිකරණයන් සමඟ මට පහත ප්‍රති results ල ලැබේ:

[1829] /tmp/so_25078285 $ cat /proc/cpuinfo |grep CPU|head -n1
model name      : Intel(R) Core(TM) i3-3225 CPU @ 3.30GHz
[1829] /tmp/so_25078285 $ g++ --version|head -n1
g++ (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3

[1829] /tmp/so_25078285 $ g++ -O3 -march=native -std=c++11 test.cpp -o test_o3
[1829] /tmp/so_25078285 $ g++ -O3 -march=native -funroll-loops -fprefetch-loop-arrays -std=c++11     test.cpp -o test_o3_unroll_loops__and__prefetch_loop_arrays

[1829] /tmp/so_25078285 $ ./test_o3 1
unsigned        41959360000     0.595 sec       17.6231 GB/s
uint64_t        41959360000     0.898626 sec    11.6687 GB/s

[1829] /tmp/so_25078285 $ ./test_o3_unroll_loops__and__prefetch_loop_arrays 1
unsigned        41959360000     0.618222 sec    16.9612 GB/s
uint64_t        41959360000     0.407304 sec    25.7443 GB/s

3
නමුත් තවමත්, ඔබේ ප්‍රති results ල මුළුමනින්ම අමුතුයි (පළමුව අත්සන් නොකල වේගවත්, පසුව uint64_t වේගවත්) ලියාපදිංචි නොකිරීම ව්‍යාජ පරායත්තතාවයේ ප්‍රධාන ගැටළුව විසඳන්නේ නැති නිසා.
ඔක්සිකාරක

7

අඩු කිරීමේ පියවර ලූපයෙන් පිටත ගෙනයාමට ඔබ උත්සාහ කර තිබේද? දැන් ඔබට ඇත්ත වශයෙන්ම අවශ්‍ය නොවන දත්ත පරායත්තතාවයක් ඇත.

උත්සාහ කරන්න:

  uint64_t subset_counts[4] = {};
  for( unsigned k = 0; k < 10000; k++){
     // Tight unrolled loop with unsigned
     unsigned i=0;
     while (i < size/8) {
        subset_counts[0] += _mm_popcnt_u64(buffer[i]);
        subset_counts[1] += _mm_popcnt_u64(buffer[i+1]);
        subset_counts[2] += _mm_popcnt_u64(buffer[i+2]);
        subset_counts[3] += _mm_popcnt_u64(buffer[i+3]);
        i += 4;
     }
  }
  count = subset_counts[0] + subset_counts[1] + subset_counts[2] + subset_counts[3];

ඔබට ද අමුතු අන්වර්ථයක් සිදුවෙමින් පවතී, දැඩි අන්වර්ථ නීතිවලට අනුකූල බව මට විශ්වාස නැත.


2
ප්‍රශ්නය කියවීමෙන් පසු මා කළ පළමු දෙය එයයි. පරායත්ත දාමය බිඳ දමන්න. පෙනෙන පරිදි කාර්ය සාධන වෙනස වෙනස් නොවේ (මගේ පරිගණකයේ අවම වශයෙන් - ජීසීසී 4.7.3 සමඟ ඉන්ටෙල් හැස්වෙල්).
නිල්ස් පයිපෙන්බ්‍රින්ක්

1
En බෙන් වොයිට්: එය දැඩි අන්වර්ථකරණයට අනුකූල වේ. void*හා char*ඔවුන් esentially "මතකය සමහර කුට්ටියේ බවට සූචක" ලෙස සලකනු ලැබේ ලෙස, aliased විය හැක වර්ග දෙකක් වේ! දත්ත පරායත්ත ඉවත් කිරීම පිළිබඳ ඔබේ අදහස ප්‍රශස්තිකරණය සඳහා හොඳයි, නමුත් එය ප්‍රශ්නයට පිළිතුරු සපයන්නේ නැත. IlsNilsPipenbrinck පවසන පරිදි, එය කිසිවක් වෙනස් කරන බවක් නොපෙනේ.
gexicide

@gexicide: දැඩි අන්වර්ථකරණ නියමය සමමිතික නොවේ. ඔබට char*ප්‍රවේශ වීමට භාවිතා කළ හැකිය T[]. A වෙත ප්‍රවේශ වීම සඳහා ඔබට ආරක්ෂිතව a භාවිතා කළ නොහැකිT* අතර char[], ඔබේ කේතය දෙවැන්න කරන බව පෙනේ.
බෙන් වොයිගට්

En බෙන් වොයිග්ට්: එවිට ඔබට කිසි විටෙකත් කිසිවක් සුරැකිය නොහැක malloc, මන්ද malloc ආපසු පැමිණෙන void*අතර ඔබ එය අර්ථ නිරූපණය කරයි T[]. හා මම හොඳට බව මම void*සහ char*දැඩි අනුවර්ත නාමකරණයට ගැන එම semantics විය. කෙසේ වෙතත්, මම හිතන්නේ මෙය මෙහි තරමක් අශෝභන ය :)
gexicide

1
පුද්ගලිකව මම සිතන්නේ නිවැරදි මාර්ගයuint64_t* buffer = new uint64_t[size/8]; /* type is clearly uint64_t[] */ char* charbuffer=reinterpret_cast<char*>(buffer); /* aliasing a uint64_t[] with char* is safe */
බෙන් වොයිග්ට්

6

ටීඑල්; ඩීආර්: __builtinඒ වෙනුවට සහජ බුද්ධිය භාවිතා කරන්න ; ඔවුන් උදව් කිරීමට සිදුවිය හැකිය.

එකම එකලස් කිරීමේ උපදෙස් gccභාවිතා කරමින් 4.8.4 (සහ gcc.godbolt.org හි 4.7.3) පවා ප්‍රශස්ත කේතයක් ජනනය කිරීමට මට හැකි විය __builtin_popcountll, නමුත් වාසනාවන්ත වන අතර අනපේක්ෂිත ලෙස කේතයක් සෑදීමට සිදුවිය. ව්‍යාජ පරායත්ත දෝෂය නිසා දිගු ලූප රැගෙන යන යැපීම.

මගේ මිණුම් සලකුණු කේතය ගැන මට 100% විශ්වාස නැත, නමුත් objdumpප්‍රතිදානය මගේ අදහස් බෙදා ගන්නා බව පෙනේ. කිසිදු උපදෙස් නොමැතිව සම්පාදකයා මා සඳහා ලූපයක් ලිවීමට මම වෙනත් උපක්‍රම ( ++iඑදිරිව i++) භාවිතා කරමි movl(අමුතු හැසිරීම, මම පැවසිය යුතුය).

ප්රතිපල:

Count: 20318230000  Elapsed: 0.411156 seconds   Speed: 25.503118 GB/s

මිණුම් සලකුණු කේතය:

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

uint64_t builtin_popcnt(const uint64_t* buf, size_t len){
  uint64_t cnt = 0;
  for(size_t i = 0; i < len; ++i){
    cnt += __builtin_popcountll(buf[i]);
  }
  return cnt;
}

int main(int argc, char** argv){
  if(argc != 2){
    printf("Usage: %s <buffer size in MB>\n", argv[0]);
    return -1;
  }
  uint64_t size = atol(argv[1]) << 20;
  uint64_t* buffer = (uint64_t*)malloc((size/8)*sizeof(*buffer));

  // Spoil copy-on-write memory allocation on *nix
  for (size_t i = 0; i < (size / 8); i++) {
    buffer[i] = random();
  }
  uint64_t count = 0;
  clock_t tic = clock();
  for(size_t i = 0; i < 10000; ++i){
    count += builtin_popcnt(buffer, size/8);
  }
  clock_t toc = clock();
  printf("Count: %lu\tElapsed: %f seconds\tSpeed: %f GB/s\n", count, (double)(toc - tic) / CLOCKS_PER_SEC, ((10000.0*size)/(((double)(toc - tic)*1e+9) / CLOCKS_PER_SEC)));
  return 0;
}

විකල්ප සම්පාදනය කරන්න:

gcc --std=gnu99 -mpopcnt -O3 -funroll-loops -march=native bench.c -o bench

GCC අනුවාදය:

gcc (Ubuntu 4.8.4-2ubuntu1~14.04.1) 4.8.4

ලිනක්ස් කර්නල් අනුවාදය:

3.19.0-58-generic

CPU තොරතුරු:

processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 70
model name  : Intel(R) Core(TM) i7-4870HQ CPU @ 2.50 GHz
stepping    : 1
microcode   : 0xf
cpu MHz     : 2494.226
cache size  : 6144 KB
physical id : 0
siblings    : 1
core id     : 0
cpu cores   : 1
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 13
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx rdtscp lm constant_tsc nopl xtopology nonstop_tsc eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm arat pln pts dtherm fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 invpcid xsaveopt
bugs        :
bogomips    : 4988.45
clflush size    : 64
cache_alignment : 64
address sizes   : 36 bits physical, 48 bits virtual
power management:

3
ව්‍යාජ ඩෙප් -funroll-loopsවිසින් නිර්මාණය කරන ලද ලූපයක් සහිත යැපුම් දාමයකට බාධා නොවන කේත සෑදීම වාසනාවකි popcnt. ව්‍යාජ යැපීම ගැන නොදන්නා පැරණි සම්පාදක අනුවාදයක් භාවිතා කිරීම අවදානමකි. එසේ නොමැතිව -funroll-loops, gcc 4.8.5 හි ලූපය ප්‍රතිදානය වෙනුවට පොප්කන්ට් ප්‍රමාදයට බාධාවක් වනු ඇතrdx . Gcc 4.9.3 විසින් සම්පාදනය කරන ලද එකම කේතය xor edx,edxපරායත්තතා දාමය බිඳ දැමීමට එකතු කරයි .
පීටර් කෝර්ඩ්ස්

3
පැරණි සම්පාදකයින් සමඟ, ඔබේ කේතය OP අත්විඳින ලද එකම ක්‍රියාකාරීත්ව විචල්‍යතාවයට තවමත් ගොදුරු විය හැකිය: පෙනෙන ආකාරයට සුළු සුළු වෙනස්කම් නිසා gcc යමක් මන්දගාමී විය හැකිය. පැරණි සම්පාදකයෙකු මත එක් අවස්ථාවක වැඩ කිරීමට යමක් සොයා ගැනීම ප්‍රශ්නය නොවේ .
පීටර් කෝර්ඩ්ස්

2
වාර්තාව සඳහා x86intrin.hගේ _mm_popcnt_*ගල්ෆ් මත කටයුතු බලහත්කාරයෙන් පමණ දවටන inlined ඇත__builtin_popcount* ; පේළිගත කිරීම එකක් අනෙකට හරියටම සමාන කළ යුතුය. ඔවුන් අතර මාරුවීමෙන් සිදුවිය හැකි වෙනසක් ඔබ දකිනු ඇතැයි මට සැකයි.
ෂැඩෝ රේන්ජර්

-2

පළමුවෙන්ම, උපරිම කාර්යසාධනය තක්සේරු කිරීමට උත්සාහ කරන්න - https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf පරීක්ෂා කරන්න , විශේෂයෙන්, උපග්‍රන්ථය සී.

ඔබේ නඩුවේදී, එය C-10 වගුවේ දැක්වෙන්නේ POPCNT උපදෙස් වලට ප්‍රමාද = ඔරලෝසු 3 ක් සහ ප්‍රතිදාන = 1 ඔරලෝසුවක් ඇති බවයි. ප්‍රතිදානය මඟින් ඔරලෝසු වල ඔබේ උපරිම අනුපාතය පෙන්වයි (ඔබේ උපරිම කලාප පළල අංකය ලබා ගැනීම සඳහා මූලික සංඛ්‍යාතයෙන් ගුණ කර පොප්කන්ට් 64 නම් බයිට් 8 ක්).

දැන් සම්පාදකයා කළේ කුමක්දැයි පරීක්ෂා කර ලූපයේ ඇති අනෙකුත් සියලුම උපදෙස්වල ප්‍රති put ල සාරාංශ කරන්න. මෙය ජනනය කළ කේතය සඳහා හොඳම ඇස්තමේන්තුව ලබා දෙනු ඇත.

අන්තිමේදී, ලූපයේ ඇති උපදෙස් අතර දත්ත පරායත්තතාවයන් දෙස බලන්න, ඒවා ප්‍රතිදානය වෙනුවට ප්‍රමාද විශාල ප්‍රමාදයකට බල කරනු ඇත - එබැවින් දත්ත ප්‍රවාහ දාමයන්හි තනි පුනරාවර්තනයේ උපදෙස් බෙදන්න සහ ඒවා හරහා ප්‍රමාදය ගණනය කර ඒවායින් උපරිම ලෙස ලබා ගන්න. දත්ත ප්‍රවාහ පරායත්තතාවයන් සැලකිල්ලට ගනිමින් එය දළ ඇස්තමේන්තුවක් ලබා දෙනු ඇත.

කෙසේ වෙතත්, ඔබේ නඩුවේදී, කේතය නිවැරදි ආකාරයෙන් ලිවීමෙන් මෙම සියලු සංකීර්ණතා ඉවත් වනු ඇත. එකම ගණන් විචල්‍යයට සමුච්චය කරනවා වෙනුවට, විවිධ ඒවාට එකතු කරන්න (count0, count1, ... count8 වැනි) ඒවා අවසානයේ එකතු කරන්න. නැතහොත් ගණන් මාලාවක් [8] නිර්මාණය කර එහි මූලද්‍රව්‍යවලට එකතු වන්න - සමහර විට එය දෛශිකකරණය වී ඔබට වඩා හොඳ ප්‍රති put ල ලැබෙනු ඇත.

PS සහ කිසි විටෙකත් තත්පරයකට මිණුම් ලකුණක් ධාවනය නොකරන්න, පළමුව හරය උණුසුම් කර අවම වශයෙන් තත්පර 10 ක් හෝ තත්පර 100 ක් සඳහා ලූපයක් ධාවනය කරන්න. එසේ නොමැතිනම්, ඔබ දෘඩාංගවල බල කළමනාකරණ ස්ථිරාංග සහ DVFS ක්‍රියාත්මක කිරීම පරීක්ෂා කරනු ඇත :)

පීපීඑස් මිණුම් ලකුණ ඇත්ත වශයෙන්ම කොපමණ කාලයක් ක්‍රියාත්මක විය යුතුද යන්න පිළිබඳ නිමක් නැති විවාද මට ඇසිණි. බොහෝ බුද්ධිමත් අය තත්පර 10 ක් 11 හෝ 12 නොවන්නේ මන්දැයි පවා අසති. මෙය න්‍යායික විහිළුවක් බව මම පිළිගත යුතුය. ප්‍රායෝගිකව, ඔබ ගොස් වරකට සිය වතාවක් මිණුම් ලකුණ ධාවනය කර අපගමනය වාර්තා කරන්න. ඒ අයි.එස් විහිළුවක්. නව කාර්ය සාධන වාර්තා ග්‍රහණය කර ගැනීම සඳහා බොහෝ අය ප්‍රභවය වෙනස් කර බංකුව ධාවනය කරයි. නිවැරදි දේ නිවැරදිව කරන්න.

තවමත් ඒත්තු ගැන්වී නැද්ද? Assp1r1n3 ( https://stackoverflow.com/a/37026212/9706746) මගින් මිණුම් ලකුණෙහි සී අනුවාදය භාවිතා කරන්න. ) කර නැවත උත්සාහ කරන්න ලූප් 10000 වෙනුවට 100 උත්සාහ කරන්න.

මගේ 7960X දර්ශන, RETRY = 100 සමඟ:

ගණන්: 203182300 කල් ඉකුත් වූ: තත්පර 0.008385 වේගය: 12.505379 GB / s

ගණන්: 203182300 කල් ඉකුත් වූ: තත්පර 0.011063 වේගය: 9.478225 GB / s

ගණන්: 203182300 කල් ඉකුත් වූ: තත්පර 0.011188 වේගය: 9.372327 GB / s

ගණන්: 203182300 කල් ඉකුත් වූ: තත්පර 0.010393 වේගය: 10.089252 GB / s

ගණන්: 203182300 කල් ඉකුත් වූ: තත්පර 0.009076 වේගය: 11.553283 GB / s

RETRY = 10000 සමඟ:

ගණන්: 20318230000 කල් ඉකුත් වූ: තත්පර 0.661791 වේගය: 15.844519 GB / s

ගණන්: 20318230000 කල් ඉකුත් වූ: තත්පර 0.665422 වේගය: 15.758060 GB / s

ගණන්: 20318230000 කල් ඉකුත් වූ: තත්පර 0.660983 වේගය: 15.863888 GB / s

ගණන්: 20318230000 කල් ඉකුත් වූ: තත්පර 0.665337 වේගය: 15.760073 GB / s

ගණන්: 20318230000 කල් ඉකුත් වූ: තත්පර 0.662138 වේගය: 15.836215 GB / s

පීපීපීඑස් අවසාන වශයෙන්, "පිළිගත් පිළිතුර" සහ වෙනත් අබිරහස් මත ;-)

Assp1r1n3 හි පිළිතුර භාවිතා කරමු - ඔහුට 2.5Ghz හරයක් ඇත. POPCNT ට ඔරලෝසු 1 ක් ඇත, ඔහුගේ කේතය 64-bit popcnt භාවිතා කරයි. එබැවින් ගණිතය 2.5Ghz * 1 ඔරලෝසුව * 8 බයිට් = 20 GB / s වේ. ඔහු 25Gb / s දකින්නේ, සමහර විට ටර්බෝ 3Ghz පමණ වැඩි වීම නිසා විය හැකිය.

මේ අනුව ark.intel.com වෙත ගොස් i7-4870HQ: https://ark.intel.com/products/83504/Intel-Core-i7-4870HQ-Processor-6M-Cache-up-to-3-70 -GHz-? Q = i7-4870HQ

එම හරය 3.7Ghz දක්වා ධාවනය විය හැකි අතර සැබෑ දෘඩාංග සඳහා උපරිම උපරිම අනුපාතය 29.6 GB / s වේ. ඉතින් තවත් 4GB / s කොහෙද? සමහර විට, එය එක් එක් පුනරාවර්තනය තුළ ලූප් තර්කනය සහ අවට ඇති අනෙකුත් කේත සඳහා වියදම් කර ඇත.

දැන් මෙම ව්‍යාජ යැපීම කොහිද ? දෘඩාංග උපරිම වේගයකින් ධාවනය වේ. සමහර විට මගේ ගණිතය නරකයි, සමහර විට එය සිදු වේ :)

පීපීපීපීඑස් තවමත් එච්ඩබ්ලිව් දෝෂය යෝජනා කරන පුද්ගලයින් වැරදිකරුවෙකි, එබැවින් මම යෝජනාව අනුගමනය කර පේළිගත කිරීමේ උදාහරණ නිර්මාණය කළෙමි, පහත බලන්න.

මගේ 7960X හි පළමු අනුවාදය (තනි නිමැවුමකින් cnt0 දක්වා) 11MB / s, දෙවන අනුවාදය (ප්‍රතිදානය cnt0, cnt1, cnt2 සහ cnt3 සමඟ) 33MB / s වේගයෙන් ධාවනය වේ. කෙනෙකුට කියන්න පුළුවන් - වොයිලා! එය ප්‍රතිදාන පරායත්තතාවයි.

හරි, සමහර විට, මා කළ කාරණය නම්, මේ වගේ කේත ලිවීමට තේරුමක් නැති අතර එය ප්‍රතිදාන පරායත්තතා ගැටලුවක් නොව ගොළු කේත උත්පාදනයයි. අපි දෘඩාංග පරීක්ෂා නොකරමු, උපරිම කාර්ය සාධනය මුදා හැරීම සඳහා අපි කේත ලියන්නෙමු. HW OOO විසින් එම “ප්‍රතිදාන-පරායත්තතා” නැවත නම් කර සැඟවිය යුතු යැයි ඔබට අපේක්ෂා කළ හැකි නමුත්, නිවැරදි දේ නිවැරදිව කරන්න, එවිට ඔබට කිසිඳු අභිරහසකට මුහුණ නොදෙනු ඇත.

uint64_t builtin_popcnt1a(const uint64_t* buf, size_t len) 
{
    uint64_t cnt0, cnt1, cnt2, cnt3;
    cnt0 = cnt1 = cnt2 = cnt3 = 0;
    uint64_t val = buf[0];
    #if 0
        __asm__ __volatile__ (
            "1:\n\t"
            "popcnt %2, %1\n\t"
            "popcnt %2, %1\n\t"
            "popcnt %2, %1\n\t"
            "popcnt %2, %1\n\t"
            "subq $4, %0\n\t"
            "jnz 1b\n\t"
        : "+q" (len), "=q" (cnt0)
        : "q" (val)
        :
        );
    #else
        __asm__ __volatile__ (
            "1:\n\t"
            "popcnt %5, %1\n\t"
            "popcnt %5, %2\n\t"
            "popcnt %5, %3\n\t"
            "popcnt %5, %4\n\t"
            "subq $4, %0\n\t"
            "jnz 1b\n\t"
        : "+q" (len), "=q" (cnt0), "=q" (cnt1), "=q" (cnt2), "=q" (cnt3)
        : "q" (val)
        :
        );
    #endif
    return cnt0;
}

ඔබ මූලික ඔරලෝසු චක්‍රයේ (තත්පර වෙනුවට) වේලාව ගත කරන්නේ නම්, තත්පර 1 යනු කුඩා CPU- බැඳී ඇති පුඩුවක් සඳහා ඕනෑ තරම් කාලයයි. ප්‍රධාන වෙනස්කම් සොයා ගැනීමට හෝ යූප් ගණනය කිරීම් සඳහා පරිපූර්ණ කවුන්ටර පරීක්ෂා කිරීමට මීටර් 100 ක් පවා හොඳයි. විශේෂයෙන් ස්කයිලේක් මත, දෘඩාංග පී-රාජ්‍ය කළමනාකරණය මඟින් බර පැටවීම ආරම්භ වූ පසු මයික්‍රෝ තත්පර වලින් උපරිම ඔරලෝසු වේගය දක්වා වැඩි කිරීමට ඉඩ දෙයි.
පීටර් කෝඩ්ස්

clang හට __builtin_popcountlAVX2 සමඟ ස්වයංක්‍රීයව දෛශිකකරණය කළ හැකි vpshufbඅතර එසේ කිරීමට C ප්‍රභවයේ බහු සමුච්චය අවශ්‍ය නොවේ. මට ඒ ගැන විශ්වාස නැත _mm_popcnt_u64; එය ස්වයංක්‍රීයව දෛශිකකරණය කළ හැක්කේ AVX512-VPOPCNT සමඟ පමණි. ( AVX-512 හෝ AVX-2 / භාවිතා කරමින් විශාල දත්ත මත බිටු 1 ගණන් කිරීම (ජනගහන ගණන) බලන්න )
පීටර් කෝඩ්ස්

කෙසේ වෙතත්, ඉන්ටෙල්හි ප්‍රශස්තිකරණ අත්පොත දෙස බැලීම ප්‍රයෝජනවත් නොවනු ඇත: පිළිගත් පිළිතුර පෙන්නුම් කරන පරිදි, ගැටළුව අනපේක්ෂිත නිමැවුම් යැපීමකි popcnt. මෙය ඔවුන්ගේ මෑත කාලීන ක්ෂුද්‍ර සැකැස්ම සඳහා ඉන්ටෙල් හි වැරදි වල ලේඛනගත කර ඇත, නමුත් මම හිතන්නේ ඒ වන විට එය සිදු නොවීය. අනපේක්ෂිත ව්‍යාජ පරායත්තතා තිබේ නම් ඔබේ ඩෙප්-දාම විශ්ලේෂණය අසාර්ථක වනු ඇත, එබැවින් මෙම පිළිතුර හොඳ සාමාන්‍ය උපදෙස් නමුත් මෙහි අදාළ නොවේ.
පීටර් කෝඩ්ස්

1
ඔයා මට විහිළු කරනව ද? අතින් ලියන ලද asm loop එකක කාර්ය සාධන කවුන්ටර සමඟ පර්යේෂණාත්මකව මැනිය හැකි දේවල් ගැන මට “විශ්වාස” කිරීමට අවශ්‍ය නැත. ඒවා කරුණු පමණයි. මම පරීක්ෂා කර ඇති අතර, ස්කයිලේක් lzcnt/ සඳහා ව්‍යාජ යැපීම සවි කර ඇත tzcnt, නමුත් සඳහා නොවේ popcnt. Intel.com/content/dam/www/public/us/en/documents/… හි Intel හි වැරදිසහගත SKL029 බලන්න . එසේම, gcc.gnu.org/bugzilla/show_bug.cgi?id=62011 “නිරාකරණය කර ඇත” මිස “අවලංගු” නොවේ. එච්ඩබ්ලිව් හි නිමැවුම් පරායත්තතාවයක් නොමැති බවට ඔබේ ප්‍රකාශයට කිසිදු පදනමක් නොමැත.
පීටර් කෝර්ඩ්ස්

1
ඔබ popcnt eax, edx/ වැනි සරල පුඩුවක් dec ecx / jnzසාදන්නේ නම්, එය ඔරලෝසුවකට 1 බැගින් ධාවනය වනු ඇතැයි ඔබ අපේක්ෂා කරනු ඇත, පොප්කන්ට් ප්‍රතිදානය මත බාධා ඇති වූ අතර ශාඛා ප්‍රතිදානය ලබා ගනී. නමුත් එය ඇත්ත වශයෙන්ම ක්‍රියාත්මක වන්නේ ඔරලෝසු 3 කට 1 බැගින් popcntපමණි. ඔබට ස්කයිලේක් ඇත, එබැවින් ඔබට එය තනිවම උත්සාහ කළ හැකිය.
පීටර් කෝර්ඩ්ස්

-3

හරි, OP විසින් ඇසූ උප ප්‍රශ්න වලින් එකකට කුඩා පිළිතුරක් ලබා දීමට මට අවශ්‍යය. Caveat, මම කිසිදු පරීක්‍ෂණයක් හෝ කේත උත්පාදනය කිරීමක් හෝ විසුරුවා හැරීමක් කර නැත, අන් අයට පැහැදිලි කිරීමක් සඳහා සිතුවිල්ලක් බෙදා ගැනීමට අවශ්‍ය විය.

staticකාර්ය සාධනය වෙනස් කරන්නේ ඇයි ?

ප්‍රශ්නාර්ථ රේඛාව: uint64_t size = atol(argv[1])<<20;

කෙටි පිළිතුර

ප්‍රවේශය සඳහා ජනනය කරන ලද එකලස් කිරීම දෙස මම බලා සිටිමි size. ස්ථිතික නොවන අනුවාදය සඳහා දර්ශක නිරෝධනය කිරීමේ අමතර පියවර තිබේදැයි බලන්න.

දිගු පිළිතුර

විචල්‍යය ප්‍රකාශයට පත් කළත් නැතත් එහි එක් පිටපතක් පමණක් ඇති staticනිසාත්, ප්‍රමාණය වෙනස් නොවන නිසාත්, වෙනස වන්නේ විචල්‍යය පිටුපසට තැබීමට භාවිතා කරන මතකයේ පිහිටීම සහ කේතයේ තවදුරටත් භාවිතා වන ස්ථානයයි. පහළ.

හරි, පැහැදිලිව ආරම්භ කිරීම සඳහා, ශ්‍රිතයක සියලුම දේශීය විචල්‍යයන් (පරාමිතීන් සමඟ) ගබඩාවක් ලෙස භාවිතා කිරීම සඳහා තොගයේ ඉඩ ලබා දී ඇති බව මතක තබා ගන්න. දැන්, පැහැදිලිවම, ප්‍රධාන () සඳහා වන රාමු රාමුව කිසි විටෙකත් පිරිසිදු නොවන අතර එය උත්පාදනය වන්නේ එක් වරක් පමණි. හරි, එය සෑදීම ගැන කුමක් කිව staticහැකිද? හොඳයි, එම අවස්ථාවේ දී සම්පාදකයා ක්‍රියාවලියේ ගෝලීය දත්ත අවකාශයේ ඉඩ වෙන් කර ගැනීමට දන්නා බැවින් තොග රාමුවක් ඉවත් කිරීමෙන් ස්ථානය ඉවත් කළ නොහැක. නමුත් තවමත්, අපට ඇත්තේ එක් ස්ථානයක් පමණි, එබැවින් වෙනස කුමක්ද? තොගයේ මතකයේ පිහිටීම් සඳහන් කරන්නේ කෙසේද යන්නට එය සම්බන්ධ යැයි මම සැක කරමි.

සම්පාදකයා සංකේත වගුව ජනනය කරන විට, එය විශාලත්වය වැනි අදාළ ගුණාංග සමඟ ලේබලයක් සඳහා ප්‍රවේශයක් ලබා දෙයි. එය මතකයේ සුදුසු ඉඩක් වෙන් කර ගත යුතු බව දන්නා නමුත් තරමක් පසුව පසුව එම ස්ථානය තෝරා නොගනී සජීවී විශ්ලේෂණය කිරීමෙන් පසු ක්‍රියාවලිය සහ ප්‍රතිපාදන වෙන් කිරීම. අවසාන එකලස් කිරීමේ කේතය සඳහා යන්ත්‍ර කේතයට සැපයිය යුතු ලිපිනය සම්බන්ධකය දන්නේ කෙසේද? එය එක්කෝ අවසාන ස්ථානය දනී හෝ ස්ථානයට පැමිණිය යුතු ආකාරය දනී. තොගයක් සමඟ, එක් මූලද්‍රව්‍ය දෙකක් මත පදනම් වූ ස්ථානයක් යොමු කිරීම ඉතා සරල ය, ස්ටැක්ෆ්‍රේම් වෙත දර්ශකය සහ පසුව රාමුවට ඕෆ්සෙට් එකක්. මෙය මූලිකවම සිදුවන්නේ ක්‍රියාකාරී වේලාවට පෙර සම්බන්ධකයට ස්ටැක්ෆ්‍රේම්හි පිහිටීම දැනගත නොහැකි බැවිනි.


2
OP විසින් පරීක්‍ෂා කරන ලද ඉන්ටෙල් CPU වල staticව්‍යාජ නිමැවුම් පරායත්තතාවයට බලපාන අයුරින් එම කාර්යය සඳහා රෙජිස්ටර් ප්‍රතිපාදන වෙනස් කිරීම භාවිතා කිරීම මට බොහෝ දුරට පෙනේ popcnt, ඒවා වළක්වා ගැනීමට නොදන්නා සම්පාදකයෙකු සමඟ. (ඉන්ටෙල් සීපීයූවල ඇති මෙම ක්‍රියාකාරී වළේ තවමත් සොයාගෙන නොතිබුණි.) සම්පාදකයෙකුට staticස්වයංක්‍රීය ගබඩා විචල්‍යයක් මෙන් දේශීය විචල්‍යයක් ලේඛනයක තබා ගත හැකිය , නමුත් ඒවා mainඑක් වරක් පමණක් ධාවනය වේ යැයි උපකල්පනය කිරීම ප්‍රශස්ත නොකළහොත් එය බලපානු ඇත code-gen (වටිනාකම නියම කර ඇත්තේ පළමු ඇමතුමෙන් පමණක් නිසාය.)
පීටර් කෝර්ඩ්ස්

1
කෙසේ වෙතත්, බොහෝ අවස්ථාවන්හීදී [RIP + rel32]සහ [rsp + 42]ඇමතීමේ ක්‍රම අතර කාර්ය සාධන වෙනස නොසැලකිලිමත් වේ. cmp dword [RIP+rel32], immediateමයික්‍රෝ ෆියුස් එක බරකට + cmp uop බවට පත් කළ නොහැක, නමුත් එය සාධකයක් වනු ඇතැයි මම නොසිතමි. මා කීවාක් මෙන්, ලූප ඇතුළත එය කෙසේ හෝ ලේඛනයක රැඳෙනු ඇත, නමුත් C ++ කරකැවීම මඟින් විවිධ සම්පාදක තේරීම් අදහස් කළ හැකිය.
පීටර් කෝර්ඩ්ස්
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.