Wie müssen wir zwei ganze Zahlen vergleichen?

8

Ich habe kürzlich ein Programm geschrieben, das ein Array sortiert. Dafür musste ich eine Vergleichsfunktion schreiben, die ich weitergeben werde. Meine Vergleichsfunktion sollte 1 (wenn x> y), -1 (wenn x <y) oder 0 (wenn x = y) zurückgegeben haben. Ich habe eine reguläre Funktion (Funktion 1) mit bedingten Ausdrücken geschrieben, aber es wurde mir empfohlen, anders zu schreiben (Funktion 2). Ist es besser so zu schreiben? Wird eine boolesche Bedingung immer 1 für die Wahrheit zurückgeben? (Ich meine, wenn x = 0 und y = 0 sind, haben wir immer (x == y) == 1?)

Funktion 1:

int Icmp(void* x, void* y)
{
    int a = *(int*)x;
    int b = *(int*)y;
    if (a > b)
        return 1;
    else if (a < b)
        return -1;
    else
        return 0;
}

Funktion 2:

int Icmp(void* x, void* y)
{
    return (*(int*)x > * (int*)y) - (*(int*)x < *(int*)y);
}
Skiv Hisink
quelle
5
Der nominelle Vorteil des zweiten ist, dass er bedingte Sprünge vermeidet und möglicherweise eine bessere Leistung erbringt. Die verwendete Notation ist schrecklich; Die Variablen sollten wie in der ersten lokalisiert und dann in der Berechnung verwendet werden.
Jonathan Leffler
4
Der zweite ist ein gängiger Trick, um ihn auf dummen Computern verzweigungslos zu machen. Kombinieren Sie die Lesbarkeit der ersten, um das Beste aus beiden Welten zu erhalten. Bearbeiten: dh wie Jonathan oben sagte
Antti Haapala
8
Beide Techniken funktionieren. Messen Sie den Leistungsunterschied sorgfältig. Es wird wahrscheinlich schwer zu erkennen sein. Bevorzugen Sie Klarheit, wenn der Unterschied gering ist.
Jonathan Leffler
1
Fragen Sie denjenigen, der Sie beraten hat, warum. Fragen Sie, in welchen Situationen die angegebenen Vorteile auftreten. Fragen Sie, warum diese Situationen für Sie von herausragender Bedeutung sein sollen. Wenn Sie auf all diese Fragen keine zufriedenstellenden Antworten erhalten, suchen Sie einen anderen Berater. Wenn möglich, sollten Sie die angegebenen Vorteile messen. Überlegen Sie sich, wie wichtig die Vorteile sind, die Sie selbst kennen, vor allem "Ich verstehe den Code" und "Ich denke, alle meine Kollegen verstehen den Code besser". Dann entscheide dich.
Yunnosch
2
@ JoëlHecht wenn es nicht 1 ist, dann ist es kein C-Compiler. Warum sollten Sie einen Nicht-C-Compiler verwenden, um C-Code zu kompilieren?!
Antti Haapala

Antworten:

14

Die bevorzugte Methode zum Schreiben des nicht verzweigten Codes wäre die Verwendung einer lokalen Variablen für die Operanden:

int icmp(const void *x, const void *y)
{
    int a = *(const int *)x;
    int b = *(const int *)y;
    return (a > b) - (a < b);
}

Der Ausdruck ist eine gängige Redewendung in Vergleichsfunktionen, und wenn er mit Variablen anstelle von In-Place-Zeiger-Dereferenzen geschrieben wird, ist er auch ziemlich lesbar.

Der Code beruht auf der Tatsache , dass das Ergebnis eines Vergleichs unter Verwendung >, <oder auch ==vom Typ intund entweder 1 oder 0. Dies wird durch die C - Standard erforderlich - beliebige Compiler, der Werte , wie 42 oder -1 ist definitionsgemäß kein C - Compiler generiert .

Es ist leicht zu erkennen, dass max. ein a > boder a < bkann zu einem bestimmten Zeitpunkt, und das Ergebnis ist entweder wahr sein 1 - 0, 0 - 1oder 0 - 0.

