Laden...

Was ist schneller: Math.Abs(a)<b vs. a<b && a>-b

Erstellt von Quaneu vor 13 Jahren Letzter Beitrag vor 13 Jahren 3.399 Views
Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 13 Jahren
Was ist schneller: Math.Abs(a)<b vs. a<b && a>-b

Hallo,

wie im Titel schon gesagt, würde mich interessieren was schneller ist:


double a;
double b;
...

if(Math.Abs(a)<b)
{
...
}

//oder:

if(a<b && a>-b)
{
...
}

Da ich diese Abfrage sehr sehr oft habe und ich nun daran interessiert bin meinen Code zu optimieren.

Viele Grüße

Quaneu

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

es hindert dich nichts dies selbst auszuprobieren und die Lösung hier zu posten 😁

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Gelöschter Account
vor 13 Jahren

solange es dabei nciht extrem riesige unterschiede in der performance gibt (was mich sehr sehr wundern würde...), solltest du diese art von optimierung wegdenken..... das ist evil

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

solange es dabei nciht extrem riesige unterschiede in der performance gibt (was mich sehr sehr wundern würde...)

Stimmt. Es gibt auch kaum einen Unterschied. Das sind sehr elementare Operationen und da gibts nicht viel zu holen.

Verwende die Math.Abs-Variante -> ist m.E. besser lesbar und auf den 1. Blick sofort erkenntlich was gemacht werden soll.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 13 Jahren

Hatte gehofft, dass schon wer Erfahrung gesammelt hat.

Hab das jetzt mal so getestet:


            double a;
            int i = 0, s = 0;
            long abs_Time = 0;
            long and_Time = 0;
            Random ran;
            Stopwatch _watch = new Stopwatch();



            for (s = 0; s < 100; s++)
            {
                _watch.Start();
                for (i = 0; i < 50000; i++)
                {
                    ran = new Random(i);
                    a = ran.NextDouble() - 2;
                    if (Math.Abs(a) < 0.5)
                    {
                        a += ran.NextDouble();
                    }
                }
                _watch.Stop();
                abs_Time += _watch.ElapsedMilliseconds;



                _watch.Start();
                for (i = 0; i < 50000; i++)
                {
                    ran = new Random(i);
                    a = ran.NextDouble() - 2;
                    if (a < 0.5 && a > -0.5)
                    {
                        a += ran.NextDouble();
                    }
                }
                _watch.Stop();
                and_Time += _watch.ElapsedMilliseconds;
            }
            MessageBox.Show((abs_Time/100).ToString() + "\n" + (and_Time/100).ToString());

Das Ergebnis war :


47929
48408

Wobei ich gesagt hätte, dass die and-Methode schneller wäre... oder teste ich falsch?

UPDATE:
Sowohl im Debug als auch im Release sind die Ergebnisse die gleichen.

Daher werde ich euren Ratschlag gerne annehmen und weiter mit Math.Abs() arbeiten.

Vielen Dank

2.891 Beiträge seit 2004
vor 13 Jahren

oder teste ich falsch?

Erstens erzeugst du die Zufallszahlen während der Zeitmessung (verfälscht das Ergebnis), zweitens sind die Eingabemengen für beide Funktionen nicht gleich (verfälscht das Ergebnis) und drittens - am gravierendsten - du resettest die StopWatch nicht.

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

oder teste ich falsch?

Ja. Du erzeugts immer ein neues Random-Objekt innerhalb der Messung (das wäre auch sonst nicht notwendig). Somit kann eine eventuelle GC die Messung verfälschen.

Siehe auch dN!3Ls-Antwort.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

2.921 Beiträge seit 2005
vor 13 Jahren

@Quaneu: Solche Unterschiede herauszufinden, ist immer interessant, vor allem natürlich wenn es um die Performance geht. Oft hilft dabei auch ein Blick in den IL-Code (Byte-Code).

s. dazu auch:

Disassemblieren von Projekte, ganz einfach mit ILDASM [VS2005 Deutsch]

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo dr4g0n76,

Oft hilft dabei auch ein Blick in den IL-Code (Byte-Code).

Und in den Maschinencode (CLR-Debugger - Release-Modus) zum Abschätzen der Operationen die für eine Aufgabe notwendig sind. Der IL-Code ist dabei oft noch auf eine zu hohen Level.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

