題:
經驗法則“避免使用浮點數”是否適用於帶有浮點單元(FPU)的微控制器?
gberth
2020-04-16 20:07:45 UTC
view on stackexchange narkive permalink

根據經驗,我盡量避免在嵌入式系統代碼庫中使用浮點數。

浮點變量是:

  • 計算密集型
  • 不是原子性的(可能會在RTOS應用程序或中斷中引起問題)
  • 它們的精度可能導致不明顯的行為(浮動比較問題)。

但是帶有浮點單元(如STM32F4)的微控制器又如何呢?

那些擔憂仍然適用嗎?您仍然建議不要使用浮點數嗎?

第(2)和(3)點仍然適用。因此,與其說是“完全避免”,不如說是“睜開眼睛使用”,以避免原子性或操作不可靠的問題。(並且永遠不要將浮點數用作循環變量!)
您應該選擇適合您的應用程序的MCU,而不是設計適合您的MCU的應用程序。因此,如果您可以避免浮點運算,那麼可以選擇不帶FPU的MCU,這可能會降低系統成本。
-1
在原子性和排序方面,_Atomic float與_Atomic int32_t類似,並且在常規ARM CPU上是無鎖的。如果您通常認為普通的int在C語言中可以安全使用,請再考慮一遍。例如[MCU編程-C ++ O2優化在循環時中斷](https://electronics.stackexchange.com/a/387478)。回复:原子浮點-編譯器/語言支持與C ++基本相同:[原子雙浮點或SSE / AVX向量加載/存儲在x86 \ _64上](https://stackoverflow.com/q/45055402)/[C ++ 20 std :: atomic- std :: atomic.specializations](https://stackoverflow.com/q/58680928)
如果沒有顯式的保護措施,整數操作就不是原子操作。它不會自動發生。這使您的第二個考慮無效且不適用。當您具有硬件FPU時,第一個注意事項不適用。因此,您只關注第三個問題:精度。但是,如果需要浮點,則需要浮點。與MCU無關。大型鐵機上的程序員在精度方面做出相同的決定,但要權衡取捨。
-1
“可能導致不明顯的行為” –這不是不使用浮點數,而是正確了解其行為的理由。在許多應用中,如果您負擔得起的話,浮子是最合適的。固定精度只會給您帶來另一組非顯而易見的行為,在實際應用中通常會更加糟糕。
您必須查看詳細的時序信息。即使使用加速的FPU,您也可能沒有單個時鐘週期的倍增和除法。優化的整數計算可能仍會更快,並且肯定會更容易移植到成本更低的硬件上。但是,如果已經選擇了處理器,那麼擺弄整數例程可能就沒有意義。
@CodyGray:大多數平台都對並發操作的效果提供了保證,該效果比大多數編程語言所要求的要強。例如,大多數32位平台不要求程序員做任何特殊的事情來讀取對齊的32位對象,從而保證產生其初始值或自此以來已寫入的某些32位值。然後,並確保如果一個對象僅由一個線程寫入,而另一個線程觀察到寫入的影響,則所有將來的觀察將產生該值或在該線程之後寫入的值。
@supercat整數/浮點數等不是原子的問題在硬件上並沒有像在C語言中那樣嚴重。例如,C喜歡使用堆棧,因此,如果您使用32位CPU讀取一些32位整數,則可能仍表示“從堆棧加載寄存器x” +“讀取寄存器x”,這是2個彙編程序指令,而不是原子的,無論CPU是否能夠執行“原子讀取寄存器x”指令。如果您正在用彙編程序編寫所有內容,那麼您就不會遇到這個問題,但是如今很少有人這樣做。
@Lundin:我確實忽略了一個觀察對象可能會穿越時間的情況,如果線程1通過一個左值,然後是另一個左值,再通過第一個左值,再次使用第一個左值來讀取對象,則會發生這種情況。從而先前讀取的值。我的罪魁禍首。另一方面,我認為大多數平台的大多數實現都必須竭盡所能,以不維護第一個保證-每次讀取都會產生對象的初始值或此後寫入的值,最通常不要這樣做。
-1
@supercat一個非常有效的方案是:-main.c將寄存器值從“ PORTX”加載到CPU寄存器。-從ISR進行上下文切換。-ISR寫入PORTX並返回。-main.c從CPU寄存器寫入PORTX,並銷毀ISR剛才所做的任何操作。
@Lundin:當然可以。如果代碼需要可靠的原子讀取-修改-寫入序列,則必須編寫代碼以強制執行。我的煩惱之一是使這些事情成為必需的硬件,而不是使用單獨的“設置位”和“清除位”地址。另一方面,如果一個人願意在每個應用程序生命週期中每個內核容忍一個洩漏的實例,那麼像我描述的那樣非常弱的語義足以支持具有零CPU通信開銷的惰性不可變單一模式。讀取單例指針的每個內核都將看到一個空指針,否則...
...已初始化實例的地址(假設生成實例的代碼在創建單例和發布其地址之間包括一個障礙,並且內存管理器提供了一種請求存儲塊的方法,可以保證不要放在任何人的緩存中)。
考慮單核多任務32位MCU(例如帶有RTOS的STM32)上的uin32_t變量。還考慮到有兩個任務可以訪問此uint32_t內存地址,第一個任務作為讀取器,另一個任務作為寫入器。 據我了解,即使使用不幸的RTOS上下文切換,也無法實現競爭條件。你同意嗎? 這就是我定義原子的方式(可能是錯誤的)。 您是對atomic的定義:操作是在一條指令中完成的(所以即使ISR也無法生成競爭條件)?如果是這樣,嵌入式系統上是否可以有一個原子變量?
九 答案:
Elliot Alderson
2020-04-16 20:51:52 UTC
view on stackexchange narkive permalink

您應該記住,這些微控制器上的FPU通常只是單精度FPU。單精度浮點只有24位尾數(帶有隱藏的MSB),因此在某些情況下,您可能會從32位整數獲得更好的精度。

我已經完成了使用定點算法的工作,並且在數據的動態範圍有限的情況下,您可以使用32位定點實現與單精度浮點相同的精度,並提高大約一個數量級。在執行時間。我還看到編譯器為FPU拖了相當多的庫開銷。

+1可能會有用,請注意,Cortex M4支持打包數據類型和DSP指令(例如,乘累加),即使您有固定的工作點(即使有FPU),也可以為您帶來巨大的改進。還要注意,STM32F7具有雙精度FPU,但是由於它仍然是32位系統,因此您會更快地遇到內存瓶頸。
+1有趣的點。我不認為FPU可能僅支持單精度浮點而不支持雙精度這一事實。
@gberth這在許多微米上很常見。FWIW還需要對整數有同樣的關注。16位和32位微控制器不支持64位整數,有些不支持小於字長的整數。所有這些操作都必須由編譯器處理,而不是作為原子指令處理。
awjlogan
2020-04-16 20:23:41 UTC
view on stackexchange narkive permalink

如果您購買的是帶有硬件FPU的處理器,那麼您就不會對精度*,可重入行為等產生同樣的擔憂。請繼續使用它們!

雖然有思想:

  • 您可能會認為處理器在不使用時可以關閉(大型)FPU,因此請檢查運行FP例程是否可以節省(如果您在意)軟件方面的功能。

  • 取決於實現方式,FPU的內核可能還具有不同的寄存器-有時編譯器可以巧妙地使用它們。

  • 不要將FPU用作劣質固件設計的拐杖。例如,您可以在定點上做同樣的事情,而改用普通核心嗎?

(* FPU應該符合給定的標準實現,因此請注意由此產生的任何限制。)

+1用於功耗。我雖然沒有這個權衡。謝謝。
請注意,諸如“ FPU使用更多功率”之類的盲目假設通常會使您陷入麻煩。與僅使用內置的高效硬件FPU相比,使用定點例程模擬FPU操作可能會花費更多的周期,因此會消耗更多的功率。而且,儘管我確定還有一些未知的地方,但我見過的所有帶有硬件FPU的MCU都有專用的浮點寄存器(即,它們不與“核心”整數寄存器重疊)。但是,我絕對同意,浮點數不應用作不良設計的關鍵。使用正確的工具完成工作。
@CodyGray-絕對是,這就是我說“檢查”的原因:)如果您使用的是FP,那麼FPU可能會更省電,但是如果您僅執行一次操作,它可能就沒有。Micros現在是如此先進,在很多地方假設都會使您誤入歧途-儘管這一切都很好玩!
Ralf Kleberhoff
2020-04-16 20:27:53 UTC
view on stackexchange narkive permalink

