Forum: PC-Programmierung Algorithmus zum Durchsuchen großer Felder


von Christian J. (Gast)


Lesenswert?

Hallo,

für die programmierung des "Game of Life" muss ich unbedingt die 
Rechenzeit für die Neuberechumng des Feldes runter bekommen.  Das Feld 
hat 800 x 600 Bytes und für die Berechnung sind 800 x 600 x 8 
Betrachtungen nötig. Da der größte teil des Feldes aber leer ist brauche 
ich einen schnellen Algorithmus, um nur die Bereiche heraus zu fischen, 
wo sich "Zellen" befinden. leider "sieht" die CPU ja nicht das ganze 
Feld von oben so wie ich sondern muss sich jede einzelne Zelle anschauen 
ob da etwas ist. es gibt ja auch fertige Programme die quasi in Echtzeit 
laufen trotz riesieger Felder.

Gibt es da irgendetwas? Zb aus der Bildverarbeitung?

von Walter (Gast)


Lesenswert?

du weißt doch von der vorherigen Berechnung welche Felder belegt sind un 
damit auch welchen Bereich du als nächstes betrachten musst

von Christian J. (Gast)


Lesenswert?

Walter schrieb:
> du weißt doch von der vorherigen Berechnung welche Felder belegt

Das "Array" weiss es.... nur muss das immer wieder neu durchsucht 
werden, oder aber ich lege noch eines an, wo nur die Veränderungen drin 
sind, d.h. viele Zeiger die nur angeben wo was ist.

von Lukas Pichler (Gast)


Lesenswert?

Du kannst um alle nicht leeren Felder einen rechteckigen Kasten ziehen, 
und beim nächsten Zyklus nur diesen und einen Rand rundherum prüfen.

von Roland P. (pram)


Lesenswert?

Leg halt noch ein 80x60 Raster darüber und in dem speicherst du für 
jedes 10x10 Teilstück (oder besser 8x8, kann die CPU leichter rechnen), 
ob leer oder nicht leer.

Gruß
Roland

: Bearbeitet durch User
von P. M. (o-o)


Lesenswert?

Christian J. schrieb:
> Das "Array" weiss es.... nur muss das immer wieder neu durchsucht
> werden, oder aber ich lege noch eines an, wo nur die Veränderungen drin
> sind, d.h. viele Zeiger die nur angeben wo was ist.

Dann kehrst du es eben um: Du hast nicht mehr ein Array von 600x800, 
sondern eine Liste mit den Koordinaten aller nicht-leerer Felder. Das 
bringt natürlich neue Probleme mit sich, ist aber bei einer kleinen 
Anzahl nicht-leerer Felder sicher eine gute Option.

Grundsätzlich muss man sagen, dass ein Algorithmus, der mit einer 
Video-tauglichen Framerate auf einem Gitter von 800x600 in jeder Zelle 
etwas tun soll, auch auf moderner Hardware noch ziemlich ambitioniert 
ist. Bei 24 Bildern und einer halben Million Zellen hat ein moderner 
Prozessorkern gerade noch rund 200 Zyklen pro Zelle...

von P. M. (o-o)


Lesenswert?

Roland Praml schrieb:
> oder besser 8x8, kann die CPU leichter rechnen

Kannst du das etwas ausführen?

von Christian J. (Gast)


Lesenswert?

P. M. schrieb:
> Grundsätzlich muss man sagen, dass ein Algorithmus, der mit einer
> Video-tauglichen Framerate auf einem Gitter von 800x600 in jeder Zelle
> etwas tun soll, auch auf moderner Hardware noch ziemlich ambitioniert
> ist. Bei 24 Bildern und einer halben Million Zellen hat ein moderner
> Prozessorkern gerade noch rund 200 Zyklen pro Zelle...

Den würde ich sowieso in einem FPGA realisieren mit Parallelverarbeitung 
oder mit einem DSP.

von P. M. (o-o)


Lesenswert?

Christian J. schrieb:
> Den würde ich sowieso in einem FPGA realisieren mit Parallelverarbeitung
> oder mit einem DSP.

Ich dachte du hättest gerade eben nach einem Algorithmus für eine CPU 
gefragt? ;-)

Naja, was sich für dieses Projekt nun wirklich anbietet, ist die 
Verwendung der GPU! Mit Nvidia CUDA könnte man sowas sehr elegant lösen. 
Falls du Zeit und Lust hast, würde ich mich da mal einen halben Tag 
einlesen. Ist längst nicht so schwierig, wie einen FPGA zu 
programmieren.

von Christian J. (Gast)


Angehängte Dateien:

Lesenswert?

P. M. schrieb:

> Naja, was sich für dieses Projekt nun wirklich anbietet, ist die
> Verwendung der GPU!

Ich suche die gerade ........ kopfkratz

von P. M. (o-o)


Lesenswert?

Christian J. schrieb:
> Ich suche die gerade ........ kopfkratz

Achso - um welche Plattform geht's denn letztlich? 800x600 auf dem Z80 
dürfte ja wohl utopisch sein?

von Christian J. (Gast)


Lesenswert?

P. M. schrieb:

> Achso - um welche Plattform geht's denn letztlich? 800x600 auf dem Z80
> dürfte ja wohl utopisch sein?

Synchron auf dem PC unter Linux, erstmal da gescheit, dann portiere ich 
es mit weniger Auflösung......

von Klaus W. (mfgkw)


Lesenswert?

Welche CPU?
Wenn du mehrere Kerne hast, können die ja alle arbeiten...

von tictactoe (Gast)


Lesenswert?

Du brauchst doch pro Zelle nur zwei Zustände, oder? Dann kannst du 8 
nebeneinander liegende Zellen auf die 8 Bits eines Bytes zusammenfassen. 
Und schon kannst du mit einem Vergleich auf 0 sehen ob irgendeine der 8 
Zellen "an" ist.

von Peter II (Gast)


Lesenswert?

du kannst auch ganze auch ohne großen Spielfeld lösen. speichere einfach 
jede lebende Zelle in einem Array. Da es recht wenig aktive Zellen gibt 
sollte das etwas effektiver sein.

Etwas komplizierter ist dann nur die suche nach den Nachbarn.
Wenn man es so macht, dann skaliert die Rechenzeit mit der Anzahl der 
lebenden Zellen und ist nicht mehr abhängig von der Spielgröße.

von P. M. (o-o)


Lesenswert?

Peter II schrieb:
> Etwas komplizierter ist dann nur die suche nach den Nachbarn.
> Wenn man es so macht, dann skaliert die Rechenzeit mit der Anzahl der
> lebenden Zellen und ist nicht mehr abhängig von der Spielgröße.

Die Rechenzeit skaliert dann mindestens quadratisch mit der Anzahl 
lebenden Zellen. (Anzahl Zellen, die man verarbeiten muss multipliziert 
mit der Anzahl Zellen, die man in jedem Schritt fragen muss, ob sie 
Nachbarn sind = Anzahl lebende Zellen x Anzahl lebende Zellen)

Man könnte es aber stark beschleunigen, indem man für jede Spalte und 
jede Zeile je eine eigene Liste hat. Die Zelle an Position (49, 75) 
würde dann in der Zeilenliste 49 und in der Spaltenliste 75 geführt. 
Such man nun ihre Nachbarn, so muss man in der Spaltenliste 74 und 76 
sowie Zeilenliste 48 und 50  schauen, ob dort etwas drin ist. Wenn ja, 
noch kurz schauen, ob die Zellen tatsächlich Nachbarn sind.

von Roland P. (pram)


Lesenswert?

P. M. schrieb:
> Roland Praml schrieb:
>> oder besser 8x8, kann die CPU leichter rechnen
>
> Kannst du das etwas ausführen?

Ich dachte mir, dass du das 800x600 Gitter in je 8x8 Blöcke unterteilst 
(und somit ein Gitter von 100x75 Blöcke bekommst) Für jeden Block kannst 
du dann abspeichern, ob es aktive Zellen gibt (ggf. sogar die Anzahl), 
somit kannst du gleich für 256 Pixel entschieden, ob sie leer sind oder 
nicht.

Gruß Roland

von P. M. (o-o)


Lesenswert?

Roland Praml schrieb:
> P. M. schrieb:
>> Roland Praml schrieb:
>>> oder besser 8x8, kann die CPU leichter rechnen
>>
>> Kannst du das etwas ausführen?
>
> Ich dachte mir, dass du das 800x600 Gitter in je 8x8 Blöcke unterteilst
> (und somit ein Gitter von 100x75 Blöcke bekommst) Für jeden Block kannst
> du dann abspeichern, ob es aktive Zellen gibt (ggf. sogar die Anzahl),
> somit kannst du gleich für 256 Pixel entschieden, ob sie leer sind oder
> nicht.

Und inwiefern sollen hier 8x8 Blöcke vorteilhaft sein gegenüber 10x10 
Blöcken?

von Christian J. (Gast)


Lesenswert?

Hi,

ich habe erstmal die menschliche Denkweise umgesetzt, die nicht 
unbedingt die optimale für den Computer sein muss. Und da mein System 
auzs einer Zeit kommt wo man noch optimal programmieren musste und 8kb 
"viel" sind sollte ich es optimieren auf Kosten der Komplexität des 
Programmes. Ein 2D Array ist ja keine natürliche Datentype sondern ein 
Konstrukt von C, Speicher läuft ja nunmal in einer Reihe und nicht 2D.

Da das Feld meist nur 1/3 voll ist bzw sich sehr schnell reduziert wäre 
die Einführung von Look Up Tabeles vielleicht sinnvoll, aber die 
Auswertung wäre komplizierter.

von Roland P. (pram)


Lesenswert?

P. M. schrieb:
> Und inwiefern sollen hier 8x8 Blöcke vorteilhaft sein gegenüber 10x10
> Blöcken?

Eine Division durch 8 bzw. Multiplikation ist für eine CPU einfacher.

/edit: 8x8=64 (und nicht 256 Pixel wie im Vorpost)

: Bearbeitet durch User
von P. M. (o-o)


Lesenswert?

Roland Praml schrieb:
> Eine Division durch 8 bzw. Multiplikation ist für eine CPU einfacher.
>
> /edit: 8x8=64 (und nicht 256 Pixel wie im Vorpost)

Ob das allerdings relevant ist...? Ohne Division sollte man auskommen 
und die Multiplikation wird man wenig (oder gar nicht) brauchen und sie 
ist sowieso nicht viel teurer gegenüber einem Shift. Ich würde mir auf 
jeden Fall eher Gedanken zu Grösse und Alignment der Cache-Blöcke 
machen.

: Bearbeitet durch User
von Roland P. (pram)


Lesenswert?

Auf einer x86 CPU magst du in allen Punkten recht haben, so wie ich den 
OP verstanden habe, soll es aber später auf einer Z80 CPU laufen und da 
sieht die Sache wieder ganz anders aus.

Vermutlich wäre es aber wirklich besser, ein (Hash)Set mit aktuell 
lebenden Zellen zu führen, wie in den anderen Posts schon angesprochen 
wurde

von Yay (Gast)


Lesenswert?

Ich würde über eine andere datenstruktur nachdenken. Kein array...
Evtl. eine baumstruktur mit informationen in den nodes ob sich weiter 
hinten belegte zellen befinden.

Diese informationen aktualisierst du dann beim schreiben und ändern der 
daten, so dass du zwar mehr instruktionen beim speichern brauchst aber 
dadurch deine suchfunktion beschleunigst.

von Roland P. (pram)


Lesenswert?

Der vermutlich schnellste Algoritmus ist übrigens "Hashlife".
Soweit ich das überflogen habe, hat das aber mit dem ursprünglichen 
Algorithmus nicht mehr viel zu tun.

von Christian J. (Gast)


Lesenswert?

http://en.wikipedia.org/wiki/Hashlife

Frage ist ob mein resturlaub von 6 Tage  dazu ausreicht diese 
Doktorarbeit zu implementieren auf einem Z80 im Eurokarten Format :-)

Ich schaue grad auf den 1/2m hohen Schnee draußen und die Bergkuppe 
gegenüber und schreibe eine Lookup Tabelle, Kerze an, Tee dabei.... so 
macht Programmieren Spass.

>>Evtl. eine baumstruktur mit informationen in den nodes ob sich weiter
>>hinten belegte zellen befinden.

Setzt einen Heap voraus, der etwas größer ist als meine 2KB... sonst 
gute Idee.

von Christian J. (Gast)


Lesenswert?

So,

ich denke damit ist mein "Spass am Kreuzworträtsel" auch erstmal 
befriedigt, denn aus dem Thema wurde sicher mehr als eine Doktorarbeit 
gemacht. Es läuft signifikant schneller, je weniger Zellen es werden 
aber der "Effekt" eines sich aufbauenden Bildes ist weg, da die 
Veränderungen wahllos erscheinen auf dem Bildschirm. Es werden nur noch 
belebte Bereiche betrachtet aber die Iterationschritte sind trotzdem 
noch recht hoch, auch wenn man ein 9er Feld um die betrachtete Zelle 
legt.

"Hashlife" ist ein sehr komplexer Mechanismus und übersteigt auch meine 
Lust an der Sache deutlich, da es nur als Spielerei gedacht war.

// Koordinaten der lebenden Zellen
struct pos_t {
    uint8_t x,y;
};

struct pos_t lcell_pos[MAX_ZELLEN];

// 2 Spielfelder alt+neu
struct feld_t {
      uint8_t last[MAX_X+1][MAX_Y+1];
      uint8_t next[MAX_X+1][MAX_Y+1];
      uint16_t n_cells_old,n_cells_new;
};

// Berechne die nächste Generation
uint16_t calc_nextgen()
{
    uint8_t x,y,i;
    uint16_t mods,count;

    // Koordinaten der 8 Nachbarn + eigene Zelle
    struct pos_t {
        uint8_t x,y;
    } nb[9];

    mods = 0;

    // Neue Generation initialisieren
    memset(feld.next,DEAD,sizeof(feld.next));

    // Durchlaufe die Hash Tabelle lebender Zellen
    for (count=0;count<feld.n_cells_old;count++)
    {
         // Hole Koordinaten lebender Zelle
         x = lcell_pos[count].x;
         y = lcell_pos[count].y;

        // Eintragen
        nb[0].x = x;
        nb[0].y = y;

        // Definiere die 8 Nachbarkoordinaten
        // schneller als indizierte Schleife

        nb[1].x = x-1;
        nb[1].y = y-1;

        nb[2].x = x;
        nb[2].y = y-1;

        nb[3].x = x+1;
        nb[3].y = y-1;

        nb[4].x = x-1;
        nb[4].y = y;

        nb[5].x = x+1;
        nb[5].y = y;

        nb[6].x = x-1;
        nb[6].y = y+1;

        nb[7].x = x;
        nb[7].y = y+1;

        nb[8].x = x+1;
        nb[8].y = y+1;

        // Berechne den neuen Status dieser 9 Zellen
        for (i=0;i<9;i++)
        {
            x = nb[i].x;
            y = nb[i].y;

            //Trage neuen Status in nächste Generation ein
            feld.next[x][y] = cell_status(x,y);

            // Bei Veränderung neu zeichnen
            if (feld.next[x][y] != feld.last[x][y])
            {
                move(MAX_Y-y,x);
                if (feld.next[x][y] == ALIVE)
                    addch('O');
                else
                    addch(' ');
                mods++;
            }
        }
    }

    // Erstelle neue Hash Tabelle
    count = 0;
    for (x=1;x <= MAX_X;x++) {
        for (y=1;y <= MAX_Y;y++) {
            if (feld.next[x][y]==ALIVE) {
                lcell_pos[count].x = x;
                lcell_pos[count++].y = y;
            }
        }
    }

    // Passe Werte an
    feld.n_cells_old = count;

     // Altes Feld = Neues Feld
     memcpy(feld.last,feld.next,sizeof(feld.next));

     // Anzahl Veränderungen zurück
     return (mods);
}

von Hans Ulli K. (Gast)


Lesenswert?

Hmm,

wenn ich richtig verstehe hast du ein Feld mit 800*600 Bytes.
Was ist denn in jedem Feld, doch nur "Null" oder "Eins".

Warum denn nicht ein Bitfeld nehmen ???

von Christian J. (Gast)


Lesenswert?

Hans Ulli Kroll schrieb:

> Hmm,
>
> wenn ich richtig verstehe hast du ein Feld mit 800*600 Bytes.
> Was ist denn in jedem Feld, doch nur "Null" oder "Eins".
>
> Warum denn nicht ein Bitfeld nehmen ???

Seufz...... die 800x600 waren für den PC, es werden 80x24 später auf 
einem Z80.

Bitfelder haben den Vorteil, dass sie wenig Ram brauchen. Daran herrscht 
aber kein Mangel. Bitfelder haben den Nachteill, dass der 
"Verwaltungsaufwand" mit "C COMPILERN" deutlich höher ist, weil die 
nicht unbedingt die Bitbfehle der CPU konsequent nutzen sondern in AND 
und OR umsetzen als Read/Mopdify/Write. Ein Byte ist eindeutig 
adressierbar über seinen Index, ein Bit ganz bestimmt nicht, da haben 
wir schon 2 Indizes, einen fürs Byte und einen für Bit in dem Byte.
Ich muss als eine Funktion schreiben

calc_bit (&bitnr, &bytenr, x,y)

die mir nochmal Zeit verbrät, da sie bei jedem Zugriff aufgerufen werden 
muss.

Ich habe auch irgendwann mal eingebleut bekommen, dass 1 Byte die 
kleinste Einheit ist mit der man rechnen soll.Es gibt zwar bei MCS51 
sowas wie "packed array", auch der PIC hat (oder hatte?) einen bit 
adressierbaren RAM Bereich aber eine normale CPU arbeitet Byte bezogen. 
Und auf dem ARMJ arbeite ich mit 32Bit-Integer, weil das die optimale 
Einheit ist für eine 32 Bit Maschine.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.