Forum: Compiler & IDEs Array-Inhalt in Hardware-RAM schreiben


von Michael S. (alteraonlinux)


Lesenswert?

Hallo Zusammen,

ich arbeite mit einem Cyclone V von Altera und Yocto Linux, dass auf dem 
ARM Prozessor läuft.

Im FPGA habe ich ein RAM erzeugt, auf dass ich von Linux Seite her 
beschreibe. Der Inhalt des RAMs wird dann in Form einer Grafik (256x128) 
einem Video überlagert.

Das funktioniert auch tadellos, nur extrem langsam. Man kann sehen wie 
sich die Grafik zeilenweise im Bild aufbaut.

Nach einiger Recherche bin ich zum dem Schluss gekommen, dass es an der 
Software liegen muss, genauer gesagt an der Funktion, welche die Daten 
ins RAM schreibt:
1
  for(int zeile=0; zeile<128; zeile++)
2
  {
3
    for(int spalte=0; spalte<256; spalte++)
4
    {
5
       char MSB = 0;
6
       char R = histR.at<char>(zeile,spalte+c+d);  
7
       char G = histR.at<char>(zeile,spalte+c+d+1);
8
       char B = histR.at<char>(zeile,spalte+c+d+1+1);  
9
      
10
       //combined = 32Bit = |-0-|-R-|-G-|-B-|
11
       int combined = (MSB << 24) | (B << 16) | (G << 8) | R; 
12
       
13
       //Datenwort "combined" wird ins RAM geschrieben
14
       setAddressValue((HISTR+spalte)+(3*spalte)+(1024*zeile), combined); 
15
      
16
       c++;
17
       d++;
18
       e++;
19
    }
20
  }

Kann man das als Funktion so schreiben? Oder muss man hier anders 
(besser) vorgehen?

Gibt es Optionen für den GNU C++ Compiler um solche Schleifen zu 
optimieren?

Ich bin für jeden Ratschlag dankbar! :-)

von Karl H. (kbuchegg)


Lesenswert?

Was versteckt sich hinter
1
   setAddressValue((HISTR+spalte)+(3*spalte)+(1024*zeile), combined);

ich denke mal, da wird dein Zeitfresser drinn stecken. Die Farbwerte der 
32k Pixel zusammenzusetzen wird eher weniger das Problem sein.

von Michael (Gast)


Lesenswert?

Dahinter versteckt sich eine Funktion die einen Port zum virtuellen 
Linux-Speicher öffnet und die Werte auf die physikalische Adresse 
schreibt.
1
void setAddressValue(int target, int writeval)
2
{  
3
  void *virt_addr, *map_base; 
4
  unsigned long read_result;
5
  int fd;
6
  
7
  //Open port to Memory
8
  if((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1) FATAL;
9
  fflush(stdout);
10
  
11
  map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, target & ~MAP_MASK);
12
  if(map_base == (void *) -1) FATAL; 
13
  fflush(stdout);
14
  
15
  virt_addr = map_base + (target & MAP_MASK);   
16
    
17
  *((unsigned long *) virt_addr) = writeval;
18
  read_result = *((unsigned long *) virt_addr);
19
20
  fflush(stdout);
21
  if(munmap(map_base, MAP_SIZE) == -1) FATAL;
22
  close(fd);
23
}

Dabei ist:
target = Zieladresse
value = 32-Bit Daten

von Karl H. (kbuchegg)


Lesenswert?

Michael schrieb:
> Dahinter versteckt sich eine Funktion die einen Port zum virtuellen
> Linux-Speicher öffnet und die Werte auf die physikalische Adresse
> schreibt.

Und das für jedes Pixel.


Umbauen!

Vor Beginn der kompletten Pixel Operation die Sache mit dem File-Öffnen 
und memory allokieren ereldigen.

Dann kommen deine Schleifen zum Zug, die die Pixel setzen. Und zwar in 
die bereits vorhandene memory-map. Innerhjalb der Schfleife passiert im 
wesentlichen nur noch ein
1
   *((unsigned long *) virt_addr) = writeval;
mit jeweils wechselenden Adressen. Aber mehr nicht.