某些擔憂仍然存在。

  • 浮點運算本質上比整數運算更密集。但是對於浮點單元,您可能不會再注意到了,可能會再增加一些cpu週期或更多的功耗。
  • 操作是原子性的,因此不必擔心。
  • 精度/舍入/比較問題仍然存在,與軟件計算完全一樣。

尤其是後一種可能會引起非常討厭的問題,並迫使您編寫非直觀的代碼,例如始終與範圍進行比較,從不針對固定值測試相等性。

請記住,單精度浮點數只有23位分辨率,因此您可能需要用雙精度浮點數替換32位整數。

+1對於“非直觀代碼”的提及。即使我的問題主要集中在性能和健壯性上,我也認為考慮代碼的清晰性很重要。
關於“非直觀代碼”,這僅意味著為您正在執行的操作選擇適當的類型。如果您的值不准確(例如測量電壓),則準確比較本質上是錯誤的。使用公差並不會使代碼變得不直觀,而是使它*正確*。
關於更多的計算密集型:使用專用硬件,這仍然可能導致功耗增加。
同意格雷厄姆。由於完全相同的原因,將整數毫伏的電壓進行比較可能是錯誤的。也就是說,您的ADC可能會產生整數測量值。
如果您使用整數進行計算,則浮點運算本質上比整數要耗費大量計算資源。如果您的問題是浮點數,那麼經過高度調整的FPU將在軟件中進行所有擴展時變得更快。這就是存在FPU的原因。
您能否舉一個浮點運算是原子的架構示例?x86或ARM均不提供此類保證。我不太熟悉MIPS和其他更深奧的體系結構。但是總的來說,您不能假設* any *運算將是原子的,包括整數運算。由於內存總線的限制,即使是諸如加載或存儲之類的簡單操作也經常被分解為多個操作(在32位MCU上,您無法將64位雙精度浮點值作為原子操作加載)。
jonathanjo
2020-04-16 22:05:47 UTC
view on stackexchange narkive permalink

