15 ms·
Should array length be stored into a local variable in C#?
- sshine 7y agoTL;DR: No.
- potiuper 7y agoTL;DR: Yes & use foreach rather than for to skip array bound checks.
- deleted 7y ago[deleted]
- vardump 7y agoYeah. A slight oops from CLR optimizer, but nothing serious. It seemed to miss a hoisting optimization opportunity in the foreach case.
- Gibbon1 7y agoLong time ago I decided that you shouldn't second guess the compiler without a reason. So just avoid worrying about local optimizations and focus on your data structures and algorithms.
- deleted 7y ago[deleted]
- yc12340 7y agoIt is odd, that C# needs foreach loop for bound check elimination. Java supports this optimization for a wide range of loop types... since Java 7, I think. Normally I would argue, that Java is just ahead of curve, but Android has also gained supports for bounds check elimination in 2014-2015. Either article does not tell us whole truth or Microsoft JIT is subpar by modern standards.
- gameswithgo 7y agoc# does not need a foreach loop for bound check elimination.
- gameswithgo 7y agoit will skip the array bounds check as long as myArray.Length is the terminal condition in the for loop, rather than a local variable with the length stored.
- suff 7y agoIt is very possible the reason is not speed, but readability. If you simply named it 'length', then sure, there is no point. If it is given a better, more descriptive name, and then gets used in an equation elsewhere in the code, then it may be very useful because it is easier to read.
- JustSomeNobody 7y agosomething like: var widgetLength = widget.Length; ?
- SketchySeaBeast 7y agoI think that's confusing - it could be referencing a different object names "widgetLength" how about widgetDotLength to be more clear?
- JustSomeNobody 7y agoYou're right. Let's go with yours. When can you push that?
- SketchySeaBeast 7y agoOh that'll be at least next week, I'm currently busy changing all the "int" declarations to "wholeNumberDataType" for clarity.
- 60654 7y agoNice benchmarking on Microsoft .Net CLR. Looks like the JIT compiler is smart enough to recognize that array.Length is an invariant and hoist it out of the loop, which is awesome for the common use cases! One nitpick about the title: C# runs on more runtimes than Microsoft .Net CLR, and those may behave very differently. For example: Mono CLR, or Unity's IL2CPP which is an ahead-of-time compiler. Specifically, I'd expect IL2CPP would not hoist length out, because it would not recognize it as an invariant. (Some great examples of IL2CPP cross compilation are here: https://jacksondunstan.com/articles/4749 https://jacksondunstan.com/articles/4749 ) TLDR: the Microsoft JIT compiler makes the local variable unnecessary, but this is a property of the JIT, not of C#. Developers on non-MS platforms shouldn't assume this.
- chrisseaton 7y ago> those may behave very differently They shouldn’t behave any differently should they? There’s a single language spec.
- tomsmeding 7y ago"Behave very differently" is meant with regards to performance, here; or in other words, things that are not observable in the language semantics.
- pjmlp 7y agoJava's language spec also doesn't dictate how escape analysis or remapping from classes into structs gets done, for example.
- deleted 7y ago[deleted]
- OskarS 7y agoThis is good to know, but fairly unsurprising. The interesting case is List<T>. The .Count property is actually a function call, and the value could change during the loop. If you don’t mutate the list, is it smart enough to both inline the function call and hoist the value out as an invariant?
- zamalek 7y agoThere's no mathematical way for it to know that you won't modify (especially remove). The developer would have to express that by, say, iterating over the internal array directly.
- ygra 7y agoThis is veering into C and undefined behavior territory, but with List<T> there's a guarantee that you cannot modify the list while enumerating over it, so could the compiler theoretically assume that when using foreach?
- thrower123 7y agoI assume it does, if you try to do so it throws an exception. It's a rather common footgun with multi-threaded code until you've lost a few toes to it. People are always writing naive producer-consumer queues with an unlocked List<T> backing it until they learn better.
- josteink 7y ago> with List<T> there's a guarantee that you cannot modify the list while enumerating over it, so could the compiler theoretically assume that when using foreach? No such guarantee exists. The only guarantee there is is that enumeration will throw on next iteration after a modification. The compiler can assume nothing.
- antisemiotic 7y ago>There's no mathematical way for it to know that you won't modify That's an overstatement. That's currently impossible in C#, but Microsoft is working on it in F*: https://www.fstar-lang.org https://www.fstar-lang.org I hope some of that will trickle down to F# and C# some day.
- cr0sh 7y agoI'm not a C# developer, but this kind of thing seems to permeate almost all languages in one form or another. Maybe it is just a style and readability thing; or maybe (as suggested elsewhere as well) it is meant to be reused elsewhere in the system, so it is cached in a variable for later use. Or, it's possible that at one time - maybe early in the early days of .NET - doing it this way was more optimized, and the habit stuck with developers (perhaps they all read the same article in the knowledge base about it?). If that's the case, it's a bit of "premature optimization", but one that doesn't apparently harm anything. What I do wonder is if certain other changes could change the speed? At least it might be interesting to see in these trivial cases; I admit that in more complex loops it might not be advisable. But - for instance, what if rather than iterating thru the array from the 0th element to the length of the array, you instead started from the last element and iterated backwards, until you hit zero? That way, you wouldn't be checking the length of the array, but rather for zero? The code for such a test might look like: public int WithoutVariable() { int sum = 0; for (int i = array.Length - 1; i > -1; i--) { sum += array[i]; } return sum; } I'm not sure that a "with variable" version would make much difference (or sense), but here it is for completeness sake: public int WithVariable() { int sum = 0; int length = array.Length - 1; for (int i = length; i > -1; i--) { sum += array[i]; } return sum; } Again - I'm not a C# developer - maybe my code is wrong above, but hopefully it gets the idea across. Would this work better? Would it be faster? What would the JIT compiler create? Maybe it wouldn't be any faster or better than the ForEach examples? I honestly don't know - but if anybody wants to give it a shot, I'd be curious as to the results... EDIT: I noticed that I said "checking for zero" - but I modified my code to check for -1 as the boundary; I suppose the check in the loops could be modified to be "i == 0;" instead. I'm not sure if whether doing an "i >= 0;" vs "i == 0;" vs "i > -1;" which is faster - another thing to check, I suppose...
- Someone 7y ago”what if rather than iterating thru the array from the 0th element to the length of the array, you instead started from the last element and iterated backwards, until you hit zero?” That used to be common in assembly, as it leads to smaller and faster code on many systems. See https://stackoverflow.com/questions/2823043/is-it-faster-to-count-down-than-it-is-to-count-up https://stackoverflow.com/questions/2823043/is-it-faster-to-..., which also shows how times have changed, with many answers calling this premature optimization.
- germanlee 7y agoIf I remember correctly, the runtime keeps size of the array in the header of the object along with sync block, etc. If you have VS, you can view the object in memory to see the sync block value, array size value, etc.
- laurent123456 7y agoSaving the array length to a variable is one of those things that inexperienced programmers love to do, thinking it will optimise something.
- vips7L 7y agoIt honestly depends on the compiler.
- stult 7y agoIt's a great default assumption when you don't know all the quirks of the specific language or compiler, because either it will help you or at least won't hurt you.
- laurent123456 7y agoIt might hurt if you add this extra variable to every loop and at some point the array length changes within the loop. It also makes the code more verbose. This optimisation should be done like all optimisations: first you benchmark and then see if it makes sense to make this change. Most of the time there's no point doing so.
- ajnin 7y agoAvoiding doing unnecessary work is something all developers should strive to, experienced or not. Maybe in that case that won't make any difference. But, say, a method call in a loop often sould be done outside the loop. Doing it here but not there leads to inconsistent style. Also depending on the language and compiler that might actually make a difference sometimes. I'm not going to write code that assumes a certain interpreter/compiler behavior.
- coinerone 7y agoAt the university, every time i put the array length into a local variable for a loop, i got 2 points deduction on my Homework.
- jay_kyburz 7y agoAhh, nice to see some c# without a new line before the braces.
- cutler 7y agoNow if they would only drop Pascal case so that I can distinguish a method from a class I might give it a shot.
- thrower123 7y agoThat is probably my least favorite thing about C# coding. The preeminent style wastes so much vertical space. K&R braces for me.
- artofcode 7y agoarchive.org has a mirror in case the site is still hugged to death: https://web.archive.org/web/20190606120130/https://habr.com/en/post/454582/ https://web.archive.org/web/20190606120130/https://habr.com/...
- ducttape12 7y agoAlways accessing array.Length is defensively coding. In the event your array is mutated, always accessing array.Length ensures you won't run into an Index Out of Bounds exception. Even better is to just avoid accessing the array's length. I almost always use foreach or Linq.
- gameswithgo 7y agoThat is orders of magnitude slower and allocates. Of course you can use: https://github.com/jackmott/LinqFaster https://github.com/jackmott/LinqFaster
- gwbas1c 7y agoWrong From the article: > It also turned out that Foreach often walks through the array faster than For
- merb 7y ago1. `ForEach` is slower. 2. `foreach` is faster. 3. Linq is slower.
- ducttape12 7y agoI write first for readability and performance second. Not to say that performance isn't important (and of course I pay attention to O notation), but throwing a little more memory and CPU power at a web page is far cheaper and easier than spending developer time reading code.
- patsplat 7y agoUse Linq and forget about array length. It's an interesting analysis and all, but why bother when the language has such an elegant collections API.
- gameswithgo 7y agoLinq is orders of magnitude slower and allocates. It is not always an appropriate choice.
- roetlich 7y agoDo you have data for that? Whenever I tried to find actual data for this Linq seemed to be slightly slower, but not too much. Examples: https://codereview.stackexchange.com/questions/14197/is-the-linq-version-faster-than-the-foreach-one https://codereview.stackexchange.com/questions/14197/is-the-... https://wheresmykeyboard.com/2015/06/linq-lambda-loop-performance-test/ https://wheresmykeyboard.com/2015/06/linq-lambda-loop-perfor...
- gameswithgo 7y agoYes. The total slowdown you get will depend on how much of the work being done is the actual iteration vs the work inside the loop. If you are doing a long running thing each time you go through the loop, then the overhead of the linq iteration is not so bad But something like: var sum = values.Sum(x => x * x); will take ~10x longer than an for or foreach loop equivalent. (260ms vs 36ms on 32 million float32s on my machine, measured by benchmarkdotnet) plus a small allocation
- smt88 7y agoYou're right, but for most people writing C#, the developer time saved by Linq (being easier to read and understand quickly) is much more valuable than the tiny amount of CPU time that's lost.
- roetlich 7y agoThanks for your reply. I'm fairly new to C#, and was hoping that linq would make my life easier. Oh well. I made some benchmarks myself: https://pastebin.com/5vQNpbPC https://pastebin.com/5vQNpbPC And esp. the Sum is a lot slower in linq. Not quite an order of magnitude, but pretty bad. Even worse: float sum = 0; arr.Select(x => (sum += x * x)).ToList(); return sum; This is somehow still a lot faster than the normal linq Sum. What does .Sum() do to be this slow? Edit: I just noticed you also wrote this blog post on the topic: https://jackmott.github.io/programming/2016/07/22/making-obvious-fast.html https://jackmott.github.io/programming/2016/07/22/making-obv... I should have read that earlier!