Ещё раз про latency

Сперва чтиво http://lwn.net/Articles/250967/

Продолжая начатую тему замечу, что на PC latency тоже можно и нужно бороть. Prefetch есть, и он тоже реально работает. Правильная (линейная) организация данных работает ещё лучше, потому что процессор умеет предсказывать и префетчить сам. В статье, впрочем, всё гораздо подробнее.

Предложеный пример всё как-то иллюстрирует. Замечу, что в реальности эффект будет значительно большим потому, что L2 будет сбрасываться между итерациями. На внятных платформах ещё и рисовать можно прямо из того-же буфера. Накодить тоже можно значительно аккуратнее, развернуть циклы итп; мой lame excuse это банальный цейтнот.

Измеряем, как всегда, попугаев. В первый раз тупо, двусвязный список. Добавили-удалили. Во второй раз - всё тоже самое, только с префетчем. В третий - циклический буфер.

Циклический буфер вообщем-то реализует идею о том, что почти весь партикл изменяется. Т-е если писать линейно, то всё равно куда писать - latency спрячется. Аналогично latency спрячется если линейно читать. На PPU (и эквиваленте) prefetch понадобится и на циклическом буфере.

Результаты

naive 11905 msec
naive with prefetch 10918 msec
ring 10107 msec

  1. const int TotalFrames = 1024*64;
  2. const int TotalNumParticles = 16384;
  3. const int TotalNumParticlesMask = TotalNumParticles - 1;
  4.  
  5. //////////////////////////////////////////////////////////////////////////
  6.  
  7. __forceinline float frand ( void )
  8. {
  9.         return float(double(rand())/double(RAND_MAX));
  10. }
  11.  
  12. __m128 frandv ( void )
  13. {
  14.         return _mm_set_ps ( frand(), frand(), frand(), frand() );
  15. }
  16.  
  17. //////////////////////////////////////////////////////////////////////////
  18.  
  19. template <typename TT>
  20. __forceinline void Attach ( TT * & pHead, TT * pPointer )
  21. {
  22.         TT * pLink = pPointer;
  23.         if ( pHead )
  24.                 pHead->prev = pLink;
  25.         pLink->next = pHead;
  26.         pLink->prev = NULL;
  27.         pHead = pLink;
  28. }
  29.  
  30.  
  31. template <typename TT>
  32. __forceinline void Detach ( TT * & pHead, TT * pPointer )
  33. {
  34.         TT * pLink = pPointer;
  35.         if ( pLink->prev )
  36.                 pLink->prev->next = pLink->next;
  37.         else
  38.                 pHead = pLink->next;
  39.         if ( pLink->next )
  40.                 pLink->next->prev = pLink->prev;
  41.         pLink->prev = NULL;
  42.         pLink->next = NULL;
  43. }
  44.  
  45.  
  46. //////////////////////////////////////////////////////////////////////////
  47.  
  48. struct NaiveParticle
  49. {
  50.         __m128  position;
  51.         __m128  velocity;
  52.         __m128  color;
  53.         NaiveParticle * next, * prev;
  54.         int             lifetime;
  55.         int             foobar;
  56. };
  57.  
  58. NaiveParticle   particles[TotalNumParticles];
  59.  
  60. //////////////////////////////////////////////////////////////////////////
  61.  
  62. template <bool isPrefetch>
  63. class NaivePool
  64. {
  65. public:
  66.         NaivePool ( void )
  67.         {
  68.                 work = NULL;
  69.                 empty = particles;
  70.                 for ( int i=0; i<TotalNumParticles; i++ )
  71.                 {
  72.                         particles[i].next = particles + i + 1;
  73.                         particles[i].prev = particles + i - 1;
  74.                 }
  75.                 particles[0].prev = NULL;
  76.                 particles[TotalNumParticles-1].next = NULL;
  77.         }
  78.  
  79.         void Seed ( int numParticles, int lifetime )
  80.         {
  81.                 while ( numParticles && empty )
  82.                 {
  83.                         numParticles ;
  84.                         NaiveParticle * pp = empty;
  85.                         Detach ( empty, pp );
  86.                         Attach ( work, pp );
  87.                         pp->position = frandv();
  88.                         pp->velocity = frandv();
  89.                         pp->color = frandv();
  90.                         pp->lifetime = (rand() % lifetime) + 1;
  91.                         nParticles ++;
  92.                 }
  93.         }
  94.  
  95.         void Update ( __m128 acceleration, __m128 colorVelocity )
  96.         {
  97.                 NaiveParticle * pp = work;
  98.                 while ( pp )
  99.                 {
  100.                         NaiveParticle * np = pp->next;
  101.                         if ( isPrefetch )
  102.                                 _mm_prefetch ( (const char *) np, _MM_HINT_T0 );
  103.  
  104.                         pp->position = _mm_add_ps ( pp->position, pp->velocity );
  105.                         pp->velocity = _mm_add_ps ( pp->velocity, acceleration );
  106.                         pp->color = _mm_add_ps ( pp->color, colorVelocity );
  107.                         pp->lifetime ;
  108.                         if ( pp->lifetime==0 )
  109.                         {
  110.                                 Detach ( work, pp );
  111.                                 Attach ( empty, pp );
  112.                                 nParticles ;
  113.                         }
  114.                         pp = np;
  115.                 }
  116.         }
  117.  
  118. public:
  119.         int                             nParticles;
  120.  
  121. protected:
  122.         NaiveParticle * work;
  123.         NaiveParticle * empty;
  124. };
  125.  
  126. //////////////////////////////////////////////////////////////////////////
  127.  
  128. class RingPool
  129. {
  130.  
  131. public:
  132.         RingPool ( void )
  133.         {
  134.                 i0 = i1 = 0;
  135.                 nParticles = 0;
  136.         }
  137.  
  138.         void Seed ( int numParticles, int lifetime )
  139.         {
  140.                 while ( numParticles && nParticles<(TotalNumParticles-1) )
  141.                 {
  142.                         numParticles ;
  143.                         NaiveParticle * pp = particles + i1;
  144.                         i1 = (i1 + 1) & TotalNumParticlesMask;
  145.                         pp->position = frandv();
  146.                         pp->velocity = frandv();
  147.                         pp->color = frandv();
  148.                         pp->lifetime = (rand() % lifetime) + 1;
  149.                         nParticles ++;
  150.                 }
  151.         }
  152.  
  153.         void Update ( __m128 acceleration, __m128 colorVelocity )
  154.         {
  155.                 for ( int nUpdate = nParticles; nUpdate; nUpdate )
  156.                 {
  157.                         NaiveParticle * pfr = particles + i0;
  158.                         NaiveParticle * pto = particles + i1;
  159.                         i0 = (i0 + 1) & TotalNumParticlesMask;
  160.                         int lifetime  = pfr->lifetime - 1;
  161.                         if ( lifetime )
  162.                         {
  163.                                 pto->position = _mm_add_ps ( pfr->position, pfr->velocity );
  164.                                 pto->velocity = _mm_add_ps ( pfr->velocity, acceleration );
  165.                                 pto->color = _mm_add_ps ( pfr->color, colorVelocity );
  166.                                 pto->lifetime = lifetime;
  167.                                 i1 = (i1 + 1) & TotalNumParticlesMask;
  168.                         }
  169.                         else
  170.                                 nParticles ;
  171.                 }
  172.         }
  173.  
  174. public:
  175.         int                             nParticles;
  176.         int                             i0, i1;
  177. };
  178.  
  179. //////////////////////////////////////////////////////////////////////////
  180.  
  181. static char blah_buffer[65536];
  182.  
  183. template <class POOL>
  184. void Measure ( const char * name )
  185. {
  186.         POOL testPool;
  187.  
  188.         clock_t nt0 =  clock();
  189.  
  190.         for ( int f=0; f<TotalFrames; f++ )
  191.         {
  192.                 testPool.Seed(32,1024);
  193.                 testPool.Update(frandv(),frandv());
  194.         }
  195.  
  196.         clock_t nt1 = clock();
  197.  
  198.         printf ( "%s %i msec\n", name, int(nt1-nt0) );
  199. }
  200.  
  201.  
  202.  
  203. int main(int argc, char* argv[])
  204. {
  205.         Measure< NaivePool<false> >("naive");
  206.         Measure< NaivePool<true> >("naive with prefetch");
  207.         Measure< RingPool >("ring");
  208.  
  209.         return 0;
  210. }