如果您有FPU,通常可以進行計算,而且權衡很容易理解。

但是請注意輸出。如果您擁有C庫之類的東西,您會驚訝於 printf(“%0.6g”,x);固有的複雜性; 我見過使用 malloc的庫()放在 printf()中,而這並不是您在微控制器中想要的。

不幸的是,該標準要求使用“ double”類型輸出printf-family函數的浮點參數,即使在只有32位浮點硬件的平台上,該類型也必須至少為48位寬。
它使用全局變量(共享狀態)嗎?
這是關於在針對嵌入式MCU時需要注意腫的C std lib實現的一個很好的觀點。不幸的是,它不僅限於浮點運算。在嚴重受限的環境中,諸如printf之類的事情可能絕對是一場災難。幸運的是,您不會在生產中使用它。如果您完全使用它,它將用於調試,而性能並不重要。當您關心性能時,您需要非常了解C編譯器正在生成或自己編寫asm。但是,通常情況下,您不在乎自己的想像。
Ron Beyer
2020-04-16 20:23:55 UTC
view on stackexchange narkive permalink

老實說,這是一種微優化,您應該在擁有完全正常工作的代碼庫之後 進行。一些MCU也存在除法問題,即使是整數也是如此。因此,執行“將fp乘以100,進行一些操作,然後除以100”之類的操作可能要比僅操縱浮點型花費更多的時間。

這是剖析出現的地方,您需要選擇自己的戰鬥,沒有答案。有了可用的代碼庫後,您可以確定瓶頸並有選擇地進行優化。避免使用籠統的聲明會導致微優化,這比實際節省了更多的時間來編寫代碼。從每小時運行一次的低優先級例程中優化出浮點數是沒有用的,而針對繁重的任務優化出浮點數很有用。

