c++ - আমি কিভাবে চক্রের প্রতি সর্বাধিক 4 টি FLOPs অর্জন করব?




optimization architecture (3)

2.4GHz Intel Core 2 Duo এ Intels icc সংস্করণ 11.1 ব্যবহার করে আমি পেতে পারি

Macintosh:~ mackie$ icc -O3 -mssse3 -oaddmul addmul.cc && ./addmul 1000
addmul:  0.105 s, 9.525 Gflops, res=0.000000
Macintosh:~ mackie$ icc -v
Version 11.1 

আদর্শ 9.6 Gflops খুব কাছাকাছি যে।

সম্পাদনা করুন:

ওহো, সমাবেশ কোডটি দেখলে মনে হয় যে আইসিসি শুধুমাত্র গুণকে ভেক্টরাইজ করেনি, তবে লুপের অতিরিক্ত সংযোজনও করেছে। একটি কঠোর FP semantics জোর করা কোড আর vectorized হয়:

Macintosh:~ mackie$ icc -O3 -mssse3 -oaddmul addmul.cc -fp-model precise && ./addmul 1000
addmul:  0.516 s, 1.938 Gflops, res=1.326463

EDIT2:

অনুরোধ হিসাবে:

Macintosh:~ mackie$ clang -O3 -mssse3 -oaddmul addmul.cc && ./addmul 1000
addmul:  0.209 s, 4.786 Gflops, res=1.326463
Macintosh:~ mackie$ clang -v
Apple clang version 3.0 (tags/Apple/clang-211.10.1) (based on LLVM 3.0svn)
Target: x86_64-apple-darwin11.2.0
Thread model: posix

Clang এর কোডের অভ্যন্তরীণ লুপ এই রকম দেখাচ্ছে:

        .align  4, 0x90
LBB2_4:                                 ## =>This Inner Loop Header: Depth=1
        addsd   %xmm2, %xmm3
        addsd   %xmm2, %xmm14
        addsd   %xmm2, %xmm5
        addsd   %xmm2, %xmm1
        addsd   %xmm2, %xmm4
        mulsd   %xmm2, %xmm0
        mulsd   %xmm2, %xmm6
        mulsd   %xmm2, %xmm7
        mulsd   %xmm2, %xmm11
        mulsd   %xmm2, %xmm13
        incl    %eax
        cmpl    %r14d, %eax
        jl      LBB2_4

EDIT3:

অবশেষে, দুটি পরামর্শ: প্রথমত, যদি আপনি এই ধরনের বেঞ্চমার্কিং পছন্দ করেন তবে gettimeofday(2) এর rdtsc নির্দেশনা rdtsc ব্যবহার করে বিবেচনা করুন। এটি অনেক বেশি সঠিক এবং চক্রগুলিতে সময় সরবরাহ করে, যা আপনি যে কোনওভাবে আগ্রহী। GCC এবং বন্ধুদের জন্য আপনি এটি এইভাবে সংজ্ঞায়িত করতে পারেন:

#include <stdint.h>

static __inline__ uint64_t rdtsc(void)
{
        uint64_t rval;
        __asm__ volatile ("rdtsc" : "=A" (rval));
        return rval;
}

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

কিভাবে আধুনিক x86-64 ইন্টেল CPU এ 4 চলাচল বিন্দু অপারেশন (ডবল স্পষ্টতা) প্রতি চক্রের তত্ত্বগত শীর্ষ কার্যক্ষমতা অর্জন করা যায়?

যতদূর আমি বুঝতে পারি এটি SSE mul জন্য তিনটি চক্র এবং আধুনিক ইন্টেল CPUs (উদাহরণস্বরূপ এগারার ফগ এর 'ইন্সট্রাকশন টেবিল' ) দেখুন। পাইপলাইনিংয়ের কারণে অ্যালগরিদমটি কমপক্ষে তিনটি স্বাধীন সংক্ষেপে থাকলে প্রতি চক্রের একটি add একটি থ্রুপুট পেতে পারে। যেহেতু প্যাকড addpd পাশাপাশি স্কলার addsd সংস্করণগুলির addpd সত্য এবং এসএসই addsd দুই double এর থ্রুপুট থাকতে পারে চক্র প্রতি দুই ফ্লপের মতো।

উপরন্তু, মনে হচ্ছে (যদিও আমি এই বিষয়ে কোনও সঠিক নথি দেখিনি) add এবং mul এর সমান্তরালভাবে চক্র প্রতি চারটি ফ্লপের একটি তাত্ত্বিক সর্বোচ্চ থ্রুপুট প্রদান করা যেতে পারে।