2.921 Beiträge seit 2005
vor 13 Jahren

@Gfoidl: Das wäre dann meiner Meinung nach der nächste Schritt für ihn gewesen.

😉

für z.B.


public void Test1()
{
    int a = 7;
    int b = 2904;

    bool bTest1 = Math.Abs(a) < b;
}

kommt heraus:


.method public hidebysig instance void  Test1() cil managed
{
  // Code size       20 (0x14)
  .maxstack  2
  .locals init ([0] int32 a,
           [1] int32 b,
           [2] bool bTest1)
  IL_0000:  nop
  IL_0001:  ldc.i4.7
  IL_0002:  stloc.0
  IL_0003:  ldc.i4     0xb58
  IL_0008:  stloc.1
  IL_0009:  ldloc.0
  IL_000a:  call       int32 [mscorlib]System.Math::Abs(int32)
  IL_000f:  ldloc.1
  IL_0010:  clt
  IL_0012:  stloc.2
  IL_0013:  ret
} // end of method Form1::Test1

und für test2:


public void Test2()
{
      int a = 7;
      int b = 2904;
      bool bTest2 = a < b && a > -b;
}


.method public hidebysig instance void  Test2() cil managed
{
  // Code size       23 (0x17)
  .maxstack  2
  .locals init ([0] int32 a,
           [1] int32 b,
           [2] bool bTest2)
  IL_0000:  nop
  IL_0001:  ldc.i4.7
  IL_0002:  stloc.0
  IL_0003:  ldc.i4     0xb58
  IL_0008:  stloc.1
  IL_0009:  ldloc.0
  IL_000a:  ldloc.1
  IL_000b:  bge.s      IL_0014
  IL_000d:  ldloc.0
  IL_000e:  ldloc.1
  IL_000f:  neg
  IL_0010:  cgt
  IL_0012:  br.s       IL_0015
  IL_0014:  ldc.i4.0
  IL_0015:  stloc.2
  IL_0016:  ret
} // end of method Form1::Test2

EDIT: muss noch mal den richtigen Code nachliefern statement ist falsch.

EDIT: korrigiert.

Das wird aus Test 1 (Release):


        public void Test1()
        {
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  push        ebx  
00000006  sub         esp,40h 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-38h] 
0000000e  mov         ecx,0Bh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-1Ch],eax 
0000001e  mov         dword ptr [ebp-3Ch],ecx 
00000021  cmp         dword ptr ds:[04AC3D60h],0 
00000028  je          0000002F 
0000002a  call        75F68F91 
0000002f  xor         edx,edx 
00000031  mov         dword ptr [ebp-44h],edx 
00000034  xor         edx,edx 
00000036  mov         dword ptr [ebp-40h],edx 
00000039  mov         dword ptr [ebp-48h],0 
00000040  nop              
            int a = 7;
00000041  mov         dword ptr [ebp-40h],7 
            int b = 2904;
00000048  mov         dword ptr [ebp-44h],0B58h 

            bool bTest1 = Math.Abs(a) < b;
0000004f  mov         ecx,dword ptr [ebp-40h] 
00000052  call        FF87F03C 
00000057  mov         dword ptr [ebp-4Ch],eax 
0000005a  mov         eax,dword ptr [ebp-4Ch] 
0000005d  cmp         eax,dword ptr [ebp-44h] 
00000060  setl        al   
00000063  movzx       eax,al 
00000066  mov         dword ptr [ebp-48h],eax 
        }
00000069  nop              
0000006a  lea         esp,[ebp-0Ch] 
0000006d  pop         ebx  
0000006e  pop         esi  
0000006f  pop         edi  
00000070  pop         ebp  
00000071  ret              

