Fråga:
Gäller tumregeln "Undvik att använda flytande punkt" på en mikrokontroller med en flytande enhet (FPU)?
gberth
2020-04-16 20:07:45 UTC
view on stackexchange narkive permalink

Som tumregel försöker jag undvika att använda flytpunkt i min inbäddade systemkodbas.

Variabler för flytpunkter är:

  • Beräkningskrävande
  • Inte atomiskt (kan orsaka problem i en RTOS-applikation eller med avbrott)
  • Deras precision kan orsaka icke-uppenbart beteende (problem med flytjämförelse).

Men hur är det med en mikrokontroller med en flytpunktsenhet (som STM32F4)?

Gäller dessa problem fortfarande?Skulle du ändå rekommendera att använda flytpunkt?

Poängen (2) och (3) gäller fortfarande.Så, inte så mycket "undvik helt och hållet" utan "använd med ögonen öppna" för att undvika problem med atomicitet eller opålitliga operationer.(Och använd aldrig flottor som loopvariabler!)
Du bör välja din MCU för att matcha din applikation istället för att utforma din applikation så att den matchar din MCU.Så om du kan undvika flytande punktoperationer kan du välja en MCU utan en FPU och förmodligen sänka kostnaden för ditt system.
@ThePhoton Med STM: erna gäller detta särskilt eftersom många av F1-F4-serien är stiftkompatibla
"_Atomic float" fungerar på samma sätt som "_Atomic int32_t" så långt som atomicitet och beställning, och är låsfritt på normala ARM-processorer.Om du tycker att vanlig `int 'är säker att använda i C i allmänhet, tänk igen.t.ex.[MCU-programmering - C ++ O2-optimering bryts under loop] (https://electronics.stackexchange.com/a/387478).Re: atomisk flytpunkt - kompilator / språkstöd är i princip samma som i C ++: [Atomic double floating point eller SSE / AVX vector load / store on x86 \ _64] (https://stackoverflow.com/q/45055402) /[C ++ 20 std :: atom- std :: atomic.specializations] (https://stackoverflow.com/q/58680928)
Heltalsoperationer är * inte * atomära utan uttryckliga vakter för att göra dem så.Det händer inte automatiskt.Detta gör din andra övervägande ogiltig och inte tillämplig.Den första överväganden gäller inte när du har en FPU för hårdvara.Så du tittar bara på det tredje problemet: precision.Om du behöver flytpunkt måste du dock ha flytpunkt.Det har inget att göra med MCU: er.Programmerare på stora järnmaskiner tar samma beslut med samma avvägningar med avseende på precision.
@CodyGray: Jag tänkte på detta "icke-atomära" påstående lite mer.Jag undrar om folk menar att vissa soft-float-bibliotek inte återkommer, och därmed kan brytas om ett avbrott inträffar i mitten av beräkningen även utan åtkomst till delat minne (om avbrottshanterare också använder FP, eller om du växlar samman)?Det skulle vara vettigt (för fall / ISA där du inte bara kan använda stackutrymme för temporärer, antingen för stort eller ingen praktisk stackrelativ adressering i gamla 8-bitarsmikro).I så fall är det inte ett tydligt sätt att beskriva det, särskilt inte i C-termer.
”Kan orsaka icke-uppenbart beteende” - det är inte en anledning att inte använda flottör, utan att korrekt lära sig om deras beteende.I många applikationer är flottörerna bäst lämpade om du har råd med dem.Fixerad precision ger dig bara en annan uppsättning icke-uppenbara beteenden som i praktiska tillämpningar ofta är mycket värre.
Du måste titta på detaljerad information om timing.Även med accelererad FPU har du kanske inte en enda klockcykel multiplicerar och delar.Optimerade heltalsberäkningar kan fortfarande vara snabbare och kommer säkert att vara mer bärbara för lägre kostnad hårdvara.Men om processorn redan är vald, kanske det inte är vettigt att lura med heltalsrutiner.
@CodyGray: De flesta plattformar erbjuder garantier om effekterna av samtidiga operationer som är starkare än vad som skulle krävas av de flesta programmeringsspråk.Till exempel kräver de flesta 32-bitars plattformar inte att programmerare gör något speciellt för att läsa ett justerat 32-bitars objekt på ett sätt som garanterat antingen ger sitt ursprungliga värde eller något 32-bitars värde som har skrivits till det sedansedan och garantera att om ett objekt bara skrivs av en tråd, och en annan tråd observerar effekterna av en skrivning, kommer alla framtida observationer att ge det värdet eller de värden som skrivs efter det.
@supercat Problemet med att heltal / float etc inte är atomisk finns inte så mycket i hårdvaran, som i C-språket.Till exempel älskar C att använda stacken, så om du läser något 32-bitars heltal med din 32-bitars CPU kan det fortfarande betyda "load register x from stack" + "read register x", vilket är 2 monteringsinstruktioner och inte atomisk, oavsett om CPU: n kan utföra instruktionen "läsregister x" atomär eller inte.Om du skriver allt i samlare har du inte det här problemet, men få människor gör det nuförtiden.
@Lundin: Jag försummade ett scenario där observationer av ett objekt kan tidsresa, vilket skulle inträffa om tråd 1 läser ett objekt via en värde, sedan en annan värde och igen via den första kan återanvändningen av den första värdet gevärde läst tidigare därigenom.Mea culpa på det.Å andra sidan tror jag att de flesta implementeringar för de flesta plattformar måste gå ur deras sätt att inte upprätthålla den första garantin - att varje läsning antingen ger det ursprungliga värdet av ett objekt eller något värde som har skrivits sedan dess,och avstår oftast från sådana saker.
@Lundin: Det är olyckligt att språkstandarden inte ger ett sätt genom vilket en implementering kan ta emot en "vanlig" pekare till ett objekt och läsa den med någon form av definierad semantik i scenarier där den kan nås någon annanstans, även om den är gammal eller nyvärden skulle vara acceptabla (nyare antagligen föredras, men gamla är fortfarande acceptabla).
@supercat Ett mycket giltigt scenario är: - main.c laddar registervärdet från "PORTX" till CPU-registret.- Context switch från ISR.- ISR skriver till PORTX och återvänder.- main.c skriver från CPU-registret till PORTX och förstör vad ISR just gjorde där.
@Lundin: Naturligtvis.Om kod vill ha en tillförlitlig atom-läs-modifier-skriv-sekvens måste man skriva kod för att tvinga den.En av mina husdjur är hårdvara som gör sådana saker nödvändiga snarare än att använda separata "set-bit" och "clear-bit" adresser.Å andra sidan skulle mycket svag semantik som jag beskrivit vara tillräcklig för att stödja det lata-oföränderliga singleton-mönstret med noll CPU-kommunikationskostnad om man är villig att tolerera en läckt instans per kärna per applikationstid.Varje kärna som läser singletonpekaren kommer antingen att se en nullpekare eller ...
... adressen till en initialiserad instans (förutsatt att koden som genererar instansen inkluderar en barriär mellan skapandet av singleton och publiceringen av dess adress, och att minneshanteraren erbjuder ett sätt att begära ett lagringsblock som är garanteratinte vara i någons cache).
Med tanke på en uin32_t-variabel på en 32-bitars MCU med en enda kärna (t.ex. STM32 med en RTOS).Med tanke på att två uppgifter har tillgång till den här uint32_t-minnesadressen, den första som läsare, den andra som författare. Enligt min förståelse, även med en olycklig RTOS-kontextomkopplare, är ett tävlingsvillkor inte möjligt.Skulle du hålla med? Så här definierade jag (kanske felaktigt) atom. Är din definition av atom: Operation sker i en enda instruktion (så även ISR kan inte generera ett rasförhållande)?Om så är fallet, är det möjligt att ha en atomvariabel i ett inbäddat system?
Nio svar:
Elliot Alderson
2020-04-16 20:51:52 UTC
view on stackexchange narkive permalink

Du bör komma ihåg att FPU: erna på dessa mikrokontroller ofta bara är FPU: er med en precision.Enkel precisionsflytpunkt har bara en 24-bitars mantissa (med den dolda MSB) så att du i vissa fall kan få bättre precision från 32-bitars heltal.

Jag har jobbat med att använda fastpunktsberäkning, och för situationer där data har ett begränsat dynamiskt område kan du uppnå samma precision som enpunktsflytpunkt med 32-bitars fixpunkt med ungefär en storleksordningi exekveringstid.Jag har också sett att kompilatorn drar in en hel del bibliotekskostnader för FPU.

+1 Kan vara användbart att notera att Cortex M4 stöder packade datatyper och DSP-instruktioner (t.ex. multiplicera ackumulera) som kan ge dig massiva förbättringar om du kan arbeta i fast punkt, även om en FPU finns.Observera också att STM32F7 har dubbel precision FPU, men du stöter på minnesflaskhalsar snabbare eftersom det fortfarande är ett 32-bitars system.
+1 Intressant punkt.Jag ansåg inte det faktum att FPU kanske bara skulle stödja en-precision flytpunkt och inte dubbelt.
@gberth Detta är vanligt på många mikron.FWIW du måste också ha samma oro för heltal.16-bitars och 32-bitars mikro stöder inte 64-bitars heltal, och vissa har inte stöd för heltal som är mindre än ordlängden.Alla dessa operationer måste hanteras av kompilatorn istället för som atominstruktioner.
awjlogan
2020-04-16 20:23:41 UTC
view on stackexchange narkive permalink

Om du köper en processor med en hårdvarufPU har du inte samma problem med precision *, återupptagande beteende osv. Fortsätt och använd dem!

Ett par tankar dock:

  • Du kan tänka dig att processorn kan stänga av den (stora) FPU när den inte används, så kontrollera att du kör dina FP-rutiner sparar du kraft (om du bryr dig om det) över att göra det i programvara.

  • Beroende på implementeringen kan FPU också ha olika register i kärnan - ibland kan kompilatorer använda dessa smart.

  • Använd inte FPU som en krycka för dålig firmwaredesign.Kan du till exempel göra samma sak med fast punkt och använda den vanliga kärnan istället?

(* FPU: n ska överensstämma med en given standardimplementering, så var medveten om eventuella begränsningar som uppstår på grund av detta.)

+1 För strömförbrukning.Jag gjorde inte om detta avvägning.Tack.
Observera att blinda antaganden som "FPU använder mer kraft" ofta kan få dig i trubbel.Efterlikna FPU-operationer med fasta punktsrutiner kan sluta använda fler cykler och därmed mer kraft än om du bara skulle använda den inbyggda effektiva maskinvarufPU.Och även om jag är säker på att det finns saker där ute som jag inte är medveten om, har alla MCU: er jag har sett med hårdvaru-FPU: er dedikerade flytpunktsregister (dvs. de överlappar inte med "core" -registret).Jag håller dock helt med om att den flytande punkten inte bör användas som en krycka för dålig design.Använd rätt verktyg för jobbet.
@CodyGray - absolut, det är därför jag sa "kolla" :) Chansen är stor, om du använder FP, då kommer FPU att vara mer energieffektiv, men det kanske inte heller, säg om du bara gör en enda operation.Micros är så avancerade nu, det finns många ställen antaganden kommer att leda dig vilse - allt roligt dock!
Ralf Kleberhoff
2020-04-16 20:27:53 UTC
view on stackexchange narkive permalink

Några av bekymmerna gäller fortfarande.

  • Flytpunktsräkning är i sig mer beräkningskrävande än heltal.Men med en flottpunktsenhet kommer du förmodligen inte att märka det längre, kanske några extra CPU-cykler eller lite mer strömförbrukning.
  • Operationerna är atomära, så att oro är borta.
  • Precisions- / avrundnings- / jämförelsesproblemet finns kvar, i exakt samma mängd som i programvaruberäkning.

Särskilt den senare kan orsaka mycket otäcka problem och tvinga dig att skriva icke-intuitiv kod, t.ex.alltid jämföra med ett intervall, aldrig testa jämlikhet mot ett fast värde.

Och kom ihåg att en flottör med en precision bara har 23 bitars upplösning, så du kan behöva ersätta ett 32-bitars heltal med ett dubbelt precisionsflott.

+1 För att nämna "icke-intuitiv kod".Även om min fråga främst fokuserade på prestanda och robusthet, tycker jag att det är viktigt att också tänka på kodens tydlighet.
Re "icke-intuitiv kod", det betyder bara att du väljer rätt typ för det du gör.Om dina värden inte är exakta (t.ex. att mäta en spänning) är exakt jämförelse i sig fel.Att använda toleranser gör inte koden icke-intuitiv, utan gör den * korrekt *.
När det gäller mer beräkningskrävande: Med dedikerad hårdvara kan detta fortfarande orsaka ökad strömförbrukning.
Håller med Graham.Att jämföra en spänning i heltal millivolt är sannolikt fel av exakt samma anledning.Som sagt, din ADC producerar sannolikt heltalsmätningar.
Flytpunktsräkning är i sig mer beräkningsintensiv än heltal, om du beräknar med heltal.Om ditt problem är med flytande nummer kommer den högt inställda FPU att bli mycket snabbare och gör all skalning i programvara.det är därför FPU finns.
Kan du ge ett exempel på en arkitektur där operationer med flytande punkter är atomära?Det finns inga sådana garantier på varken x86 eller ARM.Jag är mindre bekant med MIPS och de andra mer esoteriska arkitekturerna där ute.Men i allmänhet kan du inte anta att * några * operationer kommer att vara atomära, inklusive heltaloperationer.Till och med något så enkelt som en last eller en butik delas ofta upp i flera operationer på grund av begränsningar i minnesbussen (på en 32-bitars MCU kan du inte ladda ett 64-bitars flytpunktsvärde med dubbel precision som en atomoperation).
jonathanjo
2020-04-16 22:05:47 UTC
view on stackexchange narkive permalink

Beräkningar är ofta bra om du har FPU och avvägningarna är lätta att förstå.

Men se upp för produktionen.Om du har något som C-biblioteket skulle du bli förvånad över komplexiteten i printf ("% 0.6g", x); Jag har sett bibliotek som använde malloc() inuti printf () , och det är inte den typen av saker du vill ha i en mikrokontroller.

Tyvärr kräver standarden att flytpunktsargument till printf-familjefunktioner matas ut med en 'dubbel' typ som måste vara minst 48 bitar bred även på plattformar som bara har en 32-bitars flytpunktsmaskinvara.
Och använder den globala variabler (delat tillstånd)?
Detta är en mycket bra punkt om behovet av att se upp för uppblåsta Cdd lib-implementeringar när du riktar inbäddade MCU: er.Tyvärr är det inte begränsat till flytande punktoperationer.Något som "printf" kommer sannolikt att vara en absolut katastrof i en allvarligt begränsad miljö.Lyckligtvis kommer du inte att använda det i produktionen.Om du använder det alls är det för felsökning där prestanda inte är kritisk.När du bryr dig om prestanda måste du vara mycket medveten om vad din C-kompilator genererar eller skriva ASM själv.Vanligtvis bryr du dig mindre än du tror.
Ron Beyer
2020-04-16 20:23:55 UTC
view on stackexchange narkive permalink

Ärligt talat är detta en typ av mikrooptimering som du bara bör göra efter du har en fungerande kodbas.Vissa MCU har problem med division också, även med heltal.Så att göra något som "multiplicera fp med 100, gör lite manipulation, dela med 100" kan ta mycket mer tid än att bara manipulera flottören.

Det är här profileringen kommer in, du måste välja dina strider, det finns inget svar.När du har en fungerande kodbas kan du identifiera flaskhalsar och optimera selektivt.Att undvika något som ett filtuttryck leder till mikrooptimeringar som tar mer tid att koda än de faktiskt sparar.Det är värdelöst att optimera ut floats från en rutin med låg prioritet som körs en gång i timmen, medan det är användbart att optimera floats för en tung uppgift.

Förhoppningsvis om du gör en fast punkt, skulle du använda krafter på 2 snarare än krafter på 10. I stället för "multiplicera med 100, operera, dela med 100", bör du multiplicera med 128, operera och dela med 128.Jag känner inte till någon MCU som inte kan dela med 128 effektivt.
@ThePhoton Ja, det var bara ett snabbt exempel, och jag skulle tro att en bra kompilator kan optimera a / 128 till >> 8 innan den ens kommer till processorn, jag valde bara ett godtyckligt värde.
Division med en konstant är inte mycket svårare än multiplikation.Det kan göras i termer av multiplikation och skift, jämför t.ex.ARM-koden för [dessa två funktioner] (https://gcc.godbolt.org/z/SMdGvH).Tja, det är åtminstone sant när du har en instruktion att multiplicera (32bit, 32bit) → 64bit.
@Ruslan: Tyvärr innehåller många Cortex-M0-kärnor en hårdvara för en encykel 32x32-> 32 multiplicering, men inget effektivt sätt att beräkna det övre ordet för en 32x32-produkt.Jag skulle tro att hårdvara som kunde utföra en fyrcykelmultiplikering som gav antingen den övre eller nedre halvan av resultatet skulle ha varit billigare och mer användbar, men kanske inte lika tilltalande för marknadsavdelningen.Jag tycker också att det är något nyfiken att chipleverantörer kan begära antingen en multiplicering med en cykel eller 32 cykler, men det finns inget alternativ för en multiplicering på ~ 18 cykler, vilket skulle vara nästan lika billigt som en 32-cykel.
Justme
2020-04-16 20:51:36 UTC
view on stackexchange narkive permalink

I grund och botten inget behov att undvika om du har en FPU, och din RTOS stöder också kontextväxling av FPU.Precisionsfrågan finns fortfarande om du har en FPU eller inte.Du kan också använda flytpunkt utan FPU om du har prestanda att göra det - enstaka felsökning av float-variabel är bara bra på en FPU-mindre Cortex-M3.Men uppenbarligen på en begränsad 8-bitars MCU med litet minne kan kostnaden för att använda till och med en enda flottöroperation få in många hundra byte med mjukt flottörbibliotek, så ibland är det inte meningsfullt att använda flottörer.

Förutsatt att FPU-operationerna är atomära, vilket ytterligare arbete behöver RTOS göra för att stödja flottör?
Beror naturligtvis på FPU-programmeringsmodellen - men när en RTOS växlar från en uppgift till en annan måste den spara alla CPU-register någonstans (uppgiftsstruktur) och återställa alla CPU-register för nästa uppgift.Med en FPU måste uppgiftsomkopplaren också lagra och ladda FPU-registren, så uppgiftsomkopplingsprocessen måste lagra och återställa fler register.
Om en CPU stöder lata kontextväxlare [som Cortex-M4] (http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0298a/DAFJBFJF.html), gör RTOS inte 't måste nödvändigtvis göra FPU-butiker och laddningar på kontextomkopplaren.
Lundin
2020-04-22 19:07:30 UTC
view on stackexchange narkive permalink

WNär man inte använder flytande punkt

Det första man behöver inse är att flytande punkt betyder inte betyder "Jag behöver decimaler". Det är här som cirka 95% av alla inbäddade programmerare missbrukar flytande punkt misslyckas.

Kuren för den misstro är att inse att internt bör programmet använda en enhet som är vettigt för MCU att använda, inte en som är meningsfull för människor.

Om du till exempel mäter ström i mA med en 10-bitars ADC på chipet, är den praktiska enheten att använda i "fasta råa ADC-värden från 0 till 1024". I C-programmering betyder det en uint16_t eller eventuellt en uint_fast16_t . Inte en int och absolut inte en float .

Att använda enheten mA inuti firmware-beräkningar är bara bekvämt för den mänskliga programmerarens hjärna, om den inte kan hantera abstrakta enheter. Men det är obekvämt för programmet, för det betyder att du måste skala om alla avläsningar till och eventuellt lägga till avrundningsfelaktigheter när du gör det. Plus skalningskoden är bara uppblåst. Och det kommer sannolikt att inkludera delning, vilket kan vara smärtsamt för många MCU: er.

Ja, du läser strömmen i mA. Men om du inte behöver skriva ut strömmen på en skärm eller något till en mänsklig användare, är den enheten faktiskt inte till hjälp. Gör mA-skalning på penna och papper medan du utformar algoritmen istället för att dra den till din firmware.


W När du använder flytande punkt

  • Om din MCU har en FPU och du faktiskt behöver göra avancerad matematik bör du använda flytande punkt. Annars bör du inte.

"Avancerad matematik" betyder inte nödvändigtvis avancerad ur programmerarens perspektiv, utan ur programvarans perspektiv."Advanced" inkluderar saker som kvadratrötter, geometri eller trigonometri, användning av matematik.h i allmänhet, komplexa siffror, AI-matematik etc. Saker som skulle vara smärtsamma att implementera i fast punkt.

Sascha
2020-04-18 20:46:58 UTC
view on stackexchange narkive permalink
  • Jag skulle utesluta icke-atomära operationer från listan över skäl eftersom dina sätt att hantera andra komplexa strukturer också kan tillämpas på flottöroperationer.

  • Jag skulle också utesluta den beräkningsstyrka som behövs från listan över skäl eftersom det är mycket applikations- och processorspecifikt.

Låt oss fokusera på de "icke uppenbara" problemen som orsakas av variationen i flottörprecisionen

  • Det mest grundläggande är att FP-aritmetik är icke-associerande, (a + b) + c är inte lika med a + (b + c). Föreställ dig a = 1, b = -1, c = 1e-20. Låter ofarligt, men tänk dig att din applikation använder ett ändligt impulsfilter och du kör ett testfall som antar vara exakt noll

  • för mig är den andra anledningen det faktum att heltal och fasta kommaoperationer har ett överflöde i det intervall som jag förväntar mig (ok, inte aktiverat som standard i C). t.ex. I flytande punkt kan en integrator lätt fly till mycket stora värden utan att någon märker ...

Rich
2020-04-19 05:07:04 UTC
view on stackexchange narkive permalink

Liksom många saker beror det på ditt användningsfall.

En skräddarsydd heltalalgoritm kan vara effektivare (t.ex. för en multiplicering) om du känner till det tillåtna intervallet för ingångsvärden.När dessa värden vidgas kommer allt du har kodat att degenereras till en flytande punkt multipliceras utan att dra nytta av hårdvara.

Det är möjligt att hantera långsamma beräkningar som har svårt att bestämma tidpunkter på ett inbäddat system genom att köra dem i en bakgrundsslinga, eller till och med ha olika processorer för t.ex.kontroll och beräkning.



Denna fråga och svar översattes automatiskt från det engelska språket.Det ursprungliga innehållet finns tillgängligt på stackexchange, vilket vi tackar för cc by-sa 4.0-licensen som det distribueras under.
Loading...