4 Comments

  1. lordmaze:

    Ya vot dobavlu chto na bokse naprimer tozhe mozhno PRAVILNO usat prefetch. Te naprimer usat __xdcbt na vseh intermidiate results chtobi oni ne popadali nikogda v L2( kogda konechno usautsya vmx ). Nu a final results kotorie idut v GPU taki nado samomu flush iz L1. obichno __dcbi dostatochno… - uskorenie mozhno poluchit protsentov na 20…

    nu potom uzhe mozhno nachat kreativit s __restrict :)

  2. look4awhile:

    на боксе и на PS3 PPU префетч можно расставлять с удивительными результатами.
    надо префетчить, только, не на 1-2 линейки вперёд - а на пару килобайт вперёд.
    кусками килобайта по два.

    у меня банальный ADLER32 такими методами ускорился в 6.5 раз!!!

  3. lordmaze:

    > надо префетчить, только, не на 1-2 линейки вперёд - а на пару килобайт вперёд.
    > кусками килобайта по два.

    ты какой то умный слишком. я просто делал - для всех intermidiate результатов - dcbt, для всех результатов которые идут на gpu - dcbi. И заебись - на тех же партиклах ускорение раз в пять.

    А еще когда дураком был и shadow volumes любил - считал скиннинг на цпу. Там вообще заебись - vmx + L1 force cache + force flush - и супербыстро. И даже наверно без stalls.

    Но если честно я не очень во все это верю :) - потому что “правильно” смоделеный домик может нафик убить любые твои джедайские оптимизации. Ну и да - к сожалению на бабло это никак не влияет. А бабло - оно рулт :)

  4. finnan:

    Класс, спасибо)

    А что случилось с operator– в листинге?

Leave a comment

You must be logged in to post a comment.