Frage

Ich bin ein ziemlich kompetenter Java-Programmierer, der auf C sehr neu Ich versuche, eine Routine zu optimieren, die vier Betriebsart hat.

I Schleife über alle Pixel in einem Bild und berechnet einen neuen Wert Pixel auf dem ‚Mode‘ abhängig geben.

Meine Frage betrifft den Aufwand eines Schalters Erklärung innerhalb von zwei for-Schleifen verschachtelt. Ich würde in allen Links zur Dokumentation in Bezug auf die relative Wirksamkeit der grundlegenden C-Anweisungen, mathematische und logische Operationen interessiert sein.

Der Code würde wie folgt:

for (x = 0; x < width; x++) {
        for (y = 0; y < height; y++) {
             switch (mode)                  /* select the type of calculation */
             {
                case 0:
                weight = dCentre / maxDistanceEdge;
                case 1: 
                    weight = (float)x/width;             
                    break;
                case 2: 
                    weight = (float)y/height;
                    break;
                case 3: 
                    weight = dBottomLeft / maxDistanceCorner;
                    break;
                case 4: 
                    weight = dTopRight / maxDistanceCorner;
                    break;
                default: 
                weight = 1;
                break;
            }
             // Calculate the new pixel value given the weight
             ...
            }             

    }

Möchten Sie erwarten viel Aufwand zu sehen, ob dies über ein Pixelbild 5000 x 5000 war? Ich habe versucht, einige Tests, aber meine Ergebnisse sind ganz über dem Platz, da das System (Mobile Device) zu tun hat, alle möglichen Sachen im Hintergrund ausgeführt wird, die Ergebnisse verzerren können.

Die andere Option ist ein separates Verfahren für jeden Modus zu haben, jeder mit seinen eigenen vier Schleifen. Dies würde offensichtlich redundanten Code einführen, aber Effizienz ist der Name des Spiels hier.

Vielen Dank im Voraus!

Gav

War es hilfreich?

Lösung

Schalen Aussagen kompilieren für aufeinanderfolgende Werte in eine Sprungtabelle und zu einer Reihe von if-else-Anweisungen für spärliche Werte. Auf jeden Fall wollen Sie nicht eine switch-Anweisung in Ihrer inneren Schleife für die Bildverarbeitung, wenn Sie über die Leistung kümmern. Sie wollen, wie unten statt.

Beachten Sie auch, dass ich die Gewichtsberechnung aus der inneren Schleife bewegt (und vertauscht die Schleifen für Fall 2, um dies zu erreichen). Diese Art des Denkens, Material aus der inneren Schleife bewegen, erhalten Sie die Leistung, die Sie aus C wollen.

switch (mode)                  /* select the type of calculation */
{
case 0:
    weight = dCentre / maxDistanceEdge;
    for (x = 0; x < width; x++) {
        for (y = 0; y < height; y++) {
             // Calculate the new pixel value given the weight
             ...
        }
    }
    break;
case 1:
    for (x = 0; x < width; x++) {
        weight = (float)x/width;
        for (y = 0; y < height; y++) {
             // Calculate the new pixel value given the weight
             ...
        }
    }
    break;
case 2:
    // note - the loops have been swapped to get the weight calc out of the inner loop
    for (y = 0; y < height; y++) {
        weight = (float)y/height;
        for (x = 0; x < width; x++) {
             // Calculate the new pixel value given the weight
             ...
        }
    }
    break;
case 3:
    weight = dBottomLeft / maxDistanceCorner;
    for (x = 0; x < width; x++) {
        for (y = 0; y < height; y++) {
             // Calculate the new pixel value given the weight
             ...
        }
    }
    break;
case 4:
    weight = dTopRight / maxDistanceCorner;
    for (x = 0; x < width; x++) {
        for (y = 0; y < height; y++) {
             // Calculate the new pixel value given the weight
             ...
        }
    }
    break;
default:
    weight = 1;
    for (x = 0; x < width; x++) {
        for (y = 0; y < height; y++) {
             // Calculate the new pixel value given the weight
             ...
        }
    }
    break;

// etc..
}

Andere Tipps

Wenn Effizienz wichtiger als Code-Größe ist, dann ja, sollten Sie redundante Routinen erstellen. Die Case-Anweisung ist eine der unteren Kopf Dinge, die Sie in C tun, aber es ist nicht Null - es auf dem Modus basierend zu verzweigen haben wird, und es ist so wird Zeit in Anspruch nehmen. Wenn Sie wirklich max Leistung wollen, bekommen den Fall aus der Schleife, auch auf Kosten der Schleife zu duplizieren.

Schalter Aussagen sind etwa so effizient, wie sie nur sein kann. Sie sind zu einem Sprungtabelle zusammengestellt. das ist in der Tat, warum Schalter ist so begrenzt, wie es ist: Sie haben nur einen Schalter schreiben, für die Sie können ein Sprungtabellen basierend auf einem festen Wert kompilieren

.

Im Vergleich zu der Mathematik Sie in der Schleife tun, der Aufwand des Schalters wird wahrscheinlich minimal sein. Having said that, der einzige Weg, um sicher zu sein, ist verschiedene Versionen für die beiden unterschiedlichen Ansätze, und Zeit, um sie zu erstellen.

Schalter / Fall extrem schnell ist mit dem Äquivalent verglichen mit if / else: es typischerweise als Sprungtabelle implementiert wird. Allerdings hat es noch eine Kosten.

Während Sie die Dinge zu optimieren:

Versuchen

1) in einer Schleife über die Leitungen nicht über Spalten (Schalter x und y „für“ Schleifen), kann eine Lösung sein, extrem schneller als die anderen, aufgrund von Cache-Speicherverwaltung.

2) Ersetzen aller Divisionen durch Multiplikationen der (vorausberechnete) inverse geben Ihnen erhebliche Verstärkung, und wahrscheinlich eine akzeptable Genauigkeitsverlust.

Aus Gründen der Effizienz zuliebe besser Sie switch außerhalb der Schleife bewegen.

würde ich Funktionszeiger wie folgt verwenden:

double fun0(void) { return dCentre/maxDistanceEdge; }
double fun1(void) { return (float)x/width; }
/* and so on ... */

double (*fun)(void);

switch (mode)                  /* select the type of calculation */
{
    case 0: fun = fun0;
            break;
    case 1: fun = fun1;
            break;
    case 2: fun = fun2;
            break;
    case 3: fun = fun3;
            break;
    case 4: fun = fun3;
            break;
    default : fun = fun_default;
            break;
}

for (x = 0; x < width; x++) {
        for (y = 0; y < height; y++) {
             weight = fun();
             // Calculate the new pixel value given the weight
             ...
        }
}

Es fügt Overhead-Funktionsaufruf, aber es sollte nicht zu groß sein, da Sie keine params an die Funktion übergeben. Ich denke, es ist gut Kompromiss zwischen Leistung und Lesbarkeit.

EDIT: Wenn Sie GCC verwenden, um der Funktionsaufruf Sie goto verwenden können loszuwerden und

Schalter shouldnt einen signifikanten Overhead zu erzeugen, sie in eine Art Array von Zeigern am unteren Ende kompiliert bekommen, dann ist es ein Fall von effektiv:

JMP {Basisadresse} + switchcasenum

Dies würde wahrscheinlich davon ab, wie guter Prädiktor Ihrer CPU Zweig ist und wie der Compiler den Code für den Schalter erzeugt. Für eine so kleine Zahl von Fällen könnte es einen Entscheidungsbaum, wobei in diesem Fall normale CPU Verzweigungsvorhersage sollte in der Lage sein, entfernen die meisten der Overhead erzeugen. Dinge vielleicht ein bisschen schlimmer sein, wenn es einen Schalter Tabelle erzeugt ...

Wie gesagt, der beste Weg, um herauszufinden, ist es zu profilieren und sehen.

Neben Jims Ratschläge, versuchen die Reihenfolge der Schleifen tauschen. Ob Loop-Swapping ist ideal für den Fall 1 würde Tests erfordern, aber ich vermute, es ist. Sie wollen fast immer Ihre x in Ihrer inneren Schleife koordinieren, um Paging-Leistung zu verbessern, da dies die Funktion bewirkt eine bessere Tendenz zu haben, jede Iteration in dem gleichen allgemeinen Speicherbereich zu bleiben. Und ein mobiles Gerät mit limitted Ressourcen könnte niedrig genug RAM hat, dass dieser Unterschied betont werden.

Leider diesen Thread zu stoßen, aber es scheint mir, dass der Schalter ist bei weitem nicht das Problem.

Das eigentliche Problem mit der Effizienz in diesem Fall ist die Divisionen. Es scheint mir, dass alle Nenner der Divisionsoperationen Konstanten sind (Breite, Höhe, max ...) und diese nicht im Laufe des Bildes ändern. Wenn meine Vermutung richtig ist, dann, dass diese einfachen Variablen basierend auf dem Bild ändern kann geladen, so dass jede Größe Bild zur Laufzeit verwendet werden kann, erlaubt nun diese für jede Bildgröße geladen werden, aber das bedeutet auch die Compiler sich nicht optimieren können in die viel einfacher Multiplikationsoperation, die sie tun können, wenn sie „const“ deklariert wurden. Mein Vorschlag wäre, die Inversen dieser Konstanten vorab zu berechnen und vermehren. Soweit ich mich erinnern kann, nimmt die Multiplikationsoperation etwa 10 Taktzyklen, wobei als die Division um 70 nimmt, dass eine Erhöhung von 60 Zyklen pro Pixel ist, und mit dem oben 5000x5000 erwähnt, das ist ein geschätzte Geschwindigkeit Anstieg von 1,5 Sekunden auf einem 1 GHz CPU.

Abhängig von dem Chip und dem Compiler und den Einzelheiten des Codes, und ... aber das wird oft als Sprungtabelle implementiert werden, was ziemlich schnell sein sollte.

BTW-- diese Art der Sache zu verstehen, ist ein ziemlich gutes Argument ein paar Wochen für die Ausgaben einiger Montage an einem gewissen Punkt in Ihrer Karriere zu lernen ...

  

aber Effizienz ist der Name des Spiels hier.

Iterieren über einen Bildpuffer, um neue Pixelwerte zu berechnen, klingt wie ein typisches embarrassingly parallelen Problem, in diesem Sinne Sie könnten einen Teil der Arbeit in Arbeitsthreads zu prüfen, Schieben, das Ihr Betrieb beschleunigen sollte insbesondere als Mikro-Optimierungen wie Schalter / Fall betrifft.

Auch anstelle der Verzweigungs Anweisungen jedes Mal tun, könnten Sie einen Funktionszeiger aus einer Reihe von Funktionszeiger aufrufen, wobei der Index als Moduskennung dient.

Damit Sie mit Anrufen am Ende wie:

computeWeight[mode](pixel);

Mit 5000x5000 Pixeln, der Funktionsaufruf Overhead auch durch Aufruf der Funktion für einen Bereich von Pixeln reduziert werden könnten, anstatt einzelne Pixel.

Sie können auch Schleifenentrollen und Parameterübergabe durch Verweis / Zeiger, um diese weiter zu optimieren.

verwenden

Viele gute Punkte bereits gegeben. Das Einzige, was ich von hinzufügen, um diese denken konnte, ist die am häufigsten Fällen bis in den Schalter und die am wenigsten häufig nach unten zu bewegen.

Also, wenn Fall 4 passiert öfter als Fall 1, sollte es darüber sein:

switch (mode) {
    case 4:
        // ..
        break;
    case 1:
        // ..
        break;
}

Schade, dass Sie wurden mit c ++ nicht, denn dann könnte die Switch-Anweisung mit Polymorphismus ersetzt werden.

Cheers!

Es gibt viele kreative Vorschläge in diesem Thread von Möglichkeiten, nicht mehr als 5 separate Funktionen zu schreiben.

Wenn Sie nicht lesen ‚Modus‘ aus einer Datei oder von typisierten Eingang kann die Berechnungsmethode bei der Kompilierung bestimmt werden. In der Regel wollen Sie keine Berechnungen aus der Kompilierung bewegen Zeit laufen zu lassen.

So oder so würde der Code leichter zu lesen und niemand würde zu verwechseln, ob nicht dazu gedacht, Sie in der break-Anweisung im ersten Fall zu bringen oder nicht.

Auch wenn Sie Fehler in der Umgebung Code bekommen Sie nicht suchen, wenn die Enumeration auf den falschen Wert gesetzt wurde oder nicht.

Im Hinblick auf die inneren Schleifen ... 0-> var ist besser gemacht var-> 0 als var-- löst die Null-Flag (6502 Tage). Dieser Ansatz bedeutet auch, „Breite“ in x geladen und kann über vergessen werden, Gleiches gilt für „Höhe“. Auch die Pixel im Speicher sind in der Regel links> rechts, oben-> unten so auf jeden Fall haben x als innere Schleife.

for (y = height; y--;) {
    for (x = width; x--;) {
         weight = fun();
         // Calculate the new pixel value given the weight
         ...
    }
}

Auch ... und sehr wichtig ist, Ihre Switch-Anweisungen nur zwei Fälle, die x oder y verwenden. Der Rest sind Konstanten.

 switch (mode)                  /* select the type of calculation */
 {
     case 0:
        weight = dCentre / maxDistanceEdge;
        break;
     //case 1: 
     //  weight = (float)x/width;             
     // break;
     //case 2: 
     //     weight = (float)y/height;
     //     break;
     case 3: 
          weight = dBottomLeft / maxDistanceCorner;
          break;
      case 4: 
           weight = dTopRight / maxDistanceCorner;
           break;
      default: 
           weight = 1;
           break;
 }

Also im Grunde, es sei denn Modus 1 oder 2 Gewicht vor der Schleife berechnet wird.

... Y loop code here

    if (mode == 2) { weight = (float)y/height; } // calc only once per Y loop

    ... X loop here

        if (mode == 1) { weight = (float)x/width; } // after this all cases have filled weight
        calc_pixel_using_weight(weight);

Ich habe switch-Anweisungen gefunden sehr unfreundlich zu sein, wenn die Daten spärlich sind. Für <4 Elemente würde ich gehen für if-then-else und stellen Sie sicher, dass die häufigsten Fälle auf der Spitze sind. Wenn die erste Bedingung 90% der Fälle fängt haben Sie treffen im Grunde einen Homerun. Ebenso, wenn ein anderer Zustand ist <1% setzte es schließlich.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top