und das aus Test 2 (Release):


        public void Test2()
        {
00000000  push        ebp  
00000001  mov         ebp,esp 
00000003  push        edi  
00000004  push        esi  
00000005  push        ebx  
00000006  sub         esp,40h 
00000009  mov         esi,ecx 
0000000b  lea         edi,[ebp-38h] 
0000000e  mov         ecx,0Bh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 
00000017  mov         ecx,esi 
00000019  xor         eax,eax 
0000001b  mov         dword ptr [ebp-1Ch],eax 
0000001e  mov         dword ptr [ebp-3Ch],ecx 
00000021  cmp         dword ptr ds:[04AC3D60h],0 
00000028  je          0000002F 
0000002a  call        75F68F09 
0000002f  xor         edx,edx 
00000031  mov         dword ptr [ebp-40h],edx 
00000034  xor         edx,edx 
00000036  mov         dword ptr [ebp-44h],edx 
00000039  mov         dword ptr [ebp-48h],0 
00000040  nop              
            int a = 7;
00000041  mov         dword ptr [ebp-40h],7 
            int b = 2904;
00000048  mov         dword ptr [ebp-44h],0B58h 
            bool bTest2 = a < b && a > -b;
0000004f  mov         eax,dword ptr [ebp-40h] 
00000052  cmp         eax,dword ptr [ebp-44h] 
00000055  jge         0000006B 
00000057  nop              
00000058  mov         eax,dword ptr [ebp-44h] 
0000005b  neg         eax  
0000005d  cmp         eax,dword ptr [ebp-40h] 
00000060  setl        al   
00000063  movzx       eax,al 
00000066  mov         dword ptr [ebp-4Ch],eax 
00000069  jmp         00000070 
0000006b  xor         edx,edx 
0000006d  mov         dword ptr [ebp-4Ch],edx 
00000070  movzx       eax,byte ptr [ebp-4Ch] 
00000074  mov         dword ptr [ebp-48h],eax 
        }
00000077  nop              
00000078  lea         esp,[ebp-0Ch] 
0000007b  pop         ebx  
0000007c  pop         esi  
0000007d  pop         edi  
0000007e  pop         ebp  
0000007f  ret              

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

2.891 Beiträge seit 2004
vor 13 Jahren

für z.B. [Math.Abs] kommt heraus

Wobei da ja viel interessanter ist, was Math.Abs macht. Im Double-Fall wird nämlich gar nicht "in IL weitergemacht", sondern es wird eine externe/native Funktion gerufen.

Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 13 Jahren

Hab dies extra so gemacht, das er mir nix wegoptimiert. Denn anders hat er die Schleifen anscheinend optimiert.

Und was würde mir dies jetzt sagen? Kenne mich da leider noch zu wenig aus.

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo,

Und was würde mir dies jetzt sagen?

Verwende die Math.Abs-Variante -> ist m.E. besser lesbar und auf den 1. Blick sofort erkenntlich was gemacht werden soll.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 13 Jahren

Dies wollte ich jetzt eh machen, jedoch würde es mich interessieren, was man aus diesem Code herauslesen kann um zu beurteilen dies oder dies ist "besser" (nur für die Zukunft)

2.921 Beiträge seit 2005
vor 13 Jahren

@dN!3L:

Math.Abs internals:


public static int Abs(int value)
{
    if (value >= 0)
    {
        return value;
    }
    return AbsHelper(value);
}

und AbsHelper:


private static int AbsHelper(int value)
{
    if (value == -2147483648)
    {
        throw new OverflowException(Environment.GetResourceString("Overflow_NegateTwosCompNum"));
    }
    return -value;
}

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 13 Jahren

Dann überrascht es mich noch mehr, dass beide "Methoden" fast gleich auf sind...

2.891 Beiträge seit 2004
vor 13 Jahren

@dN!3L: Math.Abs internals: [...]

Nein, es geht hier um die double-Überladung (nicht int!)


[MethodImpl(MethodImplOptions.InternalCall), SecuritySafeCritical]
public static extern double Abs(double value);

-> Wie schon gesagt wird das (m.E.) nativ gemacht und ich würde schon von daher erwarten, dass es schneller ist.

Beste Grüße,
dN!3L

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo Quaneu,

Dies wollte ich jetzt eh machen, jedoch würde es mich interessieren, was man aus diesem Code herauslesen kann um zu beurteilen dies oder dies ist "besser" (nur für die Zukunft)

Im Verlauf der Diskussion hat sich der Kreis zum Maschinencode (CLR-Debugger) fast geschlossen. Mit diesem kannst du den Maschinencode ansehen und dort gibt es keinen Unterschied zwischen managed-Code und native-Code mehr.

Dort geht es nicht unbedingt darum was jeder Befehl macht sondern zB wenn Variante 1 nur 10 Befehle benötigt, Variante 2 jedoch 100 Befehle kann davon ausgegangen werden dass Variante 1 schneller ist.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

