Kodėl 0.1f ir 0 keitimas sulėtina našumą 10x?

Kodėl šis kodo bitas

 const float x[16] = { 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6}; const float z[16] = {1.123, 1.234, 1.345, 156.467, 1.578, 1.689, 1.790, 1.812, 1.923, 2.034, 2.145, 2.256, 2.367, 2.478, 2.589, 2.690}; float y[16]; for (int i = 0; i < 16; i++) { y[i] = x[i]; } for (int j = 0; j < 9000000; j++) { for (int i = 0; i < 16; i++) { y[i] *= x[i]; y[i] /= z[i]; y[i] = y[i] + 0.1f; // <-- y[i] = y[i] - 0.1f; // <-- } } 

veikia daugiau nei 10 kartų greičiau nei kitas bitas (identiškas, jei nenurodyta)

 const float x[16] = { 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6}; const float z[16] = {1.123, 1.234, 1.345, 156.467, 1.578, 1.689, 1.790, 1.812, 1.923, 2.034, 2.145, 2.256, 2.367, 2.478, 2.589, 2.690}; float y[16]; for (int i = 0; i < 16; i++) { y[i] = x[i]; } for (int j = 0; j < 9000000; j++) { for (int i = 0; i < 16; i++) { y[i] *= x[i]; y[i] /= z[i]; y[i] = y[i] + 0; // <-- y[i] = y[i] - 0; // <-- } } 

rengiant „Visual Studio 2010 SP1“. (Aš netikrinau kitų kompiliatorių.)

1406
16 февр. Dragarro nustatė vasario 16 d 2012-02-16 18:58 '12, 18:58, 2012-02-16 18:58
@ 5 atsakymai

Sveiki atvykę į denormalizuoto plaukiojančio taško pasaulį! Jie gali sugadinti našumą !!! p>

Denormalūs (arba neįprastiniai) skaičiai yra tam tikras įsilaužimo būdas, kad gautumėte papildomų verčių, kurios yra labai artimos nuliui nuo kintamo taško. Operacijos su denormalizuotu slankiojo kablelio tašku gali būti dešimtys ar šimtai kartų lėtesnės nei normalizuotame slankiojo kablelio taške. Taip yra dėl to, kad daugelis perdirbėjų negali juos apdoroti tiesiogiai ir turi sugauti juos ir juos išspręsti naudojant mikrokodą.

Jei spausdinsite numerius po 10 000 iteracijų, pamatysite, kad jie konvertuojasi į skirtingas reikšmes, priklausomai nuo to, ar 0.1 0 ar 0.1 .

Čia yra parengtas x64 testo kodas:

 int main() { double start = omp_get_wtime(); const float x[16]={1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2.0,2.1,2.2,2.3,2.4,2.5,2.6}; const float z[16]={1.123,1.234,1.345,156.467,1.578,1.689,1.790,1.812,1.923,2.034,2.145,2.256,2.367,2.478,2.589,2.690}; float y[16]; for(int i=0;i<16;i++) { y[i]=x[i]; } for(int j=0;j<9000000;j++) { for(int i=0;i<16;i++) { y[i]*=x[i]; y[i]/=z[i]; #ifdef FLOATING y[i]=y[i]+0.1f; y[i]=y[i]-0.1f; #else y[i]=y[i]+0; y[i]=y[i]-0; #endif if (j > 10000) cout << y[i] << " "; } if (j > 10000) cout << endl; } double end = omp_get_wtime(); cout << end - start << endl; system("pause"); return 0; } 

Išvada:

 #define FLOATING 1.78814e-007 1.3411e-007 1.04308e-007 0 7.45058e-008 6.70552e-008 6.70552e-008 5.58794e-007 3.05474e-007 2.16067e-007 1.71363e-007 1.49012e-007 1.2666e-007 1.11759e-007 1.04308e-007 1.04308e-007 1.78814e-007 1.3411e-007 1.04308e-007 0 7.45058e-008 6.70552e-008 6.70552e-008 5.58794e-007 3.05474e-007 2.16067e-007 1.71363e-007 1.49012e-007 1.2666e-007 1.11759e-007 1.04308e-007 1.04308e-007 //#define FLOATING 6.30584e-044 3.92364e-044 3.08286e-044 0 1.82169e-044 1.54143e-044 2.10195e-044 2.46842e-029 7.56701e-044 4.06377e-044 3.92364e-044 3.22299e-044 3.08286e-044 2.66247e-044 2.66247e-044 2.24208e-044 6.30584e-044 3.92364e-044 3.08286e-044 0 1.82169e-044 1.54143e-044 2.10195e-044 2.45208e-029 7.56701e-044 4.06377e-044 3.92364e-044 3.22299e-044 3.08286e-044 2.66247e-044 2.66247e-044 2.24208e-044 

Atkreipkite dėmesį, kad antrajame eiga numeriai yra labai arti nulio.

Denormalizuoti skaičiai paprastai yra reti, todėl dauguma procesorių nesistengia juos efektyviai apdoroti.


Jei norite įrodyti, kad jis turi viską, kas susiję su denormalizuotais numeriais, jei atsisakote denormalų iki nulio , pridėkite jį prie kodo viršaus:

 _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); 

Tada versija iš 0 sustoja 10 kartų lėčiau ir iš tikrųjų tampa greitesnė. (Tam reikia, kad kodas būtų sukompiliuotas su įjungta SSE.)

Tai reiškia, kad užuot naudoję šias keistąsias beveik nulinio tikslumo vertes, mes paprasčiausiai jį apvaliame iki nulio.

Terminai: „Core i7 920 @ 3.5 GHz“:

 // Don't flush denormals to zero. 0.1f: 0.564067 0 : 26.7669 // Flush denormals to zero. 0.1f: 0.587117 0 : 0.341406 

Galų gale, tai tikrai neturi nieko bendro su tuo, ar jis yra sveikasis skaičius ar plūduriuojantis taškas. 0 arba 0.1f konvertuojamas / saugomas registre už abiejų kilpų ribų. Todėl tai neturi įtakos našumui.

1506
16 февр. atsakymas pateikiamas Mysticial 16 vasario mėn. 2012-02-16 19:20 '12 at 7:20 pm 2012-02-16 19:20

Naudojant „ gcc ir taikant skirtingą generuojamą surinkimą, šis skirtumas yra toks:

 73c68,69 < movss LCPI1_0(%rip), %xmm1 --- > movabsq $0, %rcx > cvtsi2ssq %rcx, %xmm1 81d76 < subss %xmm1, %xmm0 

cvtsi2ssq vienas 10 kartų lėčiau.

Atrodo, kad float versija naudoja XMM registrą, pakrautą iš atminties, o int versija konvertuoja tikrąją int 0 reikšmę į float , naudojant cvtsi2ssq nurodymą, atsižvelgiant į ilgą laiką. Perdavimas -O3 į gcc nepadeda. (gcc versija 4.2.1.)

(Naudojant double vietoj float nėra reikšmės, išskyrus tai, kad jis keičia cvtsi2ssq į cvtsi2sdq .)

Atnaujinti

Kai kurie papildomi bandymai rodo, kad tai nebūtinai yra cvtsi2ssq instrukcija. Po pašalinimo (naudojant int ai=0;float a=ai; ir naudojant vietoj 0 ), greičio skirtumas išlieka. Taigi, @Mysticial teises, denormalized plūdės klausimas. Tai matyti išbandant vertes nuo 0 iki 0.1f . Pirmiau minėto kodo sukimo taškas yra maždaug 0.00000000000000000000000000000001 , kai kilpos staiga eina 10 kartų.

border=0

Atnaujinti <1

Nedidelis šio įdomaus reiškinio vizualizavimas:

  • 1 skiltis: plūdurys padalintas iš 2 kiekvienam iteracijai
  • 2 skiltis: šio plūdžio dvipusis vaizdas
  • 3 skiltis: laikas, praleistas sumaišant šį slankiklį 1 kartą 7 kartus

Jūs galite aiškiai matyti, kad eksponento greitis (paskutiniai 9 bitai) keičiasi į mažiausią vertę, kai įvedama denormalizacija. Šiuo metu paprastas papildymas tampa 20 kartų lėčiau.

 0.000000000000000000000000000000000100000004670110: 10111100001101110010000011100000 45 ms 0.000000000000000000000000000000000050000002335055: 10111100001101110010000101100000 43 ms 0.000000000000000000000000000000000025000001167528: 10111100001101110010000001100000 43 ms 0.000000000000000000000000000000000012500000583764: 10111100001101110010000110100000 42 ms 0.000000000000000000000000000000000006250000291882: 10111100001101110010000010100000 48 ms 0.000000000000000000000000000000000003125000145941: 10111100001101110010000100100000 43 ms 0.000000000000000000000000000000000001562500072970: 10111100001101110010000000100000 42 ms 0.000000000000000000000000000000000000781250036485: 10111100001101110010000111000000 42 ms 0.000000000000000000000000000000000000390625018243: 10111100001101110010000011000000 42 ms 0.000000000000000000000000000000000000195312509121: 10111100001101110010000101000000 43 ms 0.000000000000000000000000000000000000097656254561: 10111100001101110010000001000000 42 ms 0.000000000000000000000000000000000000048828127280: 10111100001101110010000110000000 44 ms 0.000000000000000000000000000000000000024414063640: 10111100001101110010000010000000 42 ms 0.000000000000000000000000000000000000012207031820: 10111100001101110010000100000000 42 ms 0.000000000000000000000000000000000000006103515209: 01111000011011100100001000000000 789 ms 0.000000000000000000000000000000000000003051757605: 11110000110111001000010000000000 788 ms 0.000000000000000000000000000000000000001525879503: 00010001101110010000100000000000 788 ms 0.000000000000000000000000000000000000000762939751: 00100011011100100001000000000000 795 ms 0.000000000000000000000000000000000000000381469876: 01000110111001000010000000000000 896 ms 0.000000000000000000000000000000000000000190734938: 10001101110010000100000000000000 813 ms 0.000000000000000000000000000000000000000095366768: 00011011100100001000000000000000 798 ms 0.000000000000000000000000000000000000000047683384: 00110111001000010000000000000000 791 ms 0.000000000000000000000000000000000000000023841692: 01101110010000100000000000000000 802 ms 0.000000000000000000000000000000000000000011920846: 11011100100001000000000000000000 809 ms 0.000000000000000000000000000000000000000005961124: 01111001000010000000000000000000 795 ms 0.000000000000000000000000000000000000000002980562: 11110010000100000000000000000000 835 ms 0.000000000000000000000000000000000000000001490982: 00010100001000000000000000000000 864 ms 0.000000000000000000000000000000000000000000745491: 00101000010000000000000000000000 915 ms 0.000000000000000000000000000000000000000000372745: 01010000100000000000000000000000 918 ms 0.000000000000000000000000000000000000000000186373: 10100001000000000000000000000000 881 ms 0.000000000000000000000000000000000000000000092486: 01000010000000000000000000000000 857 ms 0.000000000000000000000000000000000000000000046243: 10000100000000000000000000000000 861 ms 0.000000000000000000000000000000000000000000022421: 00001000000000000000000000000000 855 ms 0.000000000000000000000000000000000000000000011210: 00010000000000000000000000000000 887 ms 0.000000000000000000000000000000000000000000005605: 00100000000000000000000000000000 799 ms 0.000000000000000000000000000000000000000000002803: 01000000000000000000000000000000 828 ms 0.000000000000000000000000000000000000000000001401: 10000000000000000000000000000000 815 ms 0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 42 ms 0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 42 ms 0.000000000000000000000000000000000000000000000000: 00000000000000000000000000000000 44 ms 

Ar yra lygiavertis ARM aptarimas Stack'e. Ar perpildymas išleidžia denormalizuotą kintamąjį taške Objective-C? .

403
16 февр. atsakymas pateikiamas mvds 16 vasario mėn 2012-02-16 19:19 '12, 19:19, 2012-02-16 19:19

Taip yra dėl denormalizuoto slankiojo kablelio naudojimo. Kaip atsikratyti jos ir našumo bausmės? Žvelgiant per internetą, kaip užkirsti kelią nenormaliems skaičiams, atrodo, kad dar nėra „geresnio“ būdo tai padaryti. Radau šiuos tris metodus, kurie gali geriausiai veikti skirtingose ​​aplinkose:

  • Gali neveikti kai kuriose GCC aplinkose:

     // Requires #include <fenv.h> fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV); 
  • Gali neveikti kai kuriose „Visual Studio“ aplinkose: 1

     // Requires #include <xmmintrin.h> _mm_setcsr( _mm_getcsr() | (1<<15) | (1<<6) ); // Does both FTZ and DAZ bits. You can also use just hex value 0x8040 to do both. // You might also want to use the underflow mask (1<<11) 
  • Pasirodo, kad dirba tiek GCC, tiek „Visual Studio“:

     // Requires #include <xmmintrin.h> // Requires #include <pmmintrin.h> _MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); _MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); 
  • „Intel“ kompiliatoriuje yra parinkčių, kaip išjungti numatytuosius „Intel“ procesorius. Skaitykite daugiau čia.

  • Kompiliatorių jungikliai. -ffast-math , -msse arba -mfpmath=sse išjungs denormalus ir atliks kelis kitus dalykus greičiau, tačiau, deja, yra ir daug kitų apytikslių, kurie gali sugadinti jūsų kodą. Bandymo testas! Greitojo matematikos ekvivalentas „Visual Studio“ kompiliatoriui yra /fp:fast , bet negalėjau patvirtinti, kad tai taip pat išjungia denormalus. 1

30
26 февр. atsakymas pateiktas 26 pav . 2014-02-26 15:15 '14 at 15:15 2014-02-26 15:15

Naudodami gcc, galite įjungti FTZ ir DAZ su šiuo:

 #include <xmmintrin.h> #define FTZ 1 #define DAZ 1 void enableFtzDaz() { int mxcsr = _mm_getcsr (); if (FTZ) { mxcsr |= (1<<15) | (1<<11); } if (DAZ) { mxcsr |= (1<<6); } _mm_setcsr (mxcsr); } 

taip pat naudokite gcc jungiklius: -msse -mfpmath = sse

(suderintos paskolos Carl Hetherington [1])

[1] http://carlh.net/plugins/denormals.php

19
02 окт. Vokietijos Garcia atsakymas 02 spalis 2012-10-02 07:40 '12 at 7:40 2012-10-02 07:40

Dana Neely komentaras turėtų būti išplėstas atsakant į:

Tai nėra nulinė konstanta, 0.0f kuri denormalizuota arba sukelia sulėtėjimą, tai yra vertės, kurios artėja prie nulio kiekvienoje kontūro iteracijoje. Kai jie artėja prie nulio, jiems reikia didesnio tikslumo, kad jie atstovautų, ir jie tampa denormalizuoti. Tai yra y[i] . (Jie artėja prie nulio, nes x[i]/z[i] mažesnis nei 1,0 visiems i .)

Pagrindinis skirtumas tarp lėto ir greito kodo versijų yra išraiška y[i] = y[i] + 0.1f; Kai tik ši linija bus vykdoma kiekvienoje kontūro atkarpoje, prarandamas papildomas tikslumas plūduriuojančiame taške, o denormalizacija, reikalinga šiam tikslumui atspindėti, nebereikalinga. Po to j y[i] plūduriuojančių taškų operacijos lieka greitai, nes jos nėra denormalizuotos.

Kodėl pridedant 0.1f yra prarastas per didelis tikslumas? Kadangi slankiojo kablelio numeriai turi tik daug reikšminių skaitmenų. Tarkime, kad turite pakankamai atminties trims reikšmingiems skaitmenims, po to 0.00001 = 1e-5 ir 0.00001 + 0.1 = 0.1 , bent jau šiame kintamojo taško formato pavyzdyje, nes nėra vietos laikyti 0.10001 bitų 0.10001 .

Trumpai tariant, y[i]=y[i]+0.1f; y[i]=y[i]-0.1f; y[i]=y[i]+0.1f; y[i]=y[i]-0.1f; y[i]=y[i]+0.1f; y[i]=y[i]-0.1f; ar nemanote, kad tai negerai?

Mystikas sakė, kad : plūduriuojančių dalykų turinys, o ne tik kūrimo kodas.

3
01 авг. atsakymas suteiktas remicles2 01 rug . 2018-08-01 16:32 '18, 16:32 2018-08-01 16:32