為什麼編譯器不能將foreach自動轉換為for

2021-10-19 09:19:13 字數 1863 閱讀 1534

在編寫c#**的時候,開發者會發現:使用c#的foreach迴圈的效能會比對應的for迴圈要稍微慢一些。

foreach迴圈結構

for迴圈結構

我想說的第一件事是:這個效能差異,實在是太微小了,以至於可以完全忽略掉。

可千萬別有這個想法:我如果把**中的所有foreach迴圈改寫為對應的for迴圈,程式的效能應該可以大大提公升。這是不會發生的,因為迴圈的開銷很少會出現在非基準程式(non-benchmark)花費大部分時間的地方。

今天我要說的主題不是如何通過放棄foreach迴圈來提高**執行效能。我今天的主題是回答這個問題:」為什麼編譯器不會將foreach自動轉換為相應的for,這樣**仍然是可讀的,而且還可以利提公升效能。」

原因是兩個迴圈的本質並不相同。

列舉的語義是:不允許在進行列舉時更改要列舉的物件。如果你這樣做,則列舉器將在下次相關呼叫時丟擲invalidoperationexception異常。另一方面,在for迴圈中,你可以隨意更改集合,這個是允許的。如果將專案插入到for迴圈內的集合中,則迴圈將繼續進行,並且取決於插入發生的位置,你可能會對專案進行兩次列舉。

如果編譯器將foreach更改為for,則以前會引發異常的程式現在可以正常執行。你是否認為這是一項」改進」呢?(根據實際應用場景,可能使程式崩潰比產生不正確的結果更好。)

現在,編譯器也許能夠分析出你沒有在迴圈內更改集合,但這項分析通常很難。例如,下面的迴圈**會更改集合嗎?

看起來上面的**並沒有改變集合。但是誰知道呢,萬一這個target類似於如下的物件呢:

啊哦,你可能根本不知道o.gethashcode()還會修改內部的arraylist。因為它看起來是如此」無害」的操作啊!

如果sneakycontainer類是來自另乙個程式集,則編譯器必須假設最壞的情況,因為編譯器根本不知道外部程式集的內部實現方法。

如果你覺得這還不夠混亂的話,那麼還有另乙個例子。

arraylist類未宣告為sealed。因此,有人可以重寫其ienumerable.getenumerator並返回非標準的列舉器。例如,這是乙個始終返回空列舉器的類:

你可能覺得:誰會那麼無聊會重寫列舉器呢?

好吧,這是一件很奇怪的事情,但是更普遍的是,開發者可以重寫列舉器,以便新增過濾器或更改列舉的順序。

現在,如果編譯器想要執行此優化,則不僅要證明列舉的物件未在列舉內部進行修改,還必須證明該物件確實是arraylist而不是可能具有重寫了getenumerator方法。

鑑於交叉彙編類的後期繫結性質,編譯器可以證明這些要求的情況的數量確實非常有限,以至於不太可能在不更改語義的情況下安全地執行**優化。

我對c#不是很熟悉,我的建議是:始終使用一種你最為熟悉的語法結構,並保持一致。

例如,總是使用if/else而不是switch,if語句總是新增大括號,比較表示式始終用括號括起來等。

這樣在寫**的時候,就會形成肌肉記憶,你都還沒思考,**就寫出來了。腦細胞活力max。

為什麼不能將權重初始值設為0

因為在誤差反向傳播法中,所有的權重值都會進行相同的更新。比如,在2層神經網路中,假設第1層和第2層的權重為0。這樣一來,正向傳播時,因為輸入層的權重為0,所以第2層的神經元全部會被傳遞相同的值。第2層的神經元中全部輸入相同的值,這意味著反向傳播時第2層的權重全部都會進行相同的更新。因此,權重被更新為...

為什麼不能無損反編譯?

從人的思路到機器 最後到cpu可以執行的指令。每一層經歷的都是乙個從概括到實現的過程。這也是工業的思路。上層來概括做什麼,下層來決定怎麼做。這也是人的慾望的思路,是或者說是人性中貪欲的思路。好了說的有點多。從乙個想法可以解釋為怎麼實現,但是給你一堆實現你能概括出思路卻很困難,這個困難也是反編譯之所以...

為什麼不能將類模板的宣告與類模板函式實現分開寫?

定義乙個類一般都是在標頭檔案中進行類宣告,在cpp檔案中實現,但使用模板時應注意目前的c 編譯器還無法分離編譯,最好將實現 和宣告 均放在頭 檔案中。如 test.h template class ctest test.cpp template t ctest getvalue template v...