2009-06-25 26 views
7

Wie kann ich einen BYTE-Puffer (von 0 bis 255) in einen Pufferspeicher (von 0.0 bis 1.0) konvertieren? Natürlich sollte es eine Beziehung zwischen den beiden Werten geben, z. B .: 0 im Byte-Puffer wird .0.f im Float-Puffer, 128 im Byte-Puffer ist .5f im Float-Puffer, 255 im Byte-Puffer ist 1.f in Puffer schwimmen.BYTE-Puffer (0-255) in Fließkomma-Puffer konvertieren (0.0-1.0)

Eigentlich ist dies der Code, den ich habe:

for (int y=0;y<height;y++) { 
    for (int x=0;x<width;x++) { 
     float* floatpixel = floatbuffer + (y * width + x) * 4; 
     BYTE* bytepixel = (bytebuffer + (y * width + x) * 4); 
     floatpixel[0] = bytepixel[0]/255.f; 
     floatpixel[1] = bytepixel[1]/255.f; 
     floatpixel[2] = bytepixel[2]/255.f; 
     floatpixel[3] = 1.0f; // A 
    } 
} 

Diese sehr langsam läuft. Ein Freund von mir schlug mir vor, eine Umrechnungstabelle zu verwenden, aber ich wollte wissen, ob jemand anderes mir einen anderen Ansatz geben kann.

+1

Nur der Vollständigkeit halber wird 128 im Byte-Puffer .5019607843f im Float-Puffer sein, nicht .5f. –

Antwort

9

Unabhängig davon, ob Sie eine Nachschlagetabelle verwenden oder nicht, macht Ihr Code bei jeder Schleifeniteration eine Menge Arbeit, die er wirklich nicht benötigt - wahrscheinlich genug, um die Kosten der Konvertierung und Multiplikation zu überschatten.

Deklarieren Sie Ihre Zeiger einzuschränken, und Zeiger, die Sie nur von const lesen. Multiplizieren Sie mit 1/255, anstatt durch 255 zu dividieren. Berechnen Sie die Zeiger nicht in jeder Iteration der inneren Schleife, sondern berechnen Sie nur die Anfangswerte und inkrementieren Sie sie. Rollen Sie die innere Schleife ein paar Mal ab. Verwenden Sie Vektor-SIMD-Operationen, wenn Ihr Ziel dies unterstützt. Inkrementiere und vergleiche nicht mit Maximum, dekrementiere und vergleiche stattdessen mit Null.

So etwas wie

float* restrict floatpixel = floatbuffer; 
BYTE const* restrict bytepixel = bytebuffer; 
for(int size = width*height; size > 0; --size) 
{ 
    floatpixel[0] = bytepixel[0]*(1.f/255.f); 
    floatpixel[1] = bytepixel[1]*(1.f/255.f); 
    floatpixel[2] = bytepixel[2]*(1.f/255.f); 
    floatpixel[3] = 1.0f; // A 
    floatpixel += 4; 
    bytepixel += 4; 
} 

wäre ein Anfang.

+1

Einige sehr gute Vorschläge. Aber sie werden eine Nachschlagetabelle nicht schlagen. ;-) –

+1

Hängt von der Architektur ab. Multiplizieren und Konvertieren ist möglicherweise billiger als Laden, besonders wenn er die SIMD-Fähigkeiten seiner Architektur (MMX, SSE, Altivec oder was auch immer) verwenden kann, um es auf dem gesamten Pixel in einer einzigen Anweisung auszuführen. Diese Entscheidung kann jedoch unabhängig von allen obigen Vorschlägen getroffen werden. – moonshadow

+0

Dies wird mehr tun, um die Arbeit des Compilers einfacher zu machen, als die Geschwindigkeit tatsächlich zu verbessern. Außer dem Ausrichten von Zeigern und dem Aktivieren von SIMD - es kann einen echten Schub geben. – ima

2

Verwenden Sie dazu eine statische Nachschlagetabelle. Als ich in einer Firma für Computergrafik gearbeitet habe, hatten wir eine fest codierte Nachschlagetabelle, die wir mit dem Projekt verbunden haben.

1

Ja, eine Lookup-Tabelle ist definitiv schneller als viele Divisionen in einer Schleife. Erzeugen Sie einfach eine Tabelle mit 256 vorberechneten Float-Werten und verwenden Sie den Byte-Wert, um diese Tabelle zu indizieren.

Sie können auch die Schleife ein wenig durch das Entfernen der Indexberechnung optimieren und etwas tun, wie

float *floatpixel = floatbuffer; 
BYTE *bytepixel = bytebuffer; 

for (...) { 
    *floatpixel++ = float_table[*bytepixel++]; 
    *floatpixel++ = float_table[*bytepixel++]; 
    *floatpixel++ = float_table[*bytepixel++]; 
    *floatpixel++ = 1.0f; 
} 
2

benötigen Sie den Engpass, um herauszufinden, was ist:

  • wenn Sie Ihre Daten iterieren Tabellen in die "falsche" Richtung, treffen Sie ständig einen Cache-Miss. Kein Nachschlagen wird jemals dazu beitragen, das zu umgehen.
  • Wenn Ihr Prozessor beim Skalieren langsamer ist als beim Suchen, können Sie die Leistung steigern, indem Sie nachschlagen, vorausgesetzt, die Nachschlagetabelle passt in den Cache.

Noch ein Tipp:

struct Scale { 
    BYTE operator()(const float f) const { return f * 1./255; } 
}; 
std::transform(float_table, float_table + itssize, floatpixel, Scale()); 
0

Sie jedes Mal nicht berechnen 1/255. Ich weiß nicht, ob ein Compiler schlau genug ist, um dies zu entfernen. Berechne es einmal und wiederhole es jedes Mal. Noch besser, definieren Sie es als Konstante.

+0

Compiler führen konstante Falten, so dass dies kein Problem ist. –

1

Look-up-Tabelle ist der schnellste Weg zu konvertieren :) Hier gehen Sie:

Python-Code der byte_to_float zu erzeugen.h-Datei enthalten:

#!/usr/bin/env python 

def main(): 
    print "static const float byte_to_float[] = {" 

    for ii in range(0, 255): 
     print "%sf," % (ii/255.0) 

    print "1.0f };"  
    return 0 

if __name__ == "__main__": 
    main() 

und C++ Code, um die Umwandlung zu erhalten:

floatpixel[0] = byte_to_float[ bytepixel[0] ]; 

Einfach ist es nicht?

8

Ich weiß, das ist eine alte Frage, aber da niemand eine Lösung mit der IEEE Float-Darstellung gab, hier ist eine.

// Use three unions instead of one to avoid pipeline stalls 
union { float f; uint32_t i; } t, u, v, w; 
t.f = 32768.0f; 
float const b = 256.f/255.f; 

for(int size = width * height; size > 0; --size) 
{ 
    u.i = t.i | bytepixel[0]; floatpixel[0] = (u.f - t.f) * b; 
    v.i = t.i | bytepixel[1]; floatpixel[1] = (v.f - t.f) * b; 
    w.i = t.i | bytepixel[2]; floatpixel[2] = (w.f - t.f) * b; 
    floatpixel[3] = 1.0f; // A 
    floatpixel += 4; 
    bytepixel += 4; 
} 

Das ist mehr als doppelt so schnell als int zu float Umwandlung auf meinem Computer (Core 2 Duo CPU).

Hier ist eine SSE3-Version des obigen Codes, der 16 Floats gleichzeitig ausführt. Es erfordert bytepixel und floatpixel zu 128-Bit ausgerichtet sein, und die Gesamtgröße zu einem Vielfachen von 4. Beachten Sie, dass die SSE3 integrierte Int Float Conversions wird hier nicht viel helfen, da sie sowieso eine zusätzliche Multiplikation erfordern. Ich glaube, das ist der kürzeste Weg, um Anweisungen zu bekommen, aber wenn Ihr Compiler nicht schlau genug ist, möchten Sie vielleicht die Dinge von Hand ausrollen und planen.

/* Magic values */ 
__m128i zero = _mm_set_epi32(0, 0, 0, 0); 
__m128i magic1 = _mm_set_epi32(0xff000000, 0xff000000, 0xff000000, 0xff000000); 
__m128i magic2 = _mm_set_epi32(0x47004700, 0x47004700, 0x47004700, 0x47004700); 
__m128 magic3 = _mm_set_ps(32768.0f, 32768.0f, 32768.0f, 32768.0f); 
__m128 magic4 = _mm_set_ps(256.0f/255.0f, 256.0f/255.0f, 256.0f/255.0f, 256.0f/255.0f); 

for(int size = width * height/4; size > 0; --size) 
{ 
    /* Load bytes in vector and force alpha value to 255 so that 
    * the output will be 1.0f as expected. */ 
    __m128i in = _mm_load_si128((__m128i *)bytepixel); 
    in = _mm_or_si128(in, magic1); 

    /* Shuffle bytes into four ints ORed with 32768.0f and cast 
    * to float (the cast is free). */ 
    __m128i tmplo = _mm_unpacklo_epi8(in, zero); 
    __m128i tmphi = _mm_unpackhi_epi8(in, zero); 
    __m128 in1 = _mm_castsi128_ps(_mm_unpacklo_epi16(tmplo, magic2)); 
    __m128 in2 = _mm_castsi128_ps(_mm_unpackhi_epi16(tmplo, magic2)); 
    __m128 in3 = _mm_castsi128_ps(_mm_unpacklo_epi16(tmphi, magic2)); 
    __m128 in4 = _mm_castsi128_ps(_mm_unpackhi_epi16(tmphi, magic2)); 

    /* Subtract 32768.0f and multiply by 256.0f/255.0f */ 
    __m128 out1 = _mm_mul_ps(_mm_sub_ps(in1, magic3), magic4); 
    __m128 out2 = _mm_mul_ps(_mm_sub_ps(in2, magic3), magic4); 
    __m128 out3 = _mm_mul_ps(_mm_sub_ps(in3, magic3), magic4); 
    __m128 out4 = _mm_mul_ps(_mm_sub_ps(in4, magic3), magic4); 

    /* Store 16 floats */ 
    _mm_store_ps(floatpixel, out1); 
    _mm_store_ps(floatpixel + 4, out2); 
    _mm_store_ps(floatpixel + 8, out3); 
    _mm_store_ps(floatpixel + 12, out4); 

    floatpixel += 16; 
    bytepixel += 16; 
} 

bearbeiten: Verbesserung der Genauigkeit von (f + c/b) * b statt f * b + c verwenden.

Bearbeiten: SSE3-Version hinzufügen.

+0

Nun, kann dies nicht auch mit SSE-Intrinsic gemacht werden? Dies sieht wie das klassische Beispiel eines SIMD-Codes aus. (Das gleiche galt natürlich auch für den Originalcode ...) –

+0

Ja! SSE hat eingeschränkte Shuffle-Funktionen, aber sie können hier nützlich sein. –