যাইহোক, আমি একটি সহজ সি / সি ++ প্রোগ্রামের সাথে সেই কর্মক্ষমতাটি প্রতিলিপি করতে সক্ষম নই। আমার সেরা প্রচেষ্টা প্রায় 2.7 flops / চক্র ফলে। কেউ যদি সহজ সি / সি ++ বা অ্যাসবেলার প্রোগ্রামটি অবদান রাখতে পারে যা উচ্চ মানের প্রশংসা করে এমন শীর্ষ কর্মক্ষমতা প্রদর্শন করে।

আমার প্রচেষ্টা:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <sys/time.h>

double stoptime(void) {
   struct timeval t;
   gettimeofday(&t,NULL);
   return (double) t.tv_sec + t.tv_usec/1000000.0;
}

double addmul(double add, double mul, int ops){
   // Need to initialise differently otherwise compiler might optimise away
   double sum1=0.1, sum2=-0.1, sum3=0.2, sum4=-0.2, sum5=0.0;
   double mul1=1.0, mul2= 1.1, mul3=1.2, mul4= 1.3, mul5=1.4;
   int loops=ops/10;          // We have 10 floating point operations inside the loop
   double expected = 5.0*add*loops + (sum1+sum2+sum3+sum4+sum5)
               + pow(mul,loops)*(mul1+mul2+mul3+mul4+mul5);

   for (int i=0; i<loops; i++) {
      mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
      sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;
   }
   return  sum1+sum2+sum3+sum4+sum5+mul1+mul2+mul3+mul4+mul5 - expected;
}

int main(int argc, char** argv) {
   if (argc != 2) {
      printf("usage: %s <num>\n", argv[0]);
      printf("number of operations: <num> millions\n");
      exit(EXIT_FAILURE);
   }
   int n = atoi(argv[1]) * 1000000;
   if (n<=0)
       n=1000;

   double x = M_PI;
   double y = 1.0 + 1e-8;
   double t = stoptime();
   x = addmul(x, y, n);
   t = stoptime() - t;
   printf("addmul:\t %.3f s, %.3f Gflops, res=%f\n", t, (double)n/t/1e9, x);
   return EXIT_SUCCESS;
}

সঙ্গে সংকলিত

g++ -O2 -march=native addmul.cpp ; ./a.out 1000

একটি ইনটেল কোর i5-750, 2.66 গিগাহার্জ উপর নিম্নলিখিত আউটপুট উত্পাদন করে।

addmul:  0.270 s, 3.707 Gflops, res=1.326463

অর্থাৎ, প্রতি চক্র মাত্র 1.4 flops। g++ -S -O2 -march=native -masm=intel addmul.cpp এর সাথে g++ -S -O2 -march=native -masm=intel addmul.cpp প্রধান লুপটি আমার কাছে অনুকূল মনে হচ্ছে:

.L4:
inc    eax
mulsd    xmm8, xmm3
mulsd    xmm7, xmm3
mulsd    xmm6, xmm3
mulsd    xmm5, xmm3
mulsd    xmm1, xmm3
addsd    xmm13, xmm2
addsd    xmm12, xmm2
addsd    xmm11, xmm2
addsd    xmm10, xmm2
addsd    xmm9, xmm2
cmp    eax, ebx
jne    .L4

প্যাকড সংস্করণগুলির সাথে স্কলার সংস্করণগুলি পরিবর্তন করা ( addpd এবং mulpd ) mulpd সময় পরিবর্তন না করে ফ্লপ গণনা দ্বিগুণ করবে এবং তাই আমি প্রতি চক্র 2.8 ফ্লপ মাত্র অল্প পেতে পারব। চক্র প্রতি চার flops অর্জন যা একটি সহজ উদাহরণ আছে?

রহস্যময় দ্বারা চমৎকার সামান্য প্রোগ্রাম; এখানে আমার ফলাফল (যদিও কয়েক সেকেন্ডের জন্য চালানো):

  • gcc -O2 -march=nocona : 5.6 Gflops 10.66 Gflops (2.1 ফ্লপ / চক্র)
  • cl /O2 , openmp সরানো হয়েছে: 10.1 Gflops 10.66 Gflops (3.8 ফ্লপ / চক্র)