Quaneu Themenstarter:in
692 Beiträge seit 2008
vor 13 Jahren

Ach so. Ich dachte das dann auch Funktionsaufrufe und so eine größere Rolle spielen.
Vielen Dank für die Infos

2.921 Beiträge seit 2005
vor 13 Jahren

@d3!NL:

Das seh ich im Release für die double Überladung:


            double a = 3.0f;
0000009a  fld         dword ptr ds:[04161A50h] 
000000a0  fstp        qword ptr [ebp-44h] 
            double b = 7.0f;
000000a3  fld         dword ptr ds:[04161A58h] 
000000a9  fstp        qword ptr [ebp-4Ch] 
            bool test1 = Math.Abs(a) > b;
000000ac  fld         qword ptr [ebp-44h] 
000000af  fabs             
000000b1  fstp        qword ptr [ebp+FFFFFF7Ch] 
000000b7  fld         qword ptr [ebp+FFFFFF7Ch] 
000000bd  fld         qword ptr [ebp-4Ch] 
000000c0  fcomip      st,st(1) 
000000c2  fstp        st(0) 
000000c4  jp          000000CA 
000000c6  jb          000000CE 
000000c8  jmp         000000CA 
000000ca  xor         eax,eax 
000000cc  jmp         000000D3 
000000ce  mov         eax,1 
000000d3  mov         dword ptr [ebp-50h],eax 

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

6.911 Beiträge seit 2009
vor 13 Jahren

Hallo Quaneu,

Funktionsaufrufe sind auf Maschinencode-Ebene nur ein Jump zu einer anderen Speicheradresse.

Funktionsaufrufe spielen schon eine Rolle und daher gibts es Compiler-Optimierungen wie Inlining bei denen ein Methodenaufruf durch den Code der Methode ersetzt wird.

Aber:
Premature optimization is the root of all evil! (Donald E. Knuth).
Programmiere so dass der Code lesbar, verständlich, testbar ist. Das bringt insgesamt mehr als (unnötige) Optimierungen.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

2.921 Beiträge seit 2005
vor 13 Jahren

@Quaneu: Hat dich das jetzt wirklich nur für double interessiert was schneller ist (s. Überschrift) oder allgemein der Ausdruck? Denn sonst müssten wir das für andere Fälle auch noch testen.

Quasi verschiedene Überladungen von Math.Abs(...) mitberücksichtigen.

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

2.891 Beiträge seit 2004
vor 13 Jahren

Programmiere so dass der Code lesbar, verständlich, testbar ist. Das bringt insgesamt mehr als (unnötige) Optimierungen.

Das möchte ich auch noch einmal unterstreichen. In den Tests hier wurde vielleicht herausgefunden, dass eine Variante schneller ist. Allerdings musst du auch den Kontext betrachten: Bei meinem Test ist Math.Abs fast doppelt so schnell wie die andere Varianten - allerdings: Bei 20.000.000 Wiederholungen war das ganze 200 Millisekunden schneller. Und die 200ms verlierst du an anderer Stelle mal ganz schnell z.B. durch IO.

Gruß,
dN!3L

1.002 Beiträge seit 2007
vor 13 Jahren

Hallo,

ein schönes Schlusswort: The Sad Tragedy of Micro-Optimization Theater

It. Just. Doesn't. Matter!

m0rius

Mein Blog: blog.mariusschulz.com
Hochwertige Malerarbeiten in Magdeburg und Umgebung: M'Decor, Ihr Maler für Magdeburg

49.485 Beiträge seit 2005
vor 13 Jahren

Hallo zusammen,

von mir noch ein Schlusswort auf der Meta-Ebene: Mir ist immer wieder unklar, warum solche Diskussionen - obwohl fast alle Beteiligten den Leitsatz "Premature optimization is the root of all evil!" kennen und auch in diesem Thread mehrfach darauf hingewiesen wurde - trotzdem noch in der Sache geführt werden. Wenn man "Premature optimization is the root of all evil!" beherzigt, muss man die Diskussion in der Sache überhaupt nicht führen und sollte das auch nicht, weil das verschwendete Energie ist. Man nimmt sinnvollerweise den lesbarsten Code und fertig. Selbst wenn man weiß, welcher Code schneller ist, ändert das ja überhaupt nichts!

herbivore