Rust 1.98 pievieno f32 un f64 tipiem piecas metodes, kas rēķina to pašu, ko operatori +, -, *, / un %, bet atļauj kompilatoram darbības pārkārtot: algebraic_add, algebraic_sub, algebraic_mul, algebraic_div un algebraic_rem. Laidiens iznāca 20. augustā.
Itamar Turner-Trauring pārmērīja, ko tas dod. Miljona f64 vērtību saskaitīšana ar parasto + aizņem 595,2 mikrosekundes. Tā pati cilpa ar algebraic_add pabeidz darbu 144,5 mikrosekundēs, 4,1 reizi ātrāk. Mērījumi veikti uz Intel i7-12700K, kompilējot ar -C target-cpu=x86-64-v3.
Kāpēc parastā saskaitīšana ir lēna
Peldošā punkta saskaitīšana nav asociatīva. Izteiksme (a + b) + c var dot citu rezultātu nekā a + (b + c), jo pēc katra soļa rezultātu noapaļo. Rust semantika prasa ievērot rakstīto secību, tāpēc LLVM summēšanas cilpu nedrīkst sadalīt četrās vai astoņās paralēlās SIMD joslās. Tā vietā kompilators ģenerē vienu garu atkarību ķēdi, kurā katrs saskaitīšanas rezultāts jāsagaida pirms nākamā soļa.
Veselos skaitļos šī problēma nepastāv. Tā pati cilpa ar i64 miljonu vērtību saskaita 151,9 mikrosekundēs, kas iznāk apmēram puse instrukcijas uz vērtību. Kompilators tur brīvi pārkārto darbības, jo veselo skaitļu saskaitīšana ir asociatīva.
Ar jaunajām metodēm izstrādātājs pats norāda vietas, kur pārkārtošana ir pieļaujama. Kompilators tad drīkst pārvērst a + b + c + d par (a + b) + (c + d) un rēķināt abus pārus vienlaikus.
Piecas metodes vietā viena kompilatora karoga
C un C++ tam pašam mērķim lieto -ffast-math. Karogs iedarbojas uz visu kompilācijas vienību un papildus atļauj kompilatoram pieņemt, ka NaN un bezgalība kodā neparādās. Rust izvēlējās šaurāku ceļu. Atļauja tiek dota vienā izsaukuma vietā, pārējais kods paliek pie IEEE 754 noteikumiem. Pieņēmumi par NaN nemainās. Metodes nekad neizraisa nedefinētu uzvedību.
Ierosinājums nāca no sūdzības, ka stabilā Rust uzrakstīts skalārais reizinājums strādā līdz 8 reizēm lēnāk par C++ versiju uz x86-64 procesoriem. Pieprasījumu Expose algebraic floating point intrinsics sagatavoja izstrādātājs ar segvārdu calder. Metodes pieejamas arī f16 un f128 tipiem, taču šie divi tipi paši vēl nav stabilizēti, tāpēc stabilajā kanālā tos izmantot nevar.
Tā pati atkarību ķēde bija apejama arī agrāk, tikai ar rokām. Viens paņēmiens ir cilpu attīstīt pašam un krāt summu četros vai astoņos atsevišķos mainīgajos, kurus beigās saliek kopā. Kompilators tādu kodu vektorizē, jo katra josla ir atsevišķs mainīgais. Kods pēc tam ir divreiz garāks un jāpielāgo katram datu izmēram.
Otrs paņēmiens ir rakstīt SIMD instrukcijas tieši caur core::arch, kas piesien kodu konkrētai procesora arhitektūrai. Trešais bija std::intrinsics::fadd_algebraic, tā pati funkcionalitāte nestabilā veidā, pieejama tikai nightly kompilatorā. Rust 1.98 no šī saraksta atkrīt trešais variants, pirmie divi paliek noderīgi tur, kur vajag precīzu kontroli pār instrukcijām.
Ko par to maksā
Dokumentācija brīdina par cenu tieši:
Vienas un tās pašas ievaddatu vērtības var dot atšķirīgus rezultātus pat vienas programmas izpildes laikā.
Kompilators konkrēto pārkārtojumu izvēlas pats. Izvēle var atšķirties starp diviem izsaukumiem viena binārfaila iekšienē, jo katrs izsaukums iekļaujas citā kontekstā. Praksē tas skar bitu precizitātes testus, kešatmiņas atslēgas un kontrolsummas, ko rēķina no peldošā punkta rezultāta. Kodā, kur svarīgs atkārtojamības garants, jaunās metodes neder.
Ieguvums arī nav vienāds visur. Otrs Turner-Trauring mērījums, kvadrātisko starpību summa, ar parastajiem operatoriem prasa 628,7 mikrosekundes un 4,5 instrukcijas uz vērtību. Algebraiskā versija to izdara 371,1 mikrosekundē ar vienu instrukciju uz vērtību, kas iznāk 1,7 reizes ātrāk. Ātrums pieaug tur, kur cilpa krāj vienu rezultātu no daudzām vērtībām: summas, skalārie reizinājumi, normas, dispersijas. Elementu pēc elementa reizināšanā bez krāšanas nav ko pārkārtot, tāpēc arī ieguvuma nav.
Skaitļu formatēšana bez itoa
Otrs laidiena jaunums attiecas uz veselu skaitļu pārvēršanu tekstā. Visiem primitīvajiem veselo skaitļu tipiem parādījusies metode format_into, kas ņem &mut NumBuffer<Self> un ieraksta ciparus tajā. Buferim ir fiksēts izmērs, tāpēc formatēšana notiek bez dinamiskās izsaukumu pārvirzes, kuru prasa Display mehānisms. Laidiena paziņojumā rakstīts, ka ātrums sasniedz itoa bibliotēkas līmeni. Pati NumBuffer struktūra atrodas core::fmt, tāpēc strādā arī bez standarta bibliotēkas.
Pārējie stabilizētie API
str::substr_rangeun[T]::subslice_rangeatgriež apakšvirknes pozīciju sākotnējā datu blokāstr::strip_circumfixun[T]::strip_circumfixnoņem prefiksu un sufiksu vienā izsaukumāNonZero<{integer}>::from_str_radixnolasa skaitli no teksta ar norādītu bāziString::from_utf16le,from_utf16beun abu zaudējošās versijas- atomu tipiem
from_mut,get_mut_sliceunfrom_mut_slice std::range::legacy
Dokumentācija tagad arī apraksta ManuallyDrop un Box mijiedarbību ar stabilu garantiju. Vērtības pārvietošana no jau atbrīvota Box ir atļauta, agrāk šī vieta bija neskaidra. Atjauninājumu esošā instalācijā ievelk rustup update stable.
Avoti
- Announcing Rust 1.98.0, blog.rust-lang.org
- Faster floating point math with Rust’s new API, pythonspeed.com
- Rust 1.98 Adds Algebraic Floating-Point Methods Akin To “-ffast-math”, phoronix.com
- Expose algebraic floating point intrinsics, github.com
- ACP: Expose algebraic floating point intrinsics, github.com
Komentāri
Šim rakstam vēl nav komentāru. Esi pirmais, kurš dalās ar savu viedokli.