এটা সব কিছু জটিল মনে হচ্ছে, কিন্তু আমার সিদ্ধান্তগুলি এতদূর:

  • gcc -O2 বিকল্প বিন্দু বিন্দু অপারেশনগুলির ক্রম পরিবর্তন করে যদি addpd এবং mulpd সম্ভব হয়। gcc-4.6.2 -O2 -march=core2 একই প্রযোজ্য।

  • gcc -O2 -march=nocona সি ++ উত্স হিসাবে সংজ্ঞায়িত হিসাবে ভাসমান বিন্দু ক্রিয়াকলাপের ক্রম রাখা মনে হচ্ছে।

  • cl /O2 , উইন্ডোজ 7 এর SDK থেকে 64-বিট কম্পাইলারটি স্বয়ংক্রিয়ভাবে লুপ-অরোলিং করে এবং ক্রিয়াকলাপগুলি চেষ্টা করে এবং পরিচালনা করে বলে মনে হয় যাতে তিনটি addpd বিকল্প তিনটি mulpd এর বিকল্প হয় (ভাল, অন্তত আমার সিস্টেমে এবং আমার সহজ প্রোগ্রামের জন্য)।

  • আমার কোর i5 750 ( নাহলেম আর্কিটেকচার ) অ্যাড এবং মল এর বিকল্পগুলি পছন্দ করে না এবং সমান্তরালে উভয় ক্রিয়াকলাপ চালাতে অক্ষম বলে মনে হয়। যাইহোক, 3 এর মধ্যে গোষ্ঠী যদি এটি হঠাৎ জাদু মত কাজ করে।

  • অন্যান্য আর্কিটেকচার (সম্ভবত স্যান্ডি ব্রিজ এবং অন্যান্য) সমাবেশের বিকল্পে বিকল্প থাকলে সমান্তরাল কোনও সমান্তরালে যোগ / mul কার্যকর করতে সক্ষম বলে মনে হয়।

  • যদিও স্বীকার করা কঠিন হলেও আমার সিস্টেমে cl /O2 আমার সিস্টেমের জন্য নিম্ন-স্তরের অপ্টিমাইজেশান ক্রিয়াকলাপগুলিতে অনেক ভাল কাজ করে এবং উপরের সি ++ উদাহরণের জন্য শীর্ষ কর্মক্ষমতা অর্জন করে। আমি 1.85-2.01 ফ্লপ / চক্রের মধ্যে মাপা (উইন্ডোতে ঘড়ি () ব্যবহার করেছি যা সঠিক নয়। আমি মনে করি, একটি ভাল টাইমার ব্যবহার করতে হবে - ধন্যবাদ ম্যাকি মেসার)।

  • আমি gcc দিয়ে পরিচালিত সেরা ম্যানুয়াল লুপ unroll এবং তিনটি গ্রুপে সংযোজন এবং গুণ ব্যবস্থা ব্যবস্থা ছিল। g++ -O2 -march=nocona addmul_unroll.cpp আমি সেরা 0.207s, 4.825 Gflops যা 1.8 ফ্লপ / চক্রের সাথে সম্পর্কিত যা আমি এখন বেশ খুশি।

সি ++ কোড আমি লুপ for প্রতিস্থাপিত করেছি

   for (int i=0; i<loops/3; i++) {
       mul1*=mul; mul2*=mul; mul3*=mul;
       sum1+=add; sum2+=add; sum3+=add;
       mul4*=mul; mul5*=mul; mul1*=mul;
       sum4+=add; sum5+=add; sum1+=add;

       mul2*=mul; mul3*=mul; mul4*=mul;
       sum2+=add; sum3+=add; sum4+=add;
       mul5*=mul; mul1*=mul; mul2*=mul;
       sum5+=add; sum1+=add; sum2+=add;

       mul3*=mul; mul4*=mul; mul5*=mul;
       sum3+=add; sum4+=add; sum5+=add;
   }

এবং সমাবেশ এখন মনে হচ্ছে

.L4:
mulsd    xmm8, xmm3
mulsd    xmm7, xmm3
mulsd    xmm6, xmm3
addsd    xmm13, xmm2
addsd    xmm12, xmm2
addsd    xmm11, xmm2
mulsd    xmm5, xmm3
mulsd    xmm1, xmm3
mulsd    xmm8, xmm3
addsd    xmm10, xmm2
addsd    xmm9, xmm2
addsd    xmm13, xmm2
...

আমি আগে এই সঠিক কাজ করেছেন। কিন্তু এটি মূলত বিদ্যুৎ খরচ এবং CPU তাপমাত্রা পরিমাপ করতে ছিল। নিম্নলিখিত কোডটি (যা মোটামুটি লম্বা) আমার কোর i7 2600K এ অনুকূলের কাছাকাছি পৌঁছায়।

এখানে নোট করার মূল বিষয় হল ম্যানুয়াল লুপ-অরোলিংয়ের বিশাল পরিমাণ এবং বহুগুণে ইন্টারলেভিং এবং যোগ করা ...

সম্পূর্ণ প্রকল্পটি আমার GitHub এ পাওয়া যেতে পারে: https://github.com/Mysticial/Flops

সতর্কতা:

আপনি যদি কম্পাইল এবং চালানোর সিদ্ধান্ত নেন তবে আপনার CPU তাপমাত্রায় মনোযোগ দিন !!!
আপনি এটা overheat না নিশ্চিত করুন। এবং নিশ্চিত করুন যে CPU-throttling আপনার ফলাফল প্রভাবিত করে না!

উপরন্তু, এই কোডটি চালানোর ফলে যে কোনও ক্ষতির জন্য আমি কোনও দায়বদ্ধ নই।

নোট:

  • এই কোড x64 জন্য অপ্টিমাইজ করা হয়। ভাল কম্পাইল এই জন্য x86 যথেষ্ট রেজিস্ট্রি নেই।
  • এই কোডটি ভিসুয়াল স্টুডিও 2010/2012 এবং জিसीसी 4.6 তে ভাল কাজ করার জন্য পরীক্ষা করা হয়েছে।
    আইসিসির 11 (ইন্টেল কম্পাইলার 11) আশ্চর্যজনকভাবে এটি ভালভাবে সংকলন করতে সমস্যা হয়েছে।
  • এই প্রাক-এফএমএ প্রসেসর জন্য। ইন্টেল হ্যাসওয়েল এবং এএমডি বুলডোজার প্রসেসরগুলিতে (এবং পরবর্তীতে) শীর্ষক FLOPS অর্জন করার জন্য, এফএমএ (সংযুক্ত মাল্টিপল অ্যাড) নির্দেশাবলী প্রয়োজন হবে। এই এই benchmark এর সুযোগ অতিক্রম করা হয়।

#include <emmintrin.h>
#include <omp.h>
#include <iostream>
using namespace std;

typedef unsigned long long uint64;