Warum verzweigungsloser Code? Obwohl Compiler für beide Funktionen möglicherweise genau denselben Code generieren, tun sie dies häufig nicht. Zum Beispiel scheinen sowohl das neueste GCC als auch das neueste ICC eine Verzweigung für die erste Funktion auf x86-64 zu generieren, aber verzweigungslosen Code mit bedingter Ausführung für letztere. Und an alle, die sagen, dass Filialen keine Rolle spielen, verweise ich Sie auf die am höchsten bewertete Qualitätssicherung, die jemals bei Stack Overflow durchgeführt wurde .

Antti Haapala
quelle
Dies ist der richtige Weg. Es ist sehr gut lesbar, der kompilierte Maschinencode ist identisch und es werden Verzweigungen zugunsten einer kleinen zusätzlichen Arithmetik vermieden. Der Verzweigungsprädiktor kann beim Sortieren großer Arrays, die sich in einem hohen Entropiezustand befinden, viele Leistungsprobleme verursachen. Dies vermeidet dieses Problem.
3ch0
5
Nur ein Hinweis, dass das Fehlen einer ifAnweisung nicht bedeutet, dass keine Verzweigung generiert wird, und dass es keine Verzweigung gibt, wenn eine ifAnweisung vorhanden ist . Es hängt in der Tat davon ab, ob der Compiler sie wegoptimieren kann.
G. Sliepen
@ G.Sliepen Eigentlich wäre ICC perfekt in der Lage, sie zu optimieren, aber es kam zu dem Schluss, dass der Zweig wahrscheinlich nicht genommen werden würde, weil er mit Zweigen geschrieben wurde ...
Antti Haapala
3

Ist es besser so zu schreiben?

Ich würde nein sagen.

Für die Leistung; Entweder spielt es keine Rolle (wahrscheinlich für moderne Compiler), oder es sollte keine separate Funktion sein (und in den zum Sortieren verwendeten Code integriert sein), oder Sie sollten überhaupt nicht sortieren (z. B. bei der Erstellung sortierte Daten) und nicht nach der Erstellung sortiert).

Aus Gründen der Lesbarkeit (Codepflege, Möglichkeit, Fehler in der Originalversion zu sehen, Risiko, später Fehler einzuführen) würde ich Ihre Originalversion bevorzugen. besonders wenn Sie in einem Team arbeiten und besonders wenn andere Teammitglieder mit 10 anderen Programmiersprachen besser vertraut sind, für die jeweils ganz andere Regeln gelten als für C.

Speziell; Ich mag das (weil Casts im eigentlichen Code das Lesen erschweren):

    int a = *(int*)x;
    int b = *(int*)y;

..und ich würde den Rest so umschreiben, dass er so aussieht:

    if (a > b) {
        return 1;
    }
    if (a < b) {
        return -1;
    }
    return 0;
}

..oder so aussehen:

    if (a > b) return 1;
    if (a < b) return -1;
    return 0;
}

..wenn elsenach a return; und weil "wenn ohne geschweifte Klammern gefolgt von einer Anweisung in der eigenen Zeile" das Risiko besteht, dass jemand versehentlich eine neue Zeile einfügt, ohne es zu merken und alles zu beschädigen (ein Beispiel finden Sie unter https://dwheeler.com/essays/apple-goto- fail.html ).

Brendan
quelle
0

Wenn Sie die Vergleichsfunktion mit verwenden qsort, muss die Funktion nur + ve, -ve oder Nullwerte zurückgeben.

In diesem Fall können Sie die Zahlen einfach subtrahieren

int Icmp(const void* x, const void* y)
{
    return (*(int*)x - *(int*)y);
}

Rishikesh Raje
quelle
Dies ist zwar effizienter als (a > b) - (a < b), aber das Problem dabei a - bist, dass die Subtraktion unterlaufen kann. Dies ist also ein schlechter Ausdruck.
chmike
-2

Ganzzahlen

echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1

Schwimmt

echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1

Saiten

echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
Milan Maniya
quelle
Bei der Frage geht es darum, wie ganze Zahlen in C verglichen werden können. Ihre Antwort ist eindeutig kein gültiger C-Code und liefert daher keine nützlichen Informationen für die Person, die die Frage gestellt hat. Bitte stellen Sie sicher, dass Ihre Antworten zum Thema gehören.
G. Sliepen