loop optimieren



  • Ich muss gestehen, dass ich deinen calc Code nicht ganz blicke.

    (*(pos_i+atom_i))(0)
    

    Ist das ein Funktionsaufruf der etwas macht oder nur die erste Koordinate liest?

    Was vielleicht etwas bringt wäre:

    void calc_pair(const unsigend int i, const unsigned int j, const unsigned int num) {
      for(unsigned int x = 0; x < num; ++x) {
        calc(i,j,x,x);
        for(unsigned int y = x+1; y < num; ++y) {
          calc(i,j,x,y);
          calc(i,j,y,x);
        }
      }  
    }
    

    Vielleicht du kannst dir bei der Berechnung von calc(i,j,x,y) und calc(i,j,y,x) ein paar Zwischenwerte recyclen oder Symmetrien ausnutzen.

    Du sagtest, dass num nur bis 6 gehen kann. Eventuell könntest etwas wie folgt versuchen:

    void calc_pair_num6(const unsigend int i, const unsigned int j) {
      for(unsigned int x = 0; x < 6; ++x) {
        calc(i,j,x,0);
        calc(i,j,x,1);
        calc(i,j,x,2);
        calc(i,j,x,3);
        calc(i,j,x,4);
        calc(i,j,x,5);
      }  
    }
    

    Vielleicht gibt es da wieder Sachen die doppel berechnet werden. Ist natürlich sehr aufwendig zu schreiben und nur schwer wartbar.

    Vielleicht wartet auch einer von deinen Threads mehr als du erwartest und es würde was bringen den Code auf zwei Threads aufzuteilen damit der Scheduler was hat was er dem wartenden Core als Aufgabe geben kann.

    Wenn du mehr Hilfe willst dann poste mal die gesamte Funktion (optimierte und nicht optimierte Version) damit man sieht was da eventuell mehrfach berechnet wird und am besten auch mit ein paar Kommentaren bzw mathematisch und/oder physikalischen Stichwörtern was du da machst.

    Compiler und Prozessor anzugeben wäre auch sinnvoll. Wenn wirklich Micromircooptimierungen her müssen dann geht das nicht ohne das zu wissen.



  • Shade Of Mine schrieb:

    rapso schrieb:

    Woher willst du das ohne profilen wissen? nur vom code her? sorry, nichts gegen dich persoenlich, aber euer "kann man doch"-"kann man nicht" geposte ist irgendwie fuer die katz!

    wissen kann ich es nicht, aber ich sehe einfach deutlich mehr potential es domain-maessig anzugehen.

    du hast 1 sqrt und 1 division hier im code. der rest sind multiplikationen und additionen - da ist code maessig nicht viel zu machen (abgesehen von cache misses/page faults).

    cache misses, Data hazards, pipeline switching. der code stallt vermutlich die meiste zeit ueber. ich wuerde nicht sagen dass da nicht viel zu machen ist.

    was dagegen sehr viel bringen wuerde, waere den code umzustrukturieren - sofern moeglich.

    was dich vermutlich da verwirrt hat ist, dass ich zwischen code optimierungen und domain optimierungen trenne. ein

    c=sqrt(a);

    ist code maessig nicht optimierbar. domain maesig aber schon, wenn naemlich inv_sqrt reicht oder man gar mit a*a weiter reichnen kann ohne wurzel zu ziehen - das sind ebenfalls optimierungen - aber in der domain und nicht im code.

    ich denke schon dass das codemaessig optimierbar ist wenn du es im context siehst. die instruktion kann 5cycle oder ca 540cycles (auf nem AMD64) verbrauchen.
    genau aus diesem grund sag ich die ganze zeit, das man sich mit nem profiler die kosten pro zeile anschauen muss. einfach nur aufgrund des sources kann man nur raten. klar findet mal ein blindes huhn das korn, aber es wird nicht wissen, ob und wieviele noch rumliegen und schon garnicht wo.

    er koennte den code auch posten und sagen 'er compiliert nicht', da wuerden sicher auch alle sagen, dass er den compiler output zeigen soll. ich wunder mich immer wieso das niemand fuers optimieren fragt, dass man ein wenig profiling daten sieht.
    (und nein, das ist auch nichts gegen dich 😉 )



  • freiefahrt schrieb:

    Das einzige was bei dem Code noch groß was bringen könnte, wäre ein einfacheres und schneleres sqtr zu verwenden, aber dann wird die Sache wahrscheinlich zu ungenau

    Ja zu ungenau.

    Shade Of Mine schrieb:

    Hast du die Idee mit den page faults/cache misses mal verfolgt? Du greifst auf viel speicher zu - ist das alles notwendig? wie sind die datenstrukturen aufgebaut?

    Notwendig ist es leider. Ich kann die Arrays auch nicht wirklich umsortieren damit sie cache optimaler waeren... Ich hab doch den valgrind output zu den cache misses gepostet (callgrind) aber ich finds sehr schwierig da was zu interpretieren...

    Shade Of Mine schrieb:

    Die 64 Cores lastest du aber schon zu 100% aus, oder?

    Nein. Andahls Law und Load Balancing Probleme. Und die wird man so einfach auch nicht los.

    abc.w schrieb:

    Man könnte bei dem Code vielleicht noch mal versuchen, die ganzen inneren Schleifen loszuwerden.

    Das ueberlasse ich dem Compiler. Der compiler optimiert mit den Profiling Informationen und kann also die Loops unrollen wenn er will. Bringen tut das aber nicht viel.

    Ben04 schrieb:

    Ich muss gestehen, dass ich deinen calc Code nicht ganz blicke.

    (*(pos_i+atom_i))(0)
    

    Ist das ein Funktionsaufruf der etwas macht oder nur die erste Koordinate liest?

    ist ein bisschen umstaendlich geschrieben aber es ist im Prinzip nur das lesen der ersten Koordinate. normal geht das so:

    conf.current().pos(i)(0)}}}
    

    . Durch die Pointer sieht das dann halt ein bisschen haesslicher aus...

    Ben04 schrieb:

    Was vielleicht etwas bringt wäre:

    void calc_pair(const unsigend int i, const unsigned int j, const unsigned int num) {
      for(unsigned int x = 0; x < num; ++x) {
        calc(i,j,x,x);
        for(unsigned int y = x+1; y < num; ++y) {
          calc(i,j,x,y);
          calc(i,j,y,x);
        }
      }  
    }
    

    Vielleicht du kannst dir bei der Berechnung von calc(i,j,x,y) und calc(i,j,y,x) ein paar Zwischenwerte recyclen oder Symmetrien ausnutzen.

    Nein leider kann man da keine Zwischenwerte recyclen. So hast du schlussendlich was aehnliches wie das von mir gepostete Beispiel... bringt nix.

    Ben04 schrieb:

    Du sagtest, dass num nur bis 6 gehen kann. Eventuell könntest etwas wie folgt versuchen:

    void calc_pair_num6(const unsigend int i, const unsigned int j) {
      for(unsigned int x = 0; x < 6; ++x) {
        calc(i,j,x,0);
        calc(i,j,x,1);
        calc(i,j,x,2);
        calc(i,j,x,3);
        calc(i,j,x,4);
        calc(i,j,x,5);
      }  
    }
    

    Vielleicht gibt es da wieder Sachen die doppel berechnet werden. Ist natürlich sehr aufwendig zu schreiben und nur schwer wartbar.

    Ja so sehe ich das auch. Sowas will niemand warten muessen. Zudem kann das ganze bis 1000 gehen wenn jemand will... Also 6 war nur eine praktische Grenze weil mir grad kein gaengiges Loesungsmittel mit mehr als 6 Atomen einfiel. Aber im Prinzip waer das moeglich.

    Ben04 schrieb:

    Vielleicht wartet auch einer von deinen Threads mehr als du erwartest und es würde was bringen den Code auf zwei Threads aufzuteilen damit der Scheduler was hat was er dem wartenden Core als Aufgabe geben kann.

    Ich weiss nicht genau wie die OpenMP Implementierung skaliert. Ich hab mich nur mit MPI beschaeftigt und da waer das nicht trivial zu implementieren. Aber daran gedacht hab ich auch schon 🤡

    Ben04 schrieb:

    Wenn du mehr Hilfe willst dann poste mal die gesamte Funktion (optimierte und nicht optimierte Version) damit man sieht was da eventuell mehrfach berechnet wird und am besten auch mit ein paar Kommentaren bzw mathematisch und/oder physikalischen Stichwörtern was du da machst.

    Compiler und Prozessor anzugeben wäre auch sinnvoll. Wenn wirklich Micromircooptimierungen her müssen dann geht das nicht ohne das zu wissen.

    g++ 4.2.3, Linux, -O3 -funroll-loops -fno-gcse

    $ cat /proc/cpuinfo
    processor       : 0
    vendor_id       : AuthenticAMD
    cpu family      : 15
    model           : 67
    model name      : AMD Processor model unknown
    stepping        : 3
    cpu MHz         : 1000.000
    cache size      : 1024 KB
    physical id     : 0
    siblings        : 2
    core id         : 0
    cpu cores       : 2
    fpu             : yes
    fpu_exception   : yes
    cpuid level     : 1
    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 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow rep_good pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy
    bogomips        : 2011.93
    TLB size        : 1024 4K pages
    clflush size    : 64
    cache_alignment : 64
    address sizes   : 40 bits physical, 48 bits virtual
    power management: ts fid vid ttp tm stc
    
    processor       : 1
    vendor_id       : AuthenticAMD
    cpu family      : 15
    model           : 67
    model name      : AMD Processor model unknown
    stepping        : 3
    cpu MHz         : 1000.000
    cache size      : 1024 KB
    physical id     : 0
    siblings        : 2
    core id         : 1
    cpu cores       : 2
    fpu             : yes
    fpu_exception   : yes
    cpuid level     : 1
    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 ht syscall nx mmxext fxsr_opt rdtscp lm 3dnowext 3dnow rep_good pni cx16 lahf_lm cmp_legacy svm extapic cr8_legacy
    bogomips        : 2011.93
    TLB size        : 1024 4K pages
    clflush size    : 64
    cache_alignment : 64
    address sizes   : 40 bits physical, 48 bits virtual
    power management: ts fid vid ttp tm stc
    

    aufrufende Funktion (stub)

    // rufe schnellen generischen lsm loop auf.
      if (sim.param().force.special_loop == simulation::special_loop_generic) {
        const unsigned int num_solvent_atoms = topo.solvent(0).num_atoms();
        // Parameter vorberechnen (Lennard Jones Paare und Ladungsprodukt.
        // prepare parameters
        const unsigned int num_param = num_solvent_atoms * num_solvent_atoms;
        typename interaction::Nonbonded_Innerloop<t_interaction_spec>::solvent_pair_parameter pair_parameter[num_param];
    
        unsigned int param = 0;
        // loop ueber atompaare
        for(unsigned int atom_i = 0; atom_i < num_solvent_atoms; ++atom_i) {
          for(unsigned int atom_j = 0; atom_j < num_solvent_atoms; ++atom_j, ++param) {
            assert(param < num_param);
            DEBUG(10, "\tsolvent pair parameter: " << param);
    
            // hole LJ Parameter (doubles)
            const lj_parameter_struct & lj = 
    	  innerloop.param()->lj_parameter(topo.solvent(0).atom(atom_i).iac, topo.solvent(0).atom(atom_j).iac);
            pair_parameter[param].c12 = lj.c12;
            pair_parameter[param].c6 = lj.c6;
    
            // berechne Ladungsprodukt.
            pair_parameter[param].q = math::four_pi_eps_i * 
                    topo.solvent(0).atom(atom_i).charge * 
                    topo.solvent(0).atom(atom_j).charge;
    
            DEBUG(10, "\t\tc12: " << pair_parameter[param].c12 << " c6: " << 
                    pair_parameter[param].c6 << " q: " << pair_parameter[param].q);
          }
        }
    
        // use num_solvent_atoms-th atom (first of solvent molecule i)
        // loop ueber alle Molekuelpaare
        // liste enthaelt alle atompaare und deshalb werden immer num_solvent_atoms - 1 atome uebersprungen
        for (; i < size_i; i += num_solvent_atoms) {
          for (j_it = pairlist_solvent[i].begin(),
                  j_to = pairlist_solvent[i].end();
                  j_it != j_to;
                  j_it += num_solvent_atoms) { // use num_solvent_atoms-th atom (first of solvent molecule j)
    
            DEBUG(10, "\tsolvent_nonbonded_interaction: i " << i << " j " << *j_it);
            // Aufruf der zu optimierenden Funktion
            innerloop.solvent_innerloop(topo, pair_parameter, conf, num_solvent_atoms, i, *j_it, storage, periodicity);
          }
        }
      }
    

    Die zu optimierende Funktion:

    template<typename t_nonbonded_spec>
    inline void 
    interaction::Nonbonded_Innerloop<t_nonbonded_spec>::solvent_innerloop
    (
     topology::Topology & topo,
     interaction::Nonbonded_Innerloop<t_nonbonded_spec>::solvent_pair_parameter * pair_parameter,
     configuration::Configuration & conf,
     const unsigned int num_solvent_atoms,
     const int i,
     const int j,
     Storage & storage,
     math::Periodicity<t_nonbonded_spec::boundary_type> const & periodicity
     )
    {
      math::Vec r;
    
      // only one energy group
      // bestimme wo in array die Energie gespeichert werden soll
      const int egroup = topo.atom_energy_group(topo.num_solute_atoms());
      DEBUG(8, "\tspc pair\t" << i << "\t" << j << " egroup " << egroup);
    
      // hole pointer auf position (const) und kraft von atom i
      math::Vec const * const pos_i = &conf.current().pos(i);
      math::Vec * const force_i = &storage.force(i);
    
      // dito fuer atom j
      math::Vec const * const pos_j = &conf.current().pos(j);
      math::Vec * const force_j = &storage.force(j);
      DEBUG(9, "i = " << i << " j = " << j);    
    
      // first atom vs. first atom
      // berrechne naechstes periodisches Abild des Paars i, j (== Abstand zwischen atom i und j wird in vektor r gespeichert)
      periodicity.nearest_image(*pos_i, *pos_j, r);
      // berechne Periodizitaetsverschiebung fuer das Paar die dann auf 
      // alle atom Paare der zwei Molekuele angewandt werden kann.
      const double tx = r(0) - (*pos_i)(0) + (*pos_j)(0);
      const double ty = r(1) - (*pos_i)(1) + (*pos_j)(1);
      const double tz = r(2) - (*pos_i)(2) + (*pos_j)(2);
    
      // speicher fuer die energie: Lennard-Jones und Coulombe
      double e_lj = 0.0, e_crf = 0.0;
    
      // loop over atom pairs
      for(unsigned int param = 0, atom_i = 0; atom_i < num_solvent_atoms; ++atom_i) {
        // berrechne verschiebung des atoms i
        const double xi = (*(pos_i+atom_i))(0) + tx;
        const double yi = (*(pos_i+atom_i))(1) + ty;
        const double zi = (*(pos_i+atom_i))(2) + tz;
        for(unsigned int atom_j = 0; atom_j < num_solvent_atoms; ++atom_j, ++param) {
          DEBUG(15, "\tatoms: i: " << atom_i << " j: " << atom_j);
          // berechne naechstes periodisches Abbild von i-j mit hilfe verschiebung von atom i
          const double x = xi - (*(pos_j+atom_j))(0);
          const double y = yi - (*(pos_j+atom_j))(1);
          const double z = zi - (*(pos_j+atom_j))(2);
    
          // rechne abstand im quadrat
          const double r2 = x * x + y * y + z * z;
          DEBUG(15, "\tr2: " << r2);
          assert(r2 != 0.0);
          // inverser abstand im quadrat
          const double r2i = 1.0 / r2;
          // inverser abstand
          const double ri = sqrt(r2i);
          // inverser abstand hoch 6
          const double dist6i = r2i * r2i * r2i;
          // und multipliziert mit der konstante C12
          const double dist6i_c12 = pair_parameter[param].c12 * dist6i;
    
          // berrechne (und addiere) LJ energie: (r^-6 * c12 - c6) * r^-6
          e_lj += (dist6i_c12 - pair_parameter[param].c6) * dist6i;
          // berechne Coulombe und Fischer Reaction Field :
          // qi * qj / (4 Pi Eps0) * (r^-1 - b0 * r^2/cut^3 - cut^-1)
          e_crf += pair_parameter[param].q * (ri - m_crf_2cut3i * r2 - m_crf_cut);
    
          // Kraft: Ableitung nach r der beiden Energien
          const double f = (dist6i_c12 + dist6i_c12 - pair_parameter[param].c6) * 6.0 * dist6i * r2i +
                  pair_parameter[param].q * (ri * r2i + m_crf_cut3i);
    
          // kraft als Vektor
          const double fx = f * x;
          const double fy = f * y;
          const double fz = f * z;
    
          // abspeichern der Kraft: actio = reactio
          (*(force_i+atom_i))(0) += fx;
          (*(force_j+atom_j))(0) -= fx;
          (*(force_i+atom_i))(1) += fy;
          (*(force_j+atom_j))(1) -= fy;
          (*(force_i+atom_i))(2) += fz;
          (*(force_j+atom_j))(2) -= fz;
    
          // berechnung des virial tensors: r (x) f
          // wobei (x) das inner product ist.
          storage.virial_tensor(0, 0) += x * fx;
          storage.virial_tensor(0, 1) += x * fy;
          storage.virial_tensor(0, 2) += x * fz;
          storage.virial_tensor(1, 0) += y * fx;
          storage.virial_tensor(1, 1) += y * fy;
          storage.virial_tensor(1, 2) += y * fz;
          storage.virial_tensor(2, 0) += z * fx;
          storage.virial_tensor(2, 1) += z * fy;
          storage.virial_tensor(2, 2) += z * fz;
        }
      }
      // abspeichern der Energien in den entsprechenden Energiegruppen.
      storage.energies.lj_energy[egroup][egroup] += e_lj;
      storage.energies.crf_energy[egroup][egroup] += e_crf;
    }
    

    Ich hoffe damit ist die Physik des Problems ein bisschen klarer...

    LG
    Neph



  • Hallo

    Ich hab die cache misses ja gar nicht gepostet... wahrscheinlich hab ich nur von getraeumt 🙄

    Events recorded abbreviations are:
        Ir : I cache reads (ie. instructions executed) 
        I1mr: I1 cache read misses 
        I2mr: L2 cache instruction read misses 
        Dr : D cache reads (ie. memory reads) 
        D1mr: D1 cache read misses 
        D2mr: L2 cache data read misses 
        Dw : D cache writes (ie. memory writes) 
        D1mw: D1 cache write misses 
        D2mw: L2 cache data write misses
    
    --------------------------------------------------------------------------------
    Profile data file 'callgrind.out.7741' (creator: callgrind-3.3.0-Debian)
    --------------------------------------------------------------------------------
    I1 cache: 65536 B, 64 B, 2-way associative
    D1 cache: 65536 B, 64 B, 2-way associative
    L2 cache: 1048576 B, 64 B, 8-way associative
    Timerange: Basic block 0 - 3810525588
    Trigger: Program termination
    Profiled target:  /home/nschmid/gromos/svn/gromosXX/x86_64/bin/md @f md.inp (PID 7741, part 1)
    Events recorded:  Ir Dr Dw I1mr D1mr D1mw I2mr D2mr D2mw
    Events shown:     Ir Dr Dw I1mr D1mr D1mw I2mr D2mr D2mw
    Event sort order: Ir Dr Dw I1mr D1mr D1mw I2mr D2mr D2mw
    Thresholds:       99 0 0 0 0 0 0 0 0
    Include dirs:     
    User annotated:   
    Auto-annotation:  on
    
    --------------------------------------------------------------------------------
                Ir             Dr            Dw    I1mr        D1mr       D1mw    I2mr       D2mr       D2mw 
    --------------------------------------------------------------------------------
    59,696,737,439 20,453,280,842 8,831,821,039 601,767 200,229,353 40,511,699 168,776 31,171,410 20,088,219  PROGRAM TOTALS
    
    --------------------------------------------------------------------------------
    -- Auto-annotated source: /home/nschmid/gromos/svn/gromosXX/x86_64/src/interaction/../../../src/interaction/nonbonded/interaction/solvent_innerloop.cc
    --------------------------------------------------------------------------------
               Ir          Dr          Dw I1mr       D1mr  D1mw I2mr    D2mr D2mw 
    
    -- line 27 ----------------------------------------
                .           .           .    .          .     .    .       .    .    math::Vec * const force_i = &storage.force(i);
                .           .           .    .          .     .    .       .    .  
                .           .           .    .          .     .    .       .    .    math::Vec const * const pos_j = &conf.current().pos(j);
                .           .           .    .          .     .    .       .    .    math::Vec * const force_j = &storage.force(j);
                .           .           .    .          .     .    .       .    .    DEBUG(9, "i = " << i << " j = " << j);    
                .           .           .    .          .     .    .       .    .  
                .           .           .    .          .     .    .       .    .    // first atom vs. first atom
                .           .           .    .          .     .    .       .    .    periodicity.nearest_image(*pos_i, *pos_j, r);
      168,138,585 168,138,585           0    0      1,421     0    0       2    .    const double tx = r(0) - (*pos_i)(0) + (*pos_j)(0);
      100,883,151 100,883,151           0    0        184     .    .       .    .    const double ty = r(1) - (*pos_i)(1) + (*pos_j)(1);
      100,883,151 100,883,151           0  100         90     0  100       .    .    const double tz = r(2) - (*pos_i)(2) + (*pos_j)(2);
                .           .           .    .          .     .    .       .    .    
                .           .           .    .          .     .    .       .    .    double e_lj = 0.0, e_crf = 0.0;
                .           .           .    .          .     .    .       .    .    
                .           .           .    .          .     .    .       .    .    // loop over atom pairs
      638,926,623 100,883,151 168,138,585  100          0   272  100       0   10    for(unsigned int param = 0, atom_i = 0; atom_i < num_solvent_atoms; ++atom_i) {
      403,532,604 201,766,302 100,883,151  100    134,553     0  100   1,613    .      const double xi = (*(pos_i+atom_i))(0) + tx;
      302,649,453 100,883,151 100,883,151    0    123,708     0    0   1,448    .      const double yi = (*(pos_i+atom_i))(1) + ty;
      706,182,057 201,766,302 201,766,302    0    125,634     0    0   1,000    .      const double zi = (*(pos_i+atom_i))(2) + tz;
    1,614,130,416 302,649,453 403,532,604    0          0 4,949    0       0  746      for(unsigned int atom_j = 0; atom_j < num_solvent_atoms; ++atom_j, ++param) {
                .           .           .    .          .     .    .       .    .        DEBUG(15, "\tatoms: i: " << atom_i << " j: " << atom_j);
      907,948,359 907,948,359           0    0  6,537,647     0    0 171,849    .        const double x = xi - (*(pos_j+atom_j))(0);
      605,298,906 605,298,906           0  100  6,587,194     0  100 173,798    .        const double y = yi - (*(pos_j+atom_j))(1);
      605,298,906 605,298,906           0    0  6,629,181     0    0 171,226    .        const double z = zi - (*(pos_j+atom_j))(2);
                .           .           .    .          .     .    .       .    .        
    2,421,195,624           0           0  100          0     0  100       .    .        const double r2 = x * x + y * y + z * z;
                .           .           .    .          .     .    .       .    .        DEBUG(15, "\tr2: " << r2);
                .           .           .    .          .     .    .       .    .        assert(r2 != 0.0);
      605,298,906 302,649,453           0    0      3,836     0    0     184    .        const double r2i = 1.0 / r2;
    1,210,597,812           .           .    .          .     .    .       .    .        const double ri = sqrt(r2i);
      907,948,359           0           0  100          0     0  100       .    .        const double dist6i = r2i * r2i * r2i;
    1,513,247,265 605,298,906           0  100      3,413     0  100     584    .        const double dist6i_c12 = pair_parameter[param].c12 * dist6i;
                .           .           .    .          .     .    .       .    .  
    1,815,896,718 302,649,453           0    0     16,331     0    0   1,348    .        e_lj += (dist6i_c12 - pair_parameter[param].c6) * dist6i;
    2,118,546,171 907,948,359           0  100      8,117     0  100   1,083    .        e_crf += pair_parameter[param].q * (ri - m_crf_2cut3i * r2 - m_crf_cut);
                .           .           .    .          .     .    .       .    .  
                .           .           .    .          .     .    .       .    .        const double f = (dist6i_c12 + dist6i_c12 - pair_parameter[param].c6) * 6.0 * dist6i * r2i +
    2,723,845,077 605,298,906           0    2      5,440     0    2     684    .                pair_parameter[param].q * (ri * r2i + m_crf_cut3i);
                .           .           .    .          .     .    .       .    .  
      605,298,906           .           .    .          .     .    .       .    .        const double fx = f * x;
      605,298,906           .           .    .          .     .    .       .    .        const double fy = f * y;
      302,649,453           .           .    .          .     .    .       .    .        const double fz = f * z;
                .           .           .    .          .     .    .       .    .  
    1,210,597,812 605,298,906 302,649,453    0    262,421     0    0   3,871    .        (*(force_i+atom_i))(0) += fx;
      907,948,359 302,649,453 302,649,453  100 33,260,417     0  100 971,453    .        (*(force_j+atom_j))(0) -= fx;
      907,948,359 302,649,453 302,649,453    0    193,492     0    0   4,409    .        (*(force_i+atom_i))(1) += fy;
      907,948,359 302,649,453 302,649,453    0 10,177,012     0    0 365,584    .        (*(force_j+atom_j))(1) -= fy;
      907,948,359 302,649,453 302,649,453    0    186,904     0    0   2,404    .        (*(force_i+atom_i))(2) += fz;
      907,948,359 302,649,453 302,649,453    0  9,921,693     0    0 255,507    .        (*(force_j+atom_j))(2) -= fz;
                .           .           .    .          .     .    .       .    .  
    1,210,597,812 302,649,453 302,649,453  200          0     0  200       .    .        storage.virial_tensor(0, 0) += x * fx;
    1,210,597,812 302,649,453 302,649,453    .          .     .    .       .    .        storage.virial_tensor(0, 1) += x * fy;
      907,948,359 302,649,453 302,649,453    0      5,199     0    0     685    .        storage.virial_tensor(0, 2) += x * fz;
    1,210,597,812 302,649,453 302,649,453  100          0     0  100       .    .        storage.virial_tensor(1, 0) += y * fx;
    1,210,597,812 302,649,453 302,649,453    .          .     .    .       .    .        storage.virial_tensor(1, 1) += y * fy;
      907,948,359 302,649,453 302,649,453  100          0     0  100       .    .        storage.virial_tensor(1, 2) += y * fz;
      907,948,359 302,649,453 302,649,453    0      2,577     0    0     259    .        storage.virial_tensor(2, 0) += z * fx;
      907,948,359 302,649,453 302,649,453  100          0     0  100       .    .        storage.virial_tensor(2, 1) += z * fy;
      907,948,359 302,649,453 302,649,453    0      1,503     0    0     293    .        storage.virial_tensor(2, 2) += z * fz;
                .           .           .    .          .     .    .       .    .      }
                .           .           .    .          .     .    .       .    .    }
      100,883,151  67,255,434  33,627,717    0    283,981     0    0     261    .    storage.energies.lj_energy[egroup][egroup] += e_lj;
      168,138,585 100,883,151  67,255,434    0      4,627     0    0   1,017    .    storage.energies.crf_energy[egroup][egroup] += e_crf;
                .           .           .    .          .     .    .       .    .  }
                .           .           .    .          .     .    .       .    .  
                .           .           .    .          .     .    .       .    .  
                .           .           .    .          .     .    .       .    .  template<typename t_nonbonded_spec>
                .           .           .    .          .     .    .       .    .  inline void 
                .           .           .    .          .     .    .       .    .  interaction::Nonbonded_Innerloop<t_nonbonded_spec>::spc_innerloop
                .           .           .    .          .     .    .       .    .  (
                .           .           .    .          .     .    .       .    .   topology::Topology & topo,
    -- line 97 ----------------------------------------
    

    Hier sind die Resultate:



  • Nephelauxetic schrieb:

    Das ueberlasse ich dem Compiler. Der compiler optimiert mit den Profiling Informationen und kann also die Loops unrollen wenn er will. Bringen tut das aber nicht viel.

    Ich dachte, Loops unrollen macht er nur wenn er kann - ist aber egal. Ich denke auch, das bringt wirklich nicht viel, 20E6 Takte hört sich nach viel an, ist aber bei einer 1 GHz CPU nur etwa 20 ms. 1 fps, hehe.

    Nephelauxetic schrieb:

    g++ 4.2.3, Linux, -O3 -funroll-loops -fno-gcse

    Man könnte noch folgende Flags ausprobieren:

    -ffast-math -fno-rtti
    

    Ansonsten, ich geb auf 😉 Ich lege großen Wert auf Lesbarkeit und "Debugbarkeit" eines Programms...



  • Ich weiss nicht genau wie die OpenMP Implementierung skaliert. Ich hab mich nur mit MPI beschaeftigt und da waer das nicht trivial zu implementieren. Aber daran gedacht hab ich auch schon

    Aus

    #pragma omp parallel
    {
      #pragma omp for
      for(int i=0; i<n; ++i){
        ...
      }
    }
    

    wird meistens etwas wie:

    #pragma omp parallel
    {
      const int begin = n*omp_get_thread_num() / omp_get_num_threads();
      const int end = n*(omp_get_thread_num()+1) / omp_get_num_threads();
      for(int i=begin; i<end; ++i){
        ...
      }
    }
    

    Skaliert also recht gut mit n wenn die Laufzeit von ... nicht von i abhängt. Bringt aber nur etwas wenn die Anzahl der Cores <<< n

    Ein #pragma parallel for num_threads(2) ist ja schnell vor eine Schleife geschrieben von daher schadet testen wahrscheinlich nicht. Ich glaub aber nicht wirklich dran, dass das bei 2 Cores viel bringt und bei vielen Cores bin ich mir auch nicht sicher.

    Ansonsten sehe ich leider kein Potential mehr in dem Code, sorry.



  • Hast du evtl. ein vollständiges Testprogramm für diese Funktion? Dann könntest du das in Gänze posten (http://rafb.net/paste/ & co.) damit wir eigene Experimente anstellen können. Bloßes Draufschauen hilft bei solchen Microoptimierungen leider oft nicht.



  • OK, ich habe schon ewig nicht mehr mit der gcc rumgespielt, aber wenn ich mich recht entsinne ist es ganz sinnvoll -march zu verwenden.



  • Wenn ihr aus dem Code noch mehr als 10% rausholt, bekommt ihr nen virtuellen Keks.



  • camper schrieb:

    Hast du evtl. ein vollständiges Testprogramm für diese Funktion? Dann könntest du das in Gänze posten (http://rafb.net/paste/ & co.) damit wir eigene Experimente anstellen können. Bloßes Draufschauen hilft bei solchen Microoptimierungen leider oft nicht.

    Ich kann dir den Code per Mail schicken. Das Problem ist das du ganze Bibliothek (fuer Objekte wie topo, conf etc.) einfach riesig ist. Zudem wuerd ich den Code in dieser Form nur ungern ins Netz stellen.

    Helium schrieb:

    aber wenn ich mich recht entsinne ist es ganz sinnvoll -march zu verwenden.

    Ja in der Regel schon. Hier bringt es nichts.

    Ben04 schrieb:

    Skaliert also recht gut mit n wenn die Laufzeit von ... nicht von i abhängt.

    Ausser in den trivialsten Faellen hat man das aber nie.

    LG
    Neph



  • Das hat man sogar erstaunlich oft. Oder war das nur auf dein Programm bezogen?



  • Walli schrieb:

    Das hat man sogar erstaunlich oft. Oder war das nur auf dein Programm bezogen?

    Nein allgemein. Kann sein dass ich aber bis jetzt einfach nur Pech hatte dass ich noch nie eines dieser schoenen Daten-parallelen Lehrbuchbeispiele in der Praxis hatte. 😃

    Aber das ist hier OT.



  • abc.w schrieb:

    -ffast-math -fno-rtti
    

    Das bringt fast 10% auf dem ganzen Programm 🙂 Dies liegt aber am fast-math. no-rtti hat keinen Effekt.



  • Nephelauxetic schrieb:

    Dies liegt aber am fast-math. no-rtti hat keinen Effekt.

    -fno-rtti durfte sich laut gcc manual u.U. auf den Speicherverbrauch auswirken. -ffast-math bewirkt u.a. dass bei Berechnungen keine bedingten Sprungbefehle generiert werden. Damit spart man wahrscheinlich CPU-Takte. Optimal wäre, überhaupt keine Sprünge im Code zu haben, keine Schleifen, keine if-s, keine switches, am besten nur lauter nops 😉 Vielleicht lohnt es sich doch, die verschachtelte Schleife in deiner Funktion irgendwie zu beseitigen. Das ist halt nur problematisch, weil nun ja, Algorithmus umdenken und so was, ist nicht trivial , kompliziert und man muss sich dabei anstrengen 😉 Aber damit hättest Du dann num_solvent_atoms mal was weiss ich wieviele Takte noch mal gespart. Und mit Sicherheit deinen Schein oder Prüfungsnote bekommen.



  • pleevsevsi@farifluset.mailexpire.com

    Die Wirkung der Optimierungsoptionen ist leider recht unzuverlässig, interessant vielleicht auch Acovea

    Evtl bringt es hier etwas, ein wenig mit --param max-reload-search-insns=x herumzuspielen mit großen x (geht wahrscheinlich auf Kosten der Compilierzeit). In der Regel basiert die Suche nach den besten Optionen auf trial&error.



  • Und konnte noch viel aus dem Code rausgeholt werden?



  • by the hammer


Anmelden zum Antworten