double test_dp_mac_SSE(double x,double y,uint64 iterations){
    register __m128d r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,rA,rB,rC,rD,rE,rF;

    //  Generate starting data.
    r0 = _mm_set1_pd(x);
    r1 = _mm_set1_pd(y);

    r8 = _mm_set1_pd(-0.0);

    r2 = _mm_xor_pd(r0,r8);
    r3 = _mm_or_pd(r0,r8);
    r4 = _mm_andnot_pd(r8,r0);
    r5 = _mm_mul_pd(r1,_mm_set1_pd(0.37796447300922722721));
    r6 = _mm_mul_pd(r1,_mm_set1_pd(0.24253562503633297352));
    r7 = _mm_mul_pd(r1,_mm_set1_pd(4.1231056256176605498));
    r8 = _mm_add_pd(r0,_mm_set1_pd(0.37796447300922722721));
    r9 = _mm_add_pd(r1,_mm_set1_pd(0.24253562503633297352));
    rA = _mm_sub_pd(r0,_mm_set1_pd(4.1231056256176605498));
    rB = _mm_sub_pd(r1,_mm_set1_pd(4.1231056256176605498));

    rC = _mm_set1_pd(1.4142135623730950488);
    rD = _mm_set1_pd(1.7320508075688772935);
    rE = _mm_set1_pd(0.57735026918962576451);
    rF = _mm_set1_pd(0.70710678118654752440);

    uint64 iMASK = 0x800fffffffffffffull;
    __m128d MASK = _mm_set1_pd(*(double*)&iMASK);
    __m128d vONE = _mm_set1_pd(1.0);

    uint64 c = 0;
    while (c < iterations){
        size_t i = 0;
        while (i < 1000){
            //  Here's the meat - the part that really matters.

            r0 = _mm_mul_pd(r0,rC);
            r1 = _mm_add_pd(r1,rD);
            r2 = _mm_mul_pd(r2,rE);
            r3 = _mm_sub_pd(r3,rF);
            r4 = _mm_mul_pd(r4,rC);
            r5 = _mm_add_pd(r5,rD);
            r6 = _mm_mul_pd(r6,rE);
            r7 = _mm_sub_pd(r7,rF);
            r8 = _mm_mul_pd(r8,rC);
            r9 = _mm_add_pd(r9,rD);
            rA = _mm_mul_pd(rA,rE);
            rB = _mm_sub_pd(rB,rF);

            r0 = _mm_add_pd(r0,rF);
            r1 = _mm_mul_pd(r1,rE);
            r2 = _mm_sub_pd(r2,rD);
            r3 = _mm_mul_pd(r3,rC);
            r4 = _mm_add_pd(r4,rF);
            r5 = _mm_mul_pd(r5,rE);
            r6 = _mm_sub_pd(r6,rD);
            r7 = _mm_mul_pd(r7,rC);
            r8 = _mm_add_pd(r8,rF);
            r9 = _mm_mul_pd(r9,rE);
            rA = _mm_sub_pd(rA,rD);
            rB = _mm_mul_pd(rB,rC);

            r0 = _mm_mul_pd(r0,rC);
            r1 = _mm_add_pd(r1,rD);
            r2 = _mm_mul_pd(r2,rE);
            r3 = _mm_sub_pd(r3,rF);
            r4 = _mm_mul_pd(r4,rC);
            r5 = _mm_add_pd(r5,rD);
            r6 = _mm_mul_pd(r6,rE);
            r7 = _mm_sub_pd(r7,rF);
            r8 = _mm_mul_pd(r8,rC);
            r9 = _mm_add_pd(r9,rD);
            rA = _mm_mul_pd(rA,rE);
            rB = _mm_sub_pd(rB,rF);

            r0 = _mm_add_pd(r0,rF);
            r1 = _mm_mul_pd(r1,rE);
            r2 = _mm_sub_pd(r2,rD);
            r3 = _mm_mul_pd(r3,rC);
            r4 = _mm_add_pd(r4,rF);
            r5 = _mm_mul_pd(r5,rE);
            r6 = _mm_sub_pd(r6,rD);
            r7 = _mm_mul_pd(r7,rC);
            r8 = _mm_add_pd(r8,rF);
            r9 = _mm_mul_pd(r9,rE);
            rA = _mm_sub_pd(rA,rD);
            rB = _mm_mul_pd(rB,rC);

            i++;
        }

        //  Need to renormalize to prevent denormal/overflow.
        r0 = _mm_and_pd(r0,MASK);
        r1 = _mm_and_pd(r1,MASK);
        r2 = _mm_and_pd(r2,MASK);
        r3 = _mm_and_pd(r3,MASK);
        r4 = _mm_and_pd(r4,MASK);
        r5 = _mm_and_pd(r5,MASK);
        r6 = _mm_and_pd(r6,MASK);
        r7 = _mm_and_pd(r7,MASK);
        r8 = _mm_and_pd(r8,MASK);
        r9 = _mm_and_pd(r9,MASK);
        rA = _mm_and_pd(rA,MASK);
        rB = _mm_and_pd(rB,MASK);
        r0 = _mm_or_pd(r0,vONE);
        r1 = _mm_or_pd(r1,vONE);
        r2 = _mm_or_pd(r2,vONE);
        r3 = _mm_or_pd(r3,vONE);
        r4 = _mm_or_pd(r4,vONE);
        r5 = _mm_or_pd(r5,vONE);
        r6 = _mm_or_pd(r6,vONE);
        r7 = _mm_or_pd(r7,vONE);
        r8 = _mm_or_pd(r8,vONE);
        r9 = _mm_or_pd(r9,vONE);
        rA = _mm_or_pd(rA,vONE);
        rB = _mm_or_pd(rB,vONE);

        c++;
    }

    r0 = _mm_add_pd(r0,r1);
    r2 = _mm_add_pd(r2,r3);
    r4 = _mm_add_pd(r4,r5);
    r6 = _mm_add_pd(r6,r7);
    r8 = _mm_add_pd(r8,r9);
    rA = _mm_add_pd(rA,rB);

    r0 = _mm_add_pd(r0,r2);
    r4 = _mm_add_pd(r4,r6);
    r8 = _mm_add_pd(r8,rA);

    r0 = _mm_add_pd(r0,r4);
    r0 = _mm_add_pd(r0,r8);


    //  Prevent Dead Code Elimination
    double out = 0;
    __m128d temp = r0;
    out += ((double*)&temp)[0];
    out += ((double*)&temp)[1];

    return out;
}

void test_dp_mac_SSE(int tds,uint64 iterations){

    double *sum = (double*)malloc(tds * sizeof(double));
    double start = omp_get_wtime();

#pragma omp parallel num_threads(tds)
    {
        double ret = test_dp_mac_SSE(1.1,2.1,iterations);
        sum[omp_get_thread_num()] = ret;
    }

    double secs = omp_get_wtime() - start;
    uint64 ops = 48 * 1000 * iterations * tds * 2;
    cout << "Seconds = " << secs << endl;
    cout << "FP Ops  = " << ops << endl;
    cout << "FLOPs   = " << ops / secs << endl;

    double out = 0;
    int c = 0;
    while (c < tds){
        out += sum[c++];
    }

    cout << "sum = " << out << endl;
    cout << endl;

    free(sum);
}

int main(){
    //  (threads, iterations)
    test_dp_mac_SSE(8,10000000);

    system("pause");
}

আউটপুট (1 থ্রেড, 10000000 পুনরাবৃত্তি) - ভিজ্যুয়াল স্টুডিও 2010 SP1 - x64 রিলিজের সাথে সংকলিত:

Seconds = 55.5104
FP Ops  = 960000000000
FLOPs   = 1.7294e+010
sum = 2.22652

যন্ত্র একটি কোর i7 2600K @ 4.4 গিগাহার্জ। তাত্ত্বিক এসএসই শিখর 4 টি ফ্লপ * 4.4 গিগাহার্জ = 17.6 জিএফলপস । এই কোড 17.3 GFlops অর্জন - খারাপ না।

আউটপুট (8 থ্রেড, 10000000 পুনরাবৃত্তি) - ভিজ্যুয়াল স্টুডিও 2010 SP1 - x64 রিলিজের সাথে সংকলিত:

Seconds = 117.202
FP Ops  = 7680000000000
FLOPs   = 6.55279e+010
sum = 17.8122

তাত্ত্বিক এসএসই শিখর 4 টি ফ্লপ * 4 কোর * 4.4 গিগাহার্জ = 70.4 জিএফলপস। প্রকৃত 65.5 GFlops হয়

আসুন এই এক ধাপ এগিয়ে। AVX ...

#include <immintrin.h>
#include <omp.h>
#include <iostream>
using namespace std;

typedef unsigned long long uint64;

double test_dp_mac_AVX(double x,double y,uint64 iterations){
    register __m256d r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,rA,rB,rC,rD,rE,rF;

    //  Generate starting data.
    r0 = _mm256_set1_pd(x);
    r1 = _mm256_set1_pd(y);

    r8 = _mm256_set1_pd(-0.0);

    r2 = _mm256_xor_pd(r0,r8);
    r3 = _mm256_or_pd(r0,r8);
    r4 = _mm256_andnot_pd(r8,r0);
    r5 = _mm256_mul_pd(r1,_mm256_set1_pd(0.37796447300922722721));
    r6 = _mm256_mul_pd(r1,_mm256_set1_pd(0.24253562503633297352));
    r7 = _mm256_mul_pd(r1,_mm256_set1_pd(4.1231056256176605498));
    r8 = _mm256_add_pd(r0,_mm256_set1_pd(0.37796447300922722721));
    r9 = _mm256_add_pd(r1,_mm256_set1_pd(0.24253562503633297352));
    rA = _mm256_sub_pd(r0,_mm256_set1_pd(4.1231056256176605498));
    rB = _mm256_sub_pd(r1,_mm256_set1_pd(4.1231056256176605498));

    rC = _mm256_set1_pd(1.4142135623730950488);
    rD = _mm256_set1_pd(1.7320508075688772935);
    rE = _mm256_set1_pd(0.57735026918962576451);
    rF = _mm256_set1_pd(0.70710678118654752440);

    uint64 iMASK = 0x800fffffffffffffull;
    __m256d MASK = _mm256_set1_pd(*(double*)&iMASK);
    __m256d vONE = _mm256_set1_pd(1.0);

    uint64 c = 0;
    while (c < iterations){
        size_t i = 0;
        while (i < 1000){
            //  Here's the meat - the part that really matters.

            r0 = _mm256_mul_pd(r0,rC);
            r1 = _mm256_add_pd(r1,rD);
            r2 = _mm256_mul_pd(r2,rE);
            r3 = _mm256_sub_pd(r3,rF);
            r4 = _mm256_mul_pd(r4,rC);
            r5 = _mm256_add_pd(r5,rD);
            r6 = _mm256_mul_pd(r6,rE);
            r7 = _mm256_sub_pd(r7,rF);
            r8 = _mm256_mul_pd(r8,rC);
            r9 = _mm256_add_pd(r9,rD);
            rA = _mm256_mul_pd(rA,rE);
            rB = _mm256_sub_pd(rB,rF);

            r0 = _mm256_add_pd(r0,rF);
            r1 = _mm256_mul_pd(r1,rE);
            r2 = _mm256_sub_pd(r2,rD);
            r3 = _mm256_mul_pd(r3,rC);
            r4 = _mm256_add_pd(r4,rF);
            r5 = _mm256_mul_pd(r5,rE);
            r6 = _mm256_sub_pd(r6,rD);
            r7 = _mm256_mul_pd(r7,rC);
            r8 = _mm256_add_pd(r8,rF);
            r9 = _mm256_mul_pd(r9,rE);
            rA = _mm256_sub_pd(rA,rD);
            rB = _mm256_mul_pd(rB,rC);

            r0 = _mm256_mul_pd(r0,rC);
            r1 = _mm256_add_pd(r1,rD);
            r2 = _mm256_mul_pd(r2,rE);
            r3 = _mm256_sub_pd(r3,rF);
            r4 = _mm256_mul_pd(r4,rC);
            r5 = _mm256_add_pd(r5,rD);
            r6 = _mm256_mul_pd(r6,rE);
            r7 = _mm256_sub_pd(r7,rF);
            r8 = _mm256_mul_pd(r8,rC);
            r9 = _mm256_add_pd(r9,rD);
            rA = _mm256_mul_pd(rA,rE);
            rB = _mm256_sub_pd(rB,rF);

            r0 = _mm256_add_pd(r0,rF);
            r1 = _mm256_mul_pd(r1,rE);
            r2 = _mm256_sub_pd(r2,rD);
            r3 = _mm256_mul_pd(r3,rC);
            r4 = _mm256_add_pd(r4,rF);
            r5 = _mm256_mul_pd(r5,rE);
            r6 = _mm256_sub_pd(r6,rD);
            r7 = _mm256_mul_pd(r7,rC);
            r8 = _mm256_add_pd(r8,rF);
            r9 = _mm256_mul_pd(r9,rE);
            rA = _mm256_sub_pd(rA,rD);
            rB = _mm256_mul_pd(rB,rC);

            i++;
        }

        //  Need to renormalize to prevent denormal/overflow.
        r0 = _mm256_and_pd(r0,MASK);
        r1 = _mm256_and_pd(r1,MASK);
        r2 = _mm256_and_pd(r2,MASK);
        r3 = _mm256_and_pd(r3,MASK);
        r4 = _mm256_and_pd(r4,MASK);
        r5 = _mm256_and_pd(r5,MASK);
        r6 = _mm256_and_pd(r6,MASK);
        r7 = _mm256_and_pd(r7,MASK);
        r8 = _mm256_and_pd(r8,MASK);
        r9 = _mm256_and_pd(r9,MASK);
        rA = _mm256_and_pd(rA,MASK);
        rB = _mm256_and_pd(rB,MASK);
        r0 = _mm256_or_pd(r0,vONE);
        r1 = _mm256_or_pd(r1,vONE);
        r2 = _mm256_or_pd(r2,vONE);
        r3 = _mm256_or_pd(r3,vONE);
        r4 = _mm256_or_pd(r4,vONE);
        r5 = _mm256_or_pd(r5,vONE);
        r6 = _mm256_or_pd(r6,vONE);
        r7 = _mm256_or_pd(r7,vONE);
        r8 = _mm256_or_pd(r8,vONE);
        r9 = _mm256_or_pd(r9,vONE);
        rA = _mm256_or_pd(rA,vONE);
        rB = _mm256_or_pd(rB,vONE);

        c++;
    }

    r0 = _mm256_add_pd(r0,r1);
    r2 = _mm256_add_pd(r2,r3);
    r4 = _mm256_add_pd(r4,r5);
    r6 = _mm256_add_pd(r6,r7);
    r8 = _mm256_add_pd(r8,r9);
    rA = _mm256_add_pd(rA,rB);

    r0 = _mm256_add_pd(r0,r2);
    r4 = _mm256_add_pd(r4,r6);
    r8 = _mm256_add_pd(r8,rA);

    r0 = _mm256_add_pd(r0,r4);
    r0 = _mm256_add_pd(r0,r8);

    //  Prevent Dead Code Elimination
    double out = 0;
    __m256d temp = r0;
    out += ((double*)&temp)[0];
    out += ((double*)&temp)[1];
    out += ((double*)&temp)[2];
    out += ((double*)&temp)[3];

    return out;
}

void test_dp_mac_AVX(int tds,uint64 iterations){

    double *sum = (double*)malloc(tds * sizeof(double));
    double start = omp_get_wtime();

#pragma omp parallel num_threads(tds)
    {
        double ret = test_dp_mac_AVX(1.1,2.1,iterations);
        sum[omp_get_thread_num()] = ret;
    }

    double secs = omp_get_wtime() - start;
    uint64 ops = 48 * 1000 * iterations * tds * 4;
    cout << "Seconds = " << secs << endl;
    cout << "FP Ops  = " << ops << endl;
    cout << "FLOPs   = " << ops / secs << endl;

    double out = 0;
    int c = 0;
    while (c < tds){
        out += sum[c++];
    }

    cout << "sum = " << out << endl;
    cout << endl;

    free(sum);
}

int main(){
    //  (threads, iterations)
    test_dp_mac_AVX(8,10000000);

    system("pause");
}

আউটপুট (1 থ্রেড, 10000000 পুনরাবৃত্তি) - ভিজ্যুয়াল স্টুডিও 2010 SP1 - x64 রিলিজের সাথে সংকলিত:

Seconds = 57.4679
FP Ops  = 1920000000000
FLOPs   = 3.34099e+010
sum = 4.45305

তাত্ত্বিক AVX শীর্ষ 8 ফ্লপ * 4.4 গিগাহার্জ = 35.2 GFlops হয় । প্রকৃত 33.4 GFlops হয়

আউটপুট (8 থ্রেড, 10000000 পুনরাবৃত্তি) - ভিজ্যুয়াল স্টুডিও 2010 SP1 - x64 রিলিজের সাথে সংকলিত:

Seconds = 111.119
FP Ops  = 15360000000000
FLOPs   = 1.3823e+011
sum = 35.6244

তাত্ত্বিক AVX শিখর 8 ফ্লপ * 4 কোর * 4.4 গিগাহার্জ = 140.8 GFlops। প্রকৃত 138.2 GFlops হয়

এখন কিছু ব্যাখ্যা জন্য:

কর্মক্ষমতা সমালোচনামূলক অংশটি সম্ভবত অভ্যন্তরীণ লুপ ভিতরে 48 নির্দেশাবলী। আপনি লক্ষ্য করবেন যে এটি 1২ টি নির্দেশাবলীর 4 টি ব্লকের মধ্যে বিভক্ত। এই 12 টি নির্দেশাবলীর প্রতিটি ব্লক একে অপরের থেকে সম্পূর্ণরূপে স্বাধীন - এবং চালানোর জন্য গড় 6 চক্র গ্রহণ করুন।

সুতরাং ইস্যু -তে-ব্যবহারের মধ্যে 1২ নির্দেশাবলী এবং 6 চক্র রয়েছে। গুণমানের বিলম্বিততা 5 চক্র, তাই এটি কেবলমাত্র বিলম্বিত স্টল এড়াতে যথেষ্ট।

তথ্যকে উপরে / নিম্নমুখী করে রাখার জন্য স্বাভাবিককরণ পদক্ষেপটি প্রয়োজন। ডি-কিছুই কোড ধীরে ধীরে ডেটা পরিমাপ বৃদ্ধি / হ্রাস করার কারণে এটি প্রয়োজন।

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

আরও ফলাফল:

  • ইন্টেল কোর আই 7 920 @ 3.5 গিগাহার্জ
  • উইন্ডোজ 7 আলটিমেট x64
  • ভিসুয়াল স্টুডিও 2010 SP1 - x64 রিলিজ

থ্রেড: 1

Seconds = 72.1116
FP Ops  = 960000000000
FLOPs   = 1.33127e+010
sum = 2.22652

তাত্ত্বিক এসএসই শিখর: 4 ফ্লপ * 3.5 গিগাহার্জ = 14.0 GFlops । প্রকৃত 13.3 GFlops হয়

থ্রেড: 8

Seconds = 149.576
FP Ops  = 7680000000000
FLOPs   = 5.13452e+010
sum = 17.8122

তাত্ত্বিক এসএসই শিখর: 4 ফ্লপ * 4 কোর * 3.5 গিগাহার্জ = 56.0 GFlops । বাস্তব 51.3 GFlops হয়

আমার প্রসেসর temps মাল্টি থ্রেডেড রান 76C আঘাত! আপনি যদি এইগুলি চালান, তবে নিশ্চিত হন যে ফলাফলগুলি CPU সিটলিং দ্বারা প্রভাবিত হয় না।

  • ২ x ইন্টেল জিয়ন X5482 হারপারটন @ 3.2 গিগাহার্জ
  • উবুন্টু লিনাক্স 10 x 64
  • GCC 4.5.2 x64 - (-O2 -msse3 -fopenmp)

থ্রেড: 1

Seconds = 78.3357
FP Ops  = 960000000000
FLOPs   = 1.22549e+10
sum = 2.22652

তাত্ত্বিক এসএসই শিখর: 4 ফ্লপ * 3.2 গিগাহার্জ = 12.8 GFlops । প্রকৃতপক্ষে 12.3 GFlops হয়

থ্রেড: 8

Seconds = 78.4733
FP Ops  = 7680000000000
FLOPs   = 9.78676e+10
sum = 17.8122

তাত্ত্বিক এসএসই শিখর: 4 টি ফ্লপ * 8 কোর * 3.2 গিগাহার্জ = 102.4 জিএফলপস । প্রকৃতপক্ষে 97.9 GFlops হয়


শাখা স্পষ্টভাবে শীর্ষ তাত্ত্বিক কর্মক্ষমতা টেকসই থেকে আপনি রাখতে পারেন। আপনি নিজে কিছু লুপ-অরোলিং করলে কী পার্থক্য দেখেন? উদাহরণস্বরূপ, যদি আপনি লুপ পুনরাবৃত্তি হিসাবে অনেকগুলি ops 5 বা 10 বার রাখেন:

for(int i=0; i<loops/5; i++) {
      mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
      sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;
      mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
      sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;
      mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
      sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;
      mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
      sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;
      mul1*=mul; mul2*=mul; mul3*=mul; mul4*=mul; mul5*=mul;
      sum1+=add; sum2+=add; sum3+=add; sum4+=add; sum5+=add;
   }




assembly