2013-07-01 5 views
8

Prima di tutto, sono consapevole che questa domanda sembra davvero come se non avessi cercato, ma l'ho fatto, molto.Rendere il disegno di C# mandelbrot più efficiente

Ho scritto un piccolo codice di disegno Mandelbrot per C#, è fondamentalmente un modulo di Windows con un PictureBox su cui disegno il set Mandelbrot.

Il mio problema è che è piuttosto lento. Senza uno zoom profondo, fa un buon lavoro e spostarsi e zoomare è abbastanza fluido, richiede meno di un secondo per disegno, ma una volta che comincio a ingrandire un po 'e raggiungere luoghi che richiedono più calcoli, diventa molto lento.

Su altre applicazioni Mandelbrot il mio computer funziona molto bene su luoghi che funzionano molto più lentamente nella mia applicazione, quindi immagino ci sia molto che posso fare per migliorare la velocità.

ho fatto le seguenti cose per ottimizzarlo:

  • Invece di usare i metodi SetPixel GetPixel sul oggetto bitmap, ho usato LockBits metodo per scrivere direttamente sulla memoria, che ha reso le cose molto più veloce.

  • Invece di utilizzare oggetti numerici complessi (con le classi che ho creato io stesso, non quelle incorporate), ho emulato numeri complessi utilizzando 2 variabili, re e im. Questo mi ha permesso di ridurre le moltiplicazioni perché quadrare la parte reale e la parte immaginaria è qualcosa che viene fatto un po 'di tempo durante il calcolo, quindi mi limito a salvare il quadrato in una variabile e riutilizzare il risultato senza bisogno di ricalcolarlo.

  • Io uso 4 fili per disegnare il Mandelbrot, ogni filo fa un diverso quarto dell'immagine e tutti funzionano simultaneamente. Come ho capito, ciò significa che la mia CPU userà 4 dei suoi core per disegnare l'immagine.

  • Uso l'algoritmo del tempo di fuga, che come ho capito è il più veloce?

Ecco la mia come mi muovo tra i pixel e calcolare, è commentata quindi spero è comprensibile:

 //Pixel by pixel loop: 
     for (int r = rRes; r < wTo; r++) 
     { 
      for (int i = iRes; i < hTo; i++) 
      { 

       //These calculations are to determine what complex number corresponds to the (r,i) pixel. 
       double re = (r - (w/2))*step + zeroX ; 
       double im = (i - (h/2))*step - zeroY; 

       //Create the Z complex number 
       double zRe = 0; 
       double zIm = 0; 

       //Variables to store the squares of the real and imaginary part. 
       double multZre = 0; 
       double multZim = 0; 

       //Start iterating the with the complex number to determine it's escape time (mandelValue) 
       int mandelValue = 0; 
       while (multZre + multZim < 4 && mandelValue < iters) 
       { 
        /*The new real part equals re(z)^2 - im(z)^2 + re(c), we store it in a temp variable 
        tempRe because we still need re(z) in the next calculation 
         */ 
        double tempRe = multZre - multZim + re; 

        /*The new imaginary part is equal to 2*re(z)*im(z) + im(c) 
         * Instead of multiplying these by 2 I add re(z) to itself and then multiply by im(z), which 
         * means I just do 1 multiplication instead of 2. 
         */ 
        zRe += zRe; 
        zIm = zRe * zIm + im; 

        zRe = tempRe; // We can now put the temp value in its place. 

        // Do the squaring now, they will be used in the next calculation. 
        multZre = zRe * zRe; 
        multZim = zIm * zIm; 

        //Increase the mandelValue by one, because the iteration is now finished. 
        mandelValue += 1; 
       } 


       //After the mandelValue is found, this colors its pixel accordingly (unsafe code, accesses memory directly): 
       //(Unimportant for my question, I doubt the problem is with this because my code becomes really slow 
       // as the number of ITERATIONS grow, this only executes more as the number of pixels grow). 
       Byte* pos = px + (i * str) + (pixelSize * r); 
       byte col = (byte)((1 - ((double)mandelValue/iters)) * 255); 
       pos[0] = col; 
       pos[1] = col; 
       pos[2] = col; 

      } 
     } 

Cosa posso fare per migliorare questa? Trovi ovvi problemi di ottimizzazione nel mio codice?

In questo momento ci sono 2 modi so che posso migliorare:

  1. ho bisogno di usare un tipo diverso per i numeri, doppia è limitato con precisione e sono sicuro che ci sono meglio non-built -in tipi alternativi che sono più veloci (si moltiplicano e si aggiungono più velocemente) e hanno maggiore precisione, ho solo bisogno di qualcuno che mi indichi dove ho bisogno di guardare e dirmi se è vero.

  2. Posso spostare l'elaborazione nella GPU. Non ho idea di come fare questo (OpenGL forse? DirectX? È anche così semplice o avrò bisogno di imparare un sacco di cose?). Se qualcuno può inviarmi link a tutorial appropriati su questo argomento o dirmi in generale che sarebbe fantastico.

Grazie mille per la lettura così lontano e spero che me :)

+0

float è in genere più veloce, anche se penso che dipenda dal processore utilizzato. float è in genere più veloce del doppio se usi una gpu. – sav

risposta

1

può aiutare Per spostare l'elaborazione per la GPU, hai un sacco di ottimi esempi qui:

https://www.shadertoy.com/results?query=mandelbrot

Si noti che è necessario un browser abilitato per WebGL per visualizzare quel collegamento. Funziona meglio in Chrome.

Non sono esperto di frattali ma sembra che tu abbia già fatto le ottimizzazioni. Andare oltre potrebbe rendere il codice molto più difficile da leggere e mantenere, quindi dovresti chiedertelo ne vale la pena.

Una tecnica che ho osservato spesso in altri programmi frattali è questa: durante lo zoom, calcola il frattale a una risoluzione inferiore e allungalo a dimensione intera durante il rendering. Quindi eseguire il rendering alla massima risoluzione non appena lo zoom si interrompe.

Un altro suggerimento è che quando si utilizzano più thread è necessario fare attenzione che ogni thread non legga/scriva la memoria di altri thread poiché ciò causerà collisioni nella cache e prestazioni dannose. Un buon algoritmo potrebbe essere suddiviso il lavoro in scanlines (invece di quattro quarti come hai fatto ora). Creare un numero di thread, quindi finché ci sono le righe da elaborare, assegnare una scanline a un thread disponibile. Lascia che ogni thread scriva i dati dei pixel su un pezzo di memoria locale e li copia nella bitmap principale dopo ogni riga (per evitare collisioni nella cache).

+0

Grazie mille per aver trovato il tempo di rispondere :) Informazioni sulla GPU, gli esempi non mi sono di aiuto perché non ho assolutamente idea di questo argomento, come funziona e quali tipi di calcoli può fare la GPU (o come si accede anche?). Speravo prima qualcosa con le informazioni di base. Informazioni sulle ulteriori ottimizzazioni, non mi interessa la leggibilità del codice. Lo zoom a bassa risoluzione è qualcosa che ho preso in considerazione, ma speravo che ci fossero altre cose che potrei fare prima. – Omer

+0

Informazioni sulle collisioni della cache: in realtà non capisco, perché ci sarebbero collisioni nella cache? Se mi accerto che ogni thread scriva esattamente nella memoria, dovrebbero esserci ancora collisioni nella cache?Perché le linee di scansione sono un'opzione migliore (non sono solo un altro modo per suddividere l'immagine?) – Omer

+0

@Omer Le linee di scansione sono buone perché sono un blocco continuo in memoria, che è di nuovo valido per la cache della CPU. È sempre meglio scrivere in memoria continua (questo è il motivo per cui è meglio attraversare i pixel in ordine y/x invece di x/y). Le collisioni si verificano perché le cache si sovrappongono, molti thread possono avere la stessa memoria di 4096 (dire) byte nella cache in modo che entrino in collisione anche quando scrivono parti diverse di quella memoria. –

2

Codifica WRT per la GPU, puoi guardare Cudafy.Net (anche OpenCL, che non è legato a NVidia) per iniziare a capire cosa sta succedendo e forse anche a fare tutto ciò che ti serve. L'ho trovato rapidamente - e la mia scheda grafica - inadatta alle mie esigenze, ma per il Mandelbrot allo stadio in cui ti trovi, dovrebbe andare bene.

In breve: codice per la GPU con un sapore di C (Cuda C o OpenCL normalmente) quindi spingere il "kernel" (il metodo C compilato) alla GPU seguito da qualsiasi fonte dati e quindi richiamare tale " kernel ", spesso con parametri per dire quali dati utilizzare - o forse alcuni parametri per dirgli dove posizionare i risultati nella sua memoria.

Quando eseguo il rendering frattale, ho evitato di disegnare su una bitmap per le ragioni già delineate e rinviata la fase di rendering. Oltre a ciò, tendo a scrivere codice multithreaded in modo massivo, il che è davvero brutto per provare ad accedere a una bitmap. Invece, scrivo in un negozio comune - ultimamente ho usato un MemoryMappedFile (una classe .Net integrata) poiché mi dà una velocità di accesso casuale abbastanza buona e una vasta area indirizzabile. Tendo anche a scrivere i miei risultati su una coda e ho un altro thread per gestire i dati in memoria; i tempi di calcolo di ciascun pixel di Mandelbrot saranno "irregolari", vale a dire che non avranno sempre lo stesso tempo. Di conseguenza, il commit dei pixel potrebbe essere il collo di bottiglia per conteggi di iterazioni molto bassi. Coltivandolo in un altro thread, i thread di calcolo non sono mai in attesa di completamento dell'archiviazione.

Attualmente sto giocando con la visualizzazione di Buddhabrot del set Mandelbrot, considerando l'utilizzo di una GPU per ridimensionare il rendering (dal momento che richiede molto tempo con la CPU) e avere un enorme set di risultati. Stavo pensando di indirizzare un'immagine da 8 gigapixel, ma sono giunto alla conclusione che dovevo allontanarmi dai vincoli dei pixel, e possibilmente allontanarmi dall'aritmetica in virgola mobile a causa di problemi di precisione.Dovrò anche acquistare un nuovo hardware in modo da poter interagire in modo diverso con la GPU - diversi lavori di calcolo termineranno in momenti diversi (come nel mio commento di conteggio delle iterazioni prima) quindi non posso semplicemente licenziare lotti di thread e aspettare per farli completare tutti senza potenzialmente perdere un sacco di tempo in attesa di un conto di iterazione particolarmente elevato dell'intero lotto.

Un altro punto per far sì che io non veda quasi mai di essere fatto sul set di Mandelbrot è che è simmetrico. Potresti fare il doppio del calcolo di cui hai bisogno.

+0

Pensavo che il set mandlebrot non fosse simmetrico, -> caotico – sav

+0

http://kluge.in-chemnitz.de/documents/fractal/node9.html Le risposte sono là fuori :) Chaos non significa Random, c'è un alto grado di prevedibilità nel set di Mandelbrot. – user1796307

3

Se si decide di spostare l'elaborazione nella gpu, è possibile scegliere tra numerose opzioni. Dato che stai usando C#, XNA ti permetterà di usare HLSL. RB Whitaker ha le esercitazioni XNA più semplici se si sceglie questa opzione. Un'altra opzione è OpenCL. OpenTK viene fornito con un programma dimostrativo di un frattale di julia set. Questo sarebbe molto semplice da modificare per visualizzare il set mandlebrot. Vedi here Basta ricordare di trovare lo shader GLSL che accompagna il codice sorgente.

Circa la GPU, gli esempi non sono di aiuto per me, perché non ho assolutamente alcuna idea su questo argomento, come fa ancora funziona e che tipo di calcoli la GPU può fare (o come viene anche letta?)

Diversi software GPU funziona in modo diverso però ...

Tipicamente un programmatore di scrivere un programma per la GPU in un linguaggio shader di come HLSL, GLSL o OpenCL. Il programma scritto in C# carica il codice dello shader e lo compila, quindi usa le funzioni in un'API per inviare un lavoro alla GPU e ottenere il risultato dopo.

Dai uno sguardo allo FX Composer o alla scimmia di rendering se vuoi un po 'di pratica con gli shader senza doversi preoccupare delle API.

Se si utilizza HLSL, la pipeline di rendering appare come questa.

pipeline

Il vertex shader è incaricato di prendere punti nello spazio 3D e calcolare la loro posizione nel vostro campo di visione 2D. (Non è una grande preoccupazione per te dal momento che stai lavorando in 2D)

Il pixel shader è responsabile per applicare gli effetti di shader ai pixel dopo che il vertex shader è stato eseguito.

OpenCL è una storia diversa, orientata verso GPU computing generico (vale a dire: non solo grafica). È più potente e può essere utilizzato per GPU, DSP e costruzione di super computer.

Problemi correlati