Performance optimization for-loop / switch-statement

please help me determine which of the following options is more optimized code?

for(int i=0;i<count;i++) { switch(way) { case 1: doWork1(i); break; case 2: doWork2(i); break; case 3: doWork3(i); break; } } 

OR

 switch(way) { case 1: for(int i=0;i<count;i++) { doWork1(i); } break; case 2: for(int i=0;i<count;i++) { doWork2(i); } break; case 3: for(int i=0;i<count;i++) { doWork3(i); } break; } 

In the first case, the overhead is always , as a rule, performed with checking the state of the switch case at each iteration. In the second case, there is no overhead. I feel the second case is much better. If anyone has another way around the problem, please help me by suggesting it.

+6
source share
7 answers

A switch at low adjacent values ​​is insanely fast - this type of transition has a very optimized processing. Honestly, what you ask for will make no difference in the vast majority of cases - anything in doWork2(i); it's going to swamp; hell virtual call itself may lure him.

If it really, really, really matters (and I'm struggling to think about the real scenario here), then: measure it. In any scenario where this is noticeable, only the way to measure it will be with your actual, accurate code - you cannot generalize pico-optimisations.

So:

  • It does not matter.
  • measure
  • It does not matter.
+5
source

You can do something like:

 Func(void, int> doWork; switch(way) { case 1: doWork = doWork1; break; case 2: doWork = doWork2; break; case 3: doWork = doWork3; break; } for (int i=0;i<count;i++) { doWork(i); } 

(The code written here may not quite compile, just to give you an idea ...)

+2
source

I asked myself questions for optimization

  • First of all, how big is the figure? Is it 1,2,10, 1,000,000,000?
  • How powerful will the machine be that will run the code?
  • Should I write less code?
  • Does anyone read this code after I write it? If so, how professional is it?
  • What am I missing? Time? Speed? Something else?
  • What is a way ? Where do I get this from? What are the probabilities of way being 1 or 2 or 3?

Obviously, the first code fragment will go for the switch part until i is counted, but how big is the figure? If this is not a very large number, does it not matter? If it is too big, and you get a very slow time, it is useless. However, as I said, if you want readability and can guarantee that the quantity is small, why not use the first? This is much more readable than the second, and there is less code that I like.

The second snippet looks uggly, but should be preferred if count is a huge amount.

+2
source

You have to measure it to see if it is worth optimizing or not (I am very sure that it is not ). Personally, I prefer the former for readability and brevity (less code, less error prone, more dry ").

Here's another approach, which is even more concise:

 for(int i = 0; i < count; i++) { doAllWays(way, i); // let the method decide what to do next } 

All the "paths" seem to be issued, otherwise they will not appear in the same switch . Therefore, it makes sense to stratify them with one method that makes switch .

+1
source

In fact, it may be somewhat faster, despite some comments here.

Let me actually check it out:

 using System; using System.Diagnostics; namespace Demo { class Program { static void Main(string[] args) { int count = 1000000000; Stopwatch sw = Stopwatch.StartNew(); for (int way = 1; way <= 3; ++way) test1(count, way); var elapsed1 = sw.Elapsed; Console.WriteLine("test1() took " + elapsed1); sw.Restart(); for (int way = 1; way <= 3; ++way) test2(count, way); var elapsed2 = sw.Elapsed; Console.WriteLine("test2() took " + elapsed2); Console.WriteLine("test2() was {0:f1} times as fast.", + ((double)elapsed1.Ticks)/elapsed2.Ticks); } static void test1(int count, int way) { for (int i = 0; i < count; ++i) { switch (way) { case 1: doWork1(); break; case 2: doWork2(); break; case 3: doWork3(); break; } } } static void test2(int count, int way) { switch (way) { case 1: for (int i = 0; i < count; ++i) doWork1(); break; case 2: for (int i = 0; i < count; ++i) doWork2(); break; case 3: for (int i = 0; i < count; ++i) doWork3(); break; } } static void doWork1() { } static void doWork2() { } static void doWork3() { } } } 

Now this is completely unrealistic, since the doWork () methods do nothing. However, this will give us base time.

The results that I get to build RELEASE on my Windows 7 x64 system are as follows:

 test1() took 00:00:03.8041522 test2() took 00:00:01.7916698 test2() was 2.1 times as fast. 

Therefore, moving the loop to the switch statement makes it MORE THAN TWICE AS FAST.

Now let's make it a little more realistic by adding some code to doWork ():

 using System; using System.Diagnostics; namespace Demo { class Program { static void Main(string[] args) { int count = 1000000000; Stopwatch sw = Stopwatch.StartNew(); for (int way = 1; way <= 3; ++way) test1(count, way); var elapsed1 = sw.Elapsed; Console.WriteLine("test1() took " + elapsed1); sw.Restart(); for (int way = 1; way <= 3; ++way) test2(count, way); var elapsed2 = sw.Elapsed; Console.WriteLine("test2() took " + elapsed2); Console.WriteLine("test2() was {0:f1} times as fast.", + ((double)elapsed1.Ticks)/elapsed2.Ticks); } static int test1(int count, int way) { int total1 = 0, total2 = 0, total3 = 0; for (int i = 0; i < count; ++i) { switch (way) { case 1: doWork1(i, ref total1); break; case 2: doWork2(i, ref total2); break; case 3: doWork3(i, ref total3); break; } } return total1 + total2 + total3; } static int test2(int count, int way) { int total1 = 0, total2 = 0, total3 = 0; switch (way) { case 1: for (int i = 0; i < count; ++i) doWork1(i, ref total1); break; case 2: for (int i = 0; i < count; ++i) doWork2(i, ref total2); break; case 3: for (int i = 0; i < count; ++i) doWork3(i, ref total3); break; } return total1 + total2 + total3; } static void doWork1(int n, ref int total) { total += n; } static void doWork2(int n, ref int total) { total += n; } static void doWork3(int n, ref int total) { total += n; } } } 

Now I get the following results:

 test1() took 00:00:03.9153776 test2() took 00:00:05.3220507 test2() was 0.7 times as fast. 

Now SLOWER puts the loop in the switch! This counterintuitive result is typical of these things and demonstrates why you should ALWAYS run time tests when you try to optimize your code. (And, as a rule, you should not do optimizing such code if you have no good reason to suspect that there is a bottleneck. You better spend time cleaning up your code.))

I did some other tests, and for the slightly simpler doWork () methods, the test2 () method was faster. This very much depends on what the JIT compiler can do with optimization.

NOTE. I think the reason for the speed differences for my second test code is because the JIT compiler can optimize the 'ref' calls when inlaying doWork () calls when they are not in a loop, like in test1 (); whereas for test2 () it cannot (for some reason).

+1
source

The second method is more efficient; You must complete the full cycle of the cycle independently. But in the first method, you uselessly repeat the case count statement.

0
source

assuming you have a performance issue here (since in most cases the switch is really very fast):

If you are concerned about the switch statement, I suggest refactoring here.

The switch can easily be replaced with a strategy template (since the switch value does not change in for loops, there is no need to switch at all).

The real goal of optimization is those designed for loops, but without context it’s hard to say what can be done about this.

See below for more information on refactoring switches (for example, in a strategy template) CodeProject Refactoring article

0
source

Source: https://habr.com/ru/post/919012/


All Articles