und dann die Abschlussarbeiten, wie File schliessen.


So wie jetzt darfst du dich nicht wundern, wenn das alles saulangsam 
ist. Du bürdest ja deinem ARM einen Haufen Sonderarbeit für nichts und 
wieder nichts auf! Du schickst ihn bildlich gesprochen, für 50 
Beilagscheiben 50 mal ins Lager um jeweils 1 Beilagscheibe zu holen. Das 
das dann in Summe 2 Stunden dauert, darf doch nicht verblüffen, während 
ein Kunde der mitdenkt, dich nur einmal ins Lager schickt um alle 50 
Scheiben zu holen, was dann in 3 Minuten ereldigt ist. Das Problem ist 
nicht das greifen der Beilagscheiben. Das Problem ist der Fussweg ins 
Lager und wieder zurück, den du in dem einen Fall 50 mal machst und im 
anderen Fall nur einmal.

von Stephan (Gast)


Lesenswert?

wow

für jeden Wert machst du das, Wahnsinnnnnn.

Baue doch ober eine Satz von Daten auf und schreibe dann die Daten 
komplett!

von Michael (Gast)


Lesenswert?

Hallo,

zunächst einmal "Danke" für die Unterstützung!
Stimmt, das ist echt "Wahnsinn" was ich da geschrieben habe. Ich 
schreibe erst seit rund einem Jahr C++, aber ich denke das sieht man :-D

Ich habe die Funktion jetzt mal wie folgt umgebaut:
1
  void *virt_addr, *map_base; 
2
  unsigned long read_result;
3
  int fd;
4
  int address;
5
  
6
  //Port zum Speicher wird geöffnet
7
  if((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1) FATAL;
8
  fflush(stdout);
9
  
10
  for(int zeile=0; zeile<128; zeile++)
11
  {
12
    for(int spalte=0; spalte<256; spalte++)
13
    {      
14
      map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, address & ~MAP_MASK);
15
      if(map_base == (void *) -1) FATAL; 
16
      fflush(stdout);
17
  
18
      //Zusammenfügen des Datenworts
19
      char MSB = 0;
20
      char R = histR.at<char>(zeile,e+c+d);  
21
      char G = histR.at<char>(zeile,e+c+d+1);
22
      char B = histR.at<char>(zeile,e+c+d+1+1);  
23
      
24
      //Resultat = 32Bit-Wort
25
      int combined = (MSB << 24) | (B << 16) | (G << 8) | R;
26
      
27
      //Datenwort wird an Adresse geschrieben
28
      address = ((HISTR+spalte)+(3*spalte)+(1024*zeile));
29
      virt_addr = map_base + (address & MAP_MASK);   
30
      *((unsigned long *) virt_addr) = combined;
31
            
32
      c++;
33
      d++;
34
      e++;
35
    }
36
  }
37
  
38
  //Port zum Speicher wird wieder geschlossen
39
  read_result = *((unsigned long *) virt_addr);
40
  fflush(stdout);
41
  if(munmap(map_base, MAP_SIZE) == -1) FATAL;
42
  close(fd);

Resultat: Es läuft schon deutlich schneller, aber ich denke das ist 
nicht das was Karl Heinz meinte?

Wie soll ich
1
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, address & ~MAP_MASK);
vor der Schleife schreiben? Da ist "address" doch noch gar nicht 
bekannt? Ich versuche es gleich mal mit einem Pointer. Aber wie gesagt, 
ich schreibe noch nicht lange C++.

Gruß,
Michael

von Andreas B. (andreas_b77)


Lesenswert?

Michael schrieb:
> Wie soll ich
>  map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE,
>  MAP_SHARED, fd, address & ~MAP_MASK);
> vor der Schleife schreiben? Da ist "address" doch noch gar nicht
> bekannt?

In der neuen Version beim ersten Schleifendurchlauf auch nicht. Zwischen 
der Deklaration und der Verwendung von address wird kein Wert 
zugewiesen.

Das mmap() machst du einfach vor den Schleifen vom ganzen Bereich des 
Framebuffers. Es gibt doch keinen Grund den für jeden Zugriff zu ändern.

von Rolf Magnus (Gast)


Lesenswert?

Michael schrieb:
> nicht das was Karl Heinz meinte?
>
> Wie soll ichmap_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE,
> MAP_SHARED, fd, address & ~MAP_MASK);
> vor der Schleife schreiben? Da ist "address" doch noch gar nicht
> bekannt?

Der Witz bei mmap ist, daß du einen ganzen Block mappen kannst und nicht 
nur ein einzelnes Byte. Das mmap jedesmal neu zu machen, ist ungefähr 
so, als ob du beim Einlesen einer Datei für jedes einzelne gelesene Byte 
die Datei neu auf- und danach wieder zumachst. Klar, daß das wahnsinnig 
Performance frisst.

PS: Warum machst du eigentlich dauernd ein fflush(stdout), obwohl du nie 
was dorthin schreibst?

von Andreas B. (andreas_b77)


Lesenswert?

Rolf Magnus schrieb:
> Das mmap jedesmal neu zu machen, ist ungefähr
> so, als ob du beim Einlesen einer Datei für jedes einzelne gelesene Byte
> die Datei neu auf- und danach wieder zumachst. Klar, daß das wahnsinnig
> Performance frisst.

Ein mmap ist sogar noch viel schlimmer. Das muss das virtuelle Mapping 
ändern was viel aufwendiger ist. Nicht unbedingt wegen der 
Datenstrukturen die angelegt/modifiziert werden müssen, sondern wegen 
der (je nach Prozessor-Architektur) nötigen Cache- und TLB-Flushes.

Wenn die CPU nur noch mit der Geschwindigkeit des Hauptspeichers 
arbeiten kann wird es halt wirklich grottig lahm.

von Michael S. (alteraonlinux)


Lesenswert?

Gibt es denn eine schnellere Methode als mmap?

von Rolf Magnus (Gast)


Lesenswert?

Michael Schäfer schrieb:
> Gibt es denn eine schnellere Methode als mmap?

Das Problem ist nicht, daß du mmap benutzt, sondern daß du es für jeden 
der 32768 zu schreibenden Werte extra aufrufst, statt nur genau einmal 
am Anfang.
Mal ein anderer Vergleich: Stell dir vor, du mußt einen Laster mit 
Kartons befüllen. Die sinnvolle Variante wäre, den Laster vom Parkplatz 
vor die Tür zu fahren, zu öffnen, alle Kartons einzuladen, zumachen und 
losfahren.
Was du stattdessen machst: Du fährst den Laster vom Parkplatz her, 
machst ihn auf, lädst genau einen Karton ein, machst ihn wieder zu und 
fährst zurück zum Parkplatz. Dann fährst du ihn wieder vor die Tür, 
machst den Laster wieder auf und lädst den nächsten Karton ein, wieder 
zu, zurück zum Parkplatz, u.s.w.
Die Aktion "Laster herfahren und aufmachen" ist aufwendig, wird aber nur 
einmal benötigt, also macht man sie auch nur einmal. So ist das auch mit 
mmap.

von Michael S. (alteraonlinux)


Lesenswert?

Also ich habe jetzt mal alle Ratschläge befolgt und den Code-Abschnitt 
wie folgt umgeschrieben:
1
  void *virt_addr, *map_base; 
2
  unsigned long read_result;
3
  int fd;
4
  int adress = HISTR; //HISTR = Start-Adresse des RAM (wurde via #define defniert)
5
  
6
  //Port zum Speicher wird geöffnet
7
  if((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1) FATAL;
8
  map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, adress & ~MAP_MASK);
9
  if(map_base == (void *) -1) FATAL; 
10
  
11
12
  for(int zeile=0; zeile<128; zeile++)
13
  {
14
    for(int spalte=0; spalte<256; spalte++)
15
    {      
16
  
17
      //Zusammenfügen des Datenworts
18
      char MSB = 0;
19
      char R = histR.at<char>(zeile,e+c+d);  
20
      char G = histR.at<char>(zeile,e+c+d+1);
21
      char B = histR.at<char>(zeile,e+c+d+1+1);  
22
      
23
      //Resultat = 32Bit-Wort
24
      int combined = (MSB << 24) | (B << 16) | (G << 8) | R;
25
      
26
      //Datenwort wird an Adresse geschrieben
27
      adress = ((HISTR+spalte)+(3*spalte)+(1024*zeile));
28
      virt_addr = map_base + (address & MAP_MASK);   
29
      *((unsigned long *) virt_addr) = combined;
30
          
31
      c++;
32
      d++;
33
      e++;
34
    }
35
  }
36
  
37
  //Port zum Speicher wird wieder geschlossen
38
  read_result = *((unsigned long *) virt_addr);
39
  if(munmap(map_base, MAP_SIZE) == -1) FATAL;
40
  close(fd);
Das Problem hierbei ist:
Da "adress" vor der Schleife lediglich die Start-Adresse (HISTR) des RAM 
enthält, wird in der Schleife die ganze Zeit nur an diese eine Adresse 
geschrieben.

Irgendwie stehe ich gerade total auf dem Schlauch.
Was muss ich hier Wie ändern? Die Adressen müssen doch irgendwo hoch 
gezählt werden?

von Karl H. (kbuchegg)


Lesenswert?

Michael Schäfer schrieb:

>       //Datenwort wird an Adresse geschrieben
>       adress = ((HISTR+spalte)+(3*spalte)+(1024*zeile));
>       virt_addr = map_base + (address & MAP_MASK);
>       *((unsigned long *) virt_addr) = combined;
>
>       c++;
>       d++;
>       e++;
>     }
>   }
>
>   //Port zum Speicher wird wieder geschlossen
>   read_result = *((unsigned long *) virt_addr);
>   if(munmap(map_base, MAP_SIZE) == -1) FATAL;
>   close(fd);
> [/c]
> Das Problem hierbei ist:
> Da "adress" vor der Schleife lediglich die Start-Adresse (HISTR) des RAM
> enthält, wird in der Schleife die ganze Zeit nur an diese eine Adresse
> geschrieben.

Echt?

Und was ist das hier?
1
       //Datenwort wird an Adresse geschrieben
2
       adress = ((HISTR+spalte)+(3*spalte)+(1024*zeile));
3
       virt_addr = map_base + (address & MAP_MASK);

Das Problem ist eher, dass du durch rumdoktern versuchst, den Code 
irgendwie zum laufen zu bringen, anstatt durch systematisches nachdenken 
und ergründen, wie dieses File-mapping eigentlich auf Codeebene 
funktioniert bzw. was der Originalcode eigentlich gemacht hat und warum 
er es genau so gemacht hat.

Gibt es einen Grund dafür, dass du hier
1
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, adress & ~MAP_MASK);
mittels Offset nur einen Teil des 'files' in den Speicher mappen lässt?

So groß ist deine Bitmap mit den paar Pixeln dann auch wieder nicht, 
dass das ein enorme Speicherbelastung wäre, wenn da mal 32k kurzzeitig 
benutzt werden.

Lass die das komplette File mappen und greif nicht segmentiert zu. (Es 
sei denn es gibt einen technischen Grund dafür. Was ich allerdings nicht 
weiß).

Dazu siehst du dir mal die Doku zu mmap an, was denn die einzelnen Teile 
eigentlich bedeuten.

Wie groß ist bei dir eigentlich MAP_SIZE bei dir eingestellt?
Obwohl: Im Grunde wäre mir das wurscht, weil ich diesen ganzen 
Segmentiermechanismus mit MAP_SIZE und MAP_MASK sowieso rauswerfen 
würde. D.h. wenn möglich. Bei 32k Bitmaps, so wie hier, lohnt das alles 
nicht. Wenn du Bitmaps mit 4096 mal 8192 Pixel, True Color, bearbeiten 
musst, die die Megabytes nur so auffressen, dann sieht das anders aus. 
Aber für die popel-Bitmap mit ihren 32k lohnt sich das doch nicht. Wenn 
der Speicher so knapp ist, dass diese 32k ein Problem darstellen, dann 
hast du ganz andere, dringendere Probleme.

von Karl H. (kbuchegg)


Lesenswert?

Was mich auch allerdings auch Angst und Bang werden lässt, das ist dein 
Umgang mit Datentypen in diesem Code.

Wieso wird hier eigentlich
1
    *((unsigned long *) virt_addr) = combined;

auf unsigned long gecastet?
combined ist ein int (sollte das nicht eigentlich ein unsigned int sein, 
wenn ich mir so ansehe, wie du die Farbinformation zusammenstellst?). 
Ist auf deinem System die sizeof(int) identisch zur sizeof(unsigned 
long)? Wenn ja (was durchaus sein könnte), dann ist das zumindest 
grenzwertig maximal-verwirrend.


Die Byteinformationen legst du in einen char, der eigentlich ein 
unsigned char sein sollte. char ist für Textverarbeitung. Machst du hier 
Textverarbeitung? Nein! Damit stellt sich die Frage nach einem char gar 
nciht mehr und die Wahl kann nur noch zwischen 'signed char' und 
'unsigned char' gefällt werden. Da reine Bytes, so wie hier, 
normalerweise nicht als vorzeichenbehaftet angesehen werden, ist 'signed 
char' auch aus dem Rennen und es bleibt ein 'unsigned char' übrig. So 
wie eigentlich meistens, wenn man mit Bytes operiert.

Du gehst mir da überall viel zu leichtfertig mit den Datentypen um. Ein 
void* Pointer ist schon mal extrem verdächtig.


Die Adressberechnung mit den 3*spalte ist auch merkwürdig, könnte aber 
stimmen, je nachdem wie das FPGA das haben will. Auf jeden Fall ist es 
nicht logisch, dass pro Pixel 3 Bytes benutzt werden, vor allen Dingen 
mit dem Hintergrund, dass hier
1
      int combined = (MSB << 24) | (B << 16) | (G << 8) | R;
ja offenbar etwas mit 4 Bytes (die anscheinend ja alle wichtig sind) 
zusammengebaut werden, und eine komplette Pixelzeile 1024 Bytes lang 
ist. Dein Display hat pro Zeile 256 Pixel. Pro Pixel 3 Bytes würden 768 
Bytes pro Zeile machen. Rechnet man aber mit 4 Bytes pro Pixel (was mit 
dem restlichen Code logisch wäre), dann ergeben sich die 1024. Dann ist 
allerdings aber die Adressberechnung mit 3*spalte wieder unlogisch.
Wie gesagt: es wäre unter umständlich möglich, dass das alles stimmt, 
wenn das FPGA das alles wieder irgendwie seltsam auseinanderdröselt, 
weil es auf Displays bis hinauf zu 341 Pixel pro Zeile ausgelegt ist 
(was auch eine seltsame Zahl darstellen würde). Aber um ehrlich zu sein, 
denke ich eher, dass du dich da komplett vertan hast und noch nicht 
verstanden hast, wie die Adressierung eigentlich funktioniert.

von Karl H. (kbuchegg)


Lesenswert?

Oops. das ist ja eine RGB Sache. Also braucht ein 256*128 Pixel Bild 
nicht 32k, sondern das 3(4?)-fache davon. Aber selbst 128k sind bei den 
Speichergrößen, die du brauchst um Linux fahren zu können, immer noch 
kein Thema.

von Michael S. (alteraonlinux)


Lesenswert?

@Karl Heinz:
Der Tipp mit der MAP_SIZE war gold wert. Die war auf 4096UL definiert. 
Ich hab Sie nun so groß definiert, wie die Grafik ist. Folgende Änderung 
war notwendig:
1
#define HIST_SIZE       131072UL
2
#define HIST_MASK (HIST_SIZE - 1)
Der Geschwindigkeitsunterschied ist unglaublich!

Vielen Dank für die anregende Kritik!

von Michael S. (alteraonlinux)


Lesenswert?

@KarlHeinz (Datentypen)
Guter Hinweis.
Das mit den Datentypen werde ich mir gleich als nächstes noch mal 
gründlich anschauen.

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.