希望如果您要進行定點運算,則應使用2的冪而不是10的冪。而不是“乘以100,運算,除以100”,而應乘以128,運算並除以128。我不知道任何不能有效除以128的MCU。
@ThePhoton是的,這只是一個簡單的示例,我認為一個好的編譯器甚至可以在將其到處理器之前將/ 128優化為>> 8,我只是選擇了一個任意值。
用常數除並不比乘法難得多。可以根據乘法和移位來完成,例如比較。[這兩個功能]的ARM代碼(https://gcc.godbolt.org/z/SMdGvH)。好吧,至少當您有一條指令進行乘法(32位,32位)→64位時,這是正確的。
@Ruslan:不幸的是,許多Cortex-M0內核都包含用於單週期32x32-> 32乘法的硬件,但沒有有效的方法來計算32x32產品的高位字。我認為可以執行四周期乘法運算以產生結果的上半部或下半部的硬件會更便宜,更有用,但可能對市場部門沒有吸引力。我也覺得有些奇怪,芯片供應商可以請求一個週期或32個週期的乘法,但是沒有選擇〜18個週期的乘法,這幾乎和32個週期的便宜一樣。
Justme
2020-04-16 20:51:36 UTC
view on stackexchange narkive permalink

基本上沒有必要避免使用FPU,並且RTOS也支持FPU的上下文切換。如果沒有FPU,精度問題仍然存在。如果您有性能的話,也可以自由使用不帶FPU的浮點數-偶爾在不帶FPU的Cortex-M3上調試float變量就可以了。但是很顯然,在內存較小的有限8位MCU上,即使使用單個float操作也會帶來數百字節的軟float庫代碼的開銷,因此有時使用float沒有意義。

假設FPU操作是原子的,RTOS需要做哪些額外的工作來支持浮點數?
當然,這取決於FPU編程模型-但是,當RTOS從一個任務切換到另一個任務時,它必須將所有CPU寄存器保存在某個位置(任務結構),並還原下一個任務的所有CPU寄存器。對於FPU,任務切換器也必須存儲和加載FPU寄存器,因此任務切換過程必須存儲和還原更多的寄存器。
如果CPU支持惰性上下文切換(例如Cortex-M4)(http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0298a/DAFJBFJF.html),則RTOS不會這樣做。不必一定要做FPU存儲並在上下文切換上加載。
Lundin
2020-04-22 19:07:30 UTC
view on stackexchange narkive permalink

何時不使用浮點數

第一個需要意識到的是 不是 的浮點表示“我需要小數”。在這裡,有95%的潛在嵌入式程序員濫用浮點運算會失敗。

解決該懷疑的方法是在內部認識到,程序應該使用對 MCU 有意義的單元,而不是對人類有意義的單元。

例如,如果使用片上10位ADC以mA為單位測量電流,則在軟件中使用的方便單位是“定點原始ADC值從0到1024”。在C編程中,這意味著 uint16_t 或可選的 uint_fast16_t 。不是 int ,當然也不是 float

在固件計算中使用單位mA僅在無法處理抽象單位的情況下,對於人類程序員的大腦來說非常方便。但這對程序來說很不方便,因為這意味著您需要將所有讀數重新調整為比例,並可能在這樣做時增加舍入誤差。再加上縮放代碼只是開銷腫。而且可能包括除法,這對於許多MCU來說可能是痛苦的。

是的,您正在讀取以mA為單位的電流。但是,除非您實際上需要在顯示器上打印該電流或將其打印給人類用戶,否則該設備實際上無濟於事。在設計算法時,請在筆和紙上進行mA重新縮放,而不是將其拖到固件中。


何時使用浮點

  • 如果您的MCU具有FPU,並且您實際上需要執行高級數學,那麼您應該使用浮點數。否則,您不應該。

“高級數學”並不一定意味著從程序員的角度出發,而是從軟件的角度來看。“高級”包括諸如平方根,幾何或三角學,通常使用 math.h ,複數,AI數學等。在定點實現時會很痛苦。

Sascha
2020-04-18 20:46:58 UTC
view on stackexchange narkive permalink
  • 我將從原因列表中排除非原子操作,因為您處理其他復雜結構的方法也可以應用於浮點操作。

  • 我還將從原因列表中排除所需的計算能力,因為這是高度針對應用程序和處理器的。

讓我們關注由浮動精度變化引起的“非顯而易見”問題

  • 最基本的一個是FP算法是非關聯的,(a + b)+ c不等於a +(b + c)。假設a = 1,b = -1,c = 1e-20。聽起來無害,但假設您的應用程序使用了有限脈衝濾波器,並運行了一個假設為零的測試用例

  • 對我來說,
  • 的第二個原因是整數和固定逗號運算在我期望的範圍內發生溢出(好的,默認情況下未在C中啟用)。例如在浮點中,積分器可以輕易地跑到非常大的值,而無需任何人注意...

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

就像很多事情一樣,這取決於您的用例。

如果您知道輸入值的允許範圍,則定制整數算法可能會更有效率(例如,乘以)。隨著這些值的擴大,無論您編寫什麼代碼,都將退化為浮點乘法,而沒有硬件的好處。

通過在後台循環中運行嵌入式系統或確定具有不同處理器的慢速計算可能難以確定嵌入式系統的時序。控制和計算。



該問答將自動從英語翻譯而來。原始內容可在stackexchange上找到,我們感謝它分發的cc by-sa 4.0許可。
Loading...