Forum: Compiler & IDEs Was ein C++-"Guru" so ueber Geschwindigkeit erzaehlt.


von Kaj (Gast)


Lesenswert?

Hallo Leute,

"Kurze" Vorgeschichte zu der eigentlichen Frage:
Ich arbeite in einer Firma die unteranderem auch Software entwickelt, 
wir sind ca. 40 Softwareentwickler, schwerpunkt liegt auf C/C++.
Da die Firma bereit ist, uns 2 Weiterbildungen pro Jahr zu beahlen, 
haben wir uns mal einen C++-"Guru"/Dozenten eingeladen um mal zu sehen, 
ob der uns was brauchbares erzaehlen kann. Das Ergebnis war ziemlich 
ernuechtern.
Was ist passiert?
Der gute Mann hat erstmal einen auf ganz dicke Hose gemacht, und was 
fuer ein toller super Typ er doch sei, er sei ja seit Jahren 
C++-Entwickler fuer Serverprogramme und hoch parallelisierte Prozesse 
bla bla.
Dann wollte er unseren Wissensstand pruefen, um zu wissen wo er denn mit 
seiner Schulung ansaetzen koenne. Der Typ hatte tatsaechlich eine Art 
Klausur vorbereitet. Naja, die erste Aufgabe haben wir mal gemacht, die 
Aufgabestellung war in etwa:
"Schreiben sie eine Funktion, die ein Array in ein anderes Array 
kopiert, und das ganze moeglichst effektiv"
Gut, nichts wildes soweit, moechte man denken. Wir also alle in etwa 
sowas geschrieben:
1
void copy(int* src, int* dst, int elements)
2
{
3
  for(int i = 0; i < elements; i++)
4
    dst[i] = src[i];
5
}

Dann kam Herr "Ich-weiss-alles-ueber-C++-und-ihr-seid-alle-dumm" und hat 
ne kleine Standpauke gehalten:
bla bla, ihr seid alle dumm, bla bla bla, sie denken gar nicht in C++, 
bla bla, sie sind alle schlecht, bla bla bla, da muessen wir bei den 
grundlagen anfangen, bla bla, und hier die "richtige Loesung" der 
Aufgabe:
1
void copy(int* src, int* dst, int elements)
2
{
3
  for(int i = 0; i < elements; i++)
4
    *dst++ = *src++;
5
}
(ueber den schleifen typ kann man streiten)
"Das ist viiieeeeel effektiver, der Geschwindigkeitsvorteil gegenueber 
der Index-Variante ist mindestens Faktor 3. Bei der Index-Variante muss 
ja bei jeder Zuweisung zweimal die neue Adresse berechnet werden, was ja 
die ALU beansprucht und das kostet Zeit. Den Zeiger einfach Hochzaehlen 
passiert nebenbei, das ist viel schneller."
Ende der Vorgeschichte

So, diese Geschichte zum Geschwindigkeitsvorteil hat mir keine Ruhe 
gelassen und ich hab es jetztmal getestet.

Umgebung:
Atmel Studio 6.2
AVR GCC 4.8.1
AVR Projekt (Mega 2560)
Compilerflags: -O0 -g3 -std=gnu99 -Wall -Wextra
Atmel Studio Simulator
1
#include <stdint.h>
2
3
void copy(uint8_t* src, uint8_t* dst, uint16_t elements);
4
5
int main(void)
6
{
7
  uint8_t a[1000] = {[0 ... 999] = 42};
8
  uint8_t b[1000] = {[0 ... 999] = 0};
9
10
  copy(a, b, 1000);
11
12
  while(1); //  <-- breakpoint
13
}
14
15
void copy(uint8_t* src, uint8_t* dst, uint16_t elements)
16
{
17
  for(uint16_t i = 0; i < elements; i++)
18
    dst[i] = src[i];  //  4445,19us
19
    //*dst++ = *src++;  //  4570,19us
20
}
So, bei meinem Code-Beispiel, verhaelt es sich jetzt aber genau 
andersherum, als der Herr C++-Guru uns erzaehlt hat, zumindestens laut 
dem Simulator des Atmel Studios. Bis zum Breakpoint braucht das 
Programmm 4,445ms zu 4,570ms, immerhin mehr als 0,1ms unterschied.

So, jetzt die eigentlichen Fragen:
Stimmt das, was uns der werte Herr da zum Geschwindigkeitsvorteil 
erzaehlt hat prinzipiel und das liegt hier jetzt einfach daran das ich 
es mit dem AVR-Simulator getestet habe, oder ist das Grundsaetzlicher 
bloedsinn was uns da erzaehlt wurde?

Ich hoffe Ihr koennt etwas Licht ins dunkel bringen :-)

von micha (Gast)


Lesenswert?

Kaj schrieb:
> Compilerflags: -O0 -g3 -std=gnu99 -Wall -Wextra

Schalt mal die Optimierung ein und vergleich den Code.

von Peter II (Gast)


Lesenswert?

Kaj schrieb:
> Stimmt das, was uns der werte Herr da zum Geschwindigkeitsvorteil
> erzaehlt hat prinzipiel und das liegt hier jetzt einfach daran das ich
> es mit dem AVR-Simulator getestet habe, oder ist das Grundsaetzlicher
> bloedsinn was uns da erzaehlt wurde?

weder noch.

Das ganze ist abhängig von dem Compiler. Je besser der Compiler desto 
kleiner sind die unterschiede. Im Idealfall kommt sogar der gleiche Code 
raus.

Seine Aussage könnte als durchaus auf einen Compiler zutreffen, pauschal 
ist es aber falsch.

von Blubb (Gast)


Lesenswert?

Schau dir das Listing an (.lss). Teste auch mal memcpy zum Vergleich.

von Peter II (Gast)


Lesenswert?

Kaj schrieb:
> und hier die "richtige Loesung" der
> Aufgabe:void copy(int* src, int* dst, int elements)
> {
>   for(int i = 0; i < elements; i++)
>     *dst++ = *src++;
> }

damit hätte man gleich Kontern können.

Denn es ist langsamer eine Variable von 0 bis irgendwas zu zählen als 
andersrum.
1
copy(int* src, int* dst, int elements)
2
{
3
   while( elements-- )
4
    *dst++ = *src++;
5
}

und das spart sogar noch eine Variabel auf dem Stack.

(gilt alles nur ohne Optimierung vom Compiler)

von Helmut L. (helmi1)


Lesenswert?

Kaj schrieb:
> void copy(int* src, int* dst, int elements)
> {
>   for(int i = 0; i < elements; i++)
>     dst[i] = src[i];
> }

Ich haette es so geschrieben:

memcpy(dst,src,elements * sizeof(int));

von ... (Gast)


Lesenswert?

Ich dachte immer zwischen Pointern und Arrays gibt es keine 
Unterschiede.
Das ist nur eine andere Schreibweise.
Zudem verbieten manche Standarts auch über Pointer zu iteriren.
Könnte mir jemand erklären warum das so ist?

Gruß

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter II schrieb:
> (gilt alles nur ohne Optimierung vom Compiler)

Dann brauchst du auch nicht über Geschwindigkeit zu reden.

Keiner würde sich einen Ferrari kaufen, um dann zu argumentieren,
dass der mit angezogener Handbremse langsamer ist als der Ford Fiesta.

von Peter II (Gast)


Lesenswert?

... schrieb:
> Ich dachte immer zwischen Pointern und Arrays gibt es keine
> Unterschiede.
> Könnte mir jemand erklären warum das so ist?

bei der 1.Version muss immer die Adresse neu berechnet werden, bei er 
2.Version wird die Adresse immer nur um sizeof(int) erhöht.

schau dir beide Versionen in ASM-Code ohne Optimierung an.

von Test (Gast)


Lesenswert?

@helmi1
Ja memcpy ist definitiv der effizienteste weg :) alles anfänger 
hier...mit na schleife pffff.... :D

von MaWin (Gast)


Lesenswert?

> Stimmt das, was uns der werte Herr da zum Geschwindigkeitsvorteil
> erzaehlt hat prinzipiel

Nein.

Bei gutem Compiler kommt derselbe Maschinecode bei raus.

Sein Beispiel ist auch kein C++, er hat offensihcltichlich von C++ nicht 
die leiseste Ahnung und schreibt C Code und glaubt das wäre C++.

Bei C++ kopieren sich Elemente selbst.

Sein Array wäre ein Klasse. Um eine Klasse zu kopieren, braucht mal also 
bloss
1
b = a

zu schreiben. Und das ist vom erzeugten Maschinencode her VIEL langsamer 
als der entsprechende C Code, den du oben genannt hast. Denn je nach 
dem, wie die Klasse intern die Integers speichert, möglichst basierend 
auf einer dynamischen Datenstruktur mit Integers als serialisierbaren 
KLassenelementen, werden dabei tausende von new's gemacht.

Daher sind C++ Programme auch meist grottenlangsam.

von Scelumbro (Gast)


Lesenswert?

1. Das ganze mit C++ (gegenüber C) nichts zu tun.
2. Ein moderner Compiler mit eingeschalteter Optimierung dürfte da 
wahrscheinlich keinen Unterschied produzieren.
3. Nach Jahrelanger Erfahrung mit Fortbildungen: Leider werden viel zu 
viele Coaches  Trainer  Lehrer nachdem sie es im richtigen Berufsleben 
zu nichts mehr bringen. Man lernt also viel zu häufig von Versagern, 
häufig mit veraltetem Wissen. Es gibt natürlich Ausnahmen - aber die 
sind selten und teuer.

von Dr. Sommer (Gast)


Lesenswert?

MaWin schrieb:
> zu schreiben. Und das ist vom erzeugten Maschinencode her VIEL langsamer
> als der entsprechende C Code, den du oben genannt hast. Denn je nach
> dem, wie die Klasse intern die Integers speichert, möglichst basierend
> auf einer dynamischen Datenstruktur mit Integers als serialisierbaren
> KLassenelementen, werden dabei tausende von new's gemacht.
Schwachsinn. Bei vernünftigen Implementationen, wie zB std::vector<int>, 
wird 1x new und 1x memcpy gemacht, also exakt das gleiche wie in der 
C-Variante.

von Bronco (Gast)


Lesenswert?

Mir wurde mal glaubhaft beigebracht, dass bei der heutigen Komplexität 
der Prozessoren ein guter Compiler deutlichen besseren Code erzeugt, 
als händisches Optimieren.
Konkret ging es um einen TriCore, bei dem der Compiler z.B. die 
Maschinenbefehle zur optimalen Auslastung der Pipeline umsortiert: Was 
da an Assemblercode rauskam, war kaum noch nachzuvollziehen.
Unter dem Strich kam raus: Überlasst das Optimieren dem Compiler und 
konzentriert Euch auf die Aufgabe!

von Peter II (Gast)


Lesenswert?

Bronco schrieb:
> Überlasst das Optimieren dem Compiler und
> konzentriert Euch auf die Aufgabe!

muss sollte den Mittelweg finden. Man kann auch C/C++ so schlecht 
schreiben das der Compiler es auch nicht mehr retten kann.

von Udo S. (urschmitt)


Lesenswert?

In der Theorie können das gute Compiler so optimieren, daß da kein 
Unterschied ist.
In der Praxis kann das bei bestimmten Systemen viel ausmachen.

Aktuelles Beispiel hier bei uns in der Firma: Eine alte Sun mit einem 
Gnu C++ Compiler. Standard copy ist bis zu Faktor 100! langsamer 
gegenüber einem tricky Code. Problem da: Compiler optimiert nicht 
ordentlich die Zugriffe auf ungerade Adressen, was bei dieser einen Sun 
sich in extrem langsamen Speicherzugriffen auswirkt.
Aber portabel und lesbar ist anders.
Für gängige Systeme (PC, gängige µCs) sieht es deutlich besser aus, da 
sind die Optimierungen normalerweise besser und kaum 
Geschwindigkeitsunterschiede zu erzielen.
Punkt ist: Mach nur das schneller was wirklich zu langsam ist.
Bei modernen (großen) Anwendungen lassen sich in der Regel viel mehr 
Zeit sparen indem man Schwachstellen in der Architektur findet, bessere 
Parallelsierungen ermöglicht und bei Datenstrukturen (Hashtabellen, 
clevere Bäume etc.) , File I/O und Datenbankzugriffen Grips investiert.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

MaWin schrieb:

>
1
> b = a
2
>
>
> zu schreiben. Und das ist vom erzeugten Maschinencode her VIEL langsamer
> als der entsprechende C Code, den du oben genannt hast. Denn je nach
> dem, wie die Klasse intern die Integers speichert, möglichst basierend
> auf einer dynamischen Datenstruktur mit Integers als serialisierbaren
> KLassenelementen, werden dabei tausende von new's gemacht.
>
> Daher sind C++ Programme auch meist grottenlangsam.


Was natürlich kompletter Schwachsinn ist.

von Karl H. (kbuchegg)


Lesenswert?

>  Das Ergebnis war ziemlich ernuechtern.

Da muss ich dir zustimmen.
Leider glauben immer noch viel zu viele, durchaus auch Dozenten, dass 
man Geschwindigkeit auf dieser unteren Ebene mit sogenannten 
Mikrooptimierungen holen könne. Was die Leute geflissentlich ignorieren: 
All diese Tricks kennen die COmpiler auch schon seit Jahrzehnten. Und 
wahrscheinlich noch Dutzende andere, von denen der Dozent noch nie etwas 
gehört hat.

Diesen sog. Guru hätt ich rausgeschmissen, gleich nachdem er die Aufgabe 
eine Kopierfunktion zu schreiben gestellt hätte. Alleine die 
Aufgabenstellung zeigt, dass er ein paar wesentliche Dinge nicht 
begriffen hat, worauf es in der SW-Entwicklung ankommt.

von Rolf Magnus (Gast)


Lesenswert?

Peter II schrieb:
> Das ganze ist abhängig von dem Compiler. Je besser der Compiler desto
> kleiner sind die unterschiede. Im Idealfall kommt sogar der gleiche Code
> raus.

Dazu kommt noch, daß es auch von der Zielplattform abhängt. Schon 
alleine auf Plattformen, die mehr als 8 Bit breit sind, bekommt man 
Geschwindigkeitsvorteile daraus, wenn man nicht jedes Byte einzeln 
kopiert. Dann gibt es außerdem noch Plattformen, bei denen es ganz 
unterschiedliche Arten zum Kopieren gibt. Auf dem PC geht das bei 
Programmen, die öfter Speicherblöcke schnell kopieren müssen, so weit, 
daß sie beim Programmstart einen Performance-Test machen und dann später 
die speziell auf diesem Rechner performanteste Variante benutzen.

... schrieb:
> Ich dachte immer zwischen Pointern und Arrays gibt es keine
> Unterschiede.
> Das ist nur eine andere Schreibweise.

Ja, das ist richtig. p[i] ist zu 100% äquivalent zu *(p+i).

> Zudem verbieten manche Standarts auch über Pointer zu iteriren.
> Könnte mir jemand erklären warum das so ist?

Bei der einen Variante wird der Pointer in jedem Schleifendurchlauf 
inkrementiert, bei der anderen wird dagegen der Offset inkrementiert und 
in jedem Schleifendurchlauf zum Pointer addiert. Das ist aber erstmal 
nur die C-Ebene, die für die Performance völlig unerheblich ist. Wichtig 
ist, was der Optimizer des Compilers daraus macht. Abgesehen davon gibt 
es auf manchen Plattformen auch Instruktionen, denen man einfach direkt 
den Pointer und den Offset übergeben kann und die dann nebenher ohne 
zusätzlich Zeit zu verbrauchen, diese zusammenzählen können. Teilweise 
kann dort das Inkrementieren eines speziellen Adressregisters auch 
langsamer sein als das Inkrementieren eines allgemeinen Registers.
Also ist selbst ohne Optimizer und wenn der Compiler die C-Answeisungen 
1:1 umsetzt keine sichere Aussage möglich, was schneller wäre.

Mit anderen Worten: Der Herr Guru erzählt Mist und sollte mal meditieren 
gehen. Das kann er dann auf seinem Amiga tun, wo die Welt vielleicht 
sogar noch so ist, wie er sie sich vorstellt.

von lalala (Gast)


Lesenswert?

Rolf Magnus schrieb:
> p[i] ist zu 100% äquivalent zu *(p+i).

stimmt. Und da die Kommutativität der Addition gilt ist:

p[i]  <==> *(p+i) <==> *(i+p) <==> i[p]

verwirrt aber eher den Leser.

von Hardware Kenner (Gast)


Lesenswert?

> "Das ist viiieeeeel effektiver, der Geschwindigkeitsvorteil gegenueber
> der Index-Variante ist mindestens Faktor 3. Bei der Index-Variante muss
> ja bei jeder Zuweisung zweimal die neue Adresse berechnet werden, was ja
> die ALU beansprucht und das kostet Zeit. Den Zeiger einfach Hochzaehlen
> passiert nebenbei, das ist viel schneller."


Beim DSP mit extra Adressberechnungsunit (VLIW) hat er recht, da ist 
pointer Inkrement besser als der Umweg über die ALU.

Halt eine Frage der CPU-Architektur, wahrscheinlich ist das schon bei 
Harvard vers von Neumann unterschiedlich.


Bei so einem Waschmaschinencontroller wie dem AVR, der keine parallele 
adressberechnung kennt (?) ist natürlich wurscht.


MfG,

von .... (Gast)


Lesenswert?

verwirrt auch den compiler, wenn du einen int ohne cast 
dereferenzierst.. spars dir doch einfach. -.-

von .... (Gast)


Lesenswert?

....der AVR kann 16bit pointer post/pre-inc/decementieren

von Steffen R. (steffen_rose)


Lesenswert?

Rolf Magnus schrieb:
> ... schrieb:
>> Ich dachte immer zwischen Pointern und Arrays gibt es keine
>> Unterschiede.
>> Das ist nur eine andere Schreibweise.
>
> Ja, das ist richtig. p[i] ist zu 100% äquivalent zu *(p+i).

Gilt dies den auch bei C++? Operatoren könnten ja überladen sein.
Nur mal ganz spitzfindig gefragt.

Oder?

von Karl H. (kbuchegg)


Lesenswert?

Steffen Rose schrieb:
> Rolf Magnus schrieb:
>> ... schrieb:
>>> Ich dachte immer zwischen Pointern und Arrays gibt es keine
>>> Unterschiede.
>>> Das ist nur eine andere Schreibweise.
>>
>> Ja, das ist richtig. p[i] ist zu 100% äquivalent zu *(p+i).
>
> Gilt dies den auch bei C++?

Bei Pointer: ja.

> Operatoren könnten ja überladen sein.
> Nur mal ganz spitzfindig gefragt.

Du kannst aber nicht die [] Operation für Pointer oder int überladen.

D.h. beim gemeinsamen 'Subset' Pointeroperationen, ist das in C nicht 
anders als in C++

von lalala (Gast)


Lesenswert?

.... schrieb:
> verwirrt auch den compiler, wenn du einen int ohne cast
> dereferenzierst..

es wurde kein int dereferenziert.

von (prx) A. K. (prx)


Lesenswert?

Kaj schrieb:
> Stimmt das, was uns der werte Herr da zum Geschwindigkeitsvorteil
> erzaehlt hat prinzipiel

Ja, dem Prinzip nach, wenngleich nicht unbedingt vom Faktor her. Und 
wenn man dazu in der Zeit zurück reist, so in die 80er Jahre oder davor, 
als Compiler noch ungefähr das machten, was man hinschrieb.

Und auch dann hätte ich es nur akzeptiert, wenn er es so gemacht hätte:
1
void copy(int* src, int* dst, int elements)
2
{
3
  for (int *limit = src + elements; src < limit; )
4
    *dst++ = *src++;
5
}
oder (manchmal) noch besser und stilistisch angepasst:
1
void copy(src, dst, elements)
2
  int *src, *dst;
3
{
4
  if (elements) {
5
    int *limit = src + elements;
6
    do *dst++ = *src++; while (src < limit);
7
  }
8
}

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:


> oder (manchmal) noch besser und stilistisch angepasst:

Ich bin mir jetzt gar nicht mal sicher, ob es in K&R C schon erlaubt 
war, innerhalb von Blöcken neue Variablen zu definieren. Aus dem Bauch 
raus würde ich sagen: Nein, die mussten alle am Anfang der Funktion 
stehen.

Aber ansonsten fand ich die Formulierung "oder (manchmal) noch besser 
und stilistisch angepasst" Klasse, um darauf hinzuweisen, dass das 
eventuell mal so war, kurz nachdem wir von den Bäumen runter gekomen 
sind :-)

von (prx) A. K. (prx)


Lesenswert?

Karl Heinz schrieb:
> Ich bin mir jetzt gar nicht mal sicher, ob es in K&R C schon erlaubt
> war, innerhalb von Blöcken neue Variablen zu definieren.

Ich meine schon. Das war beispielsweise nützlich, um in "switch" 
Statements abschnittsweise verschiedene "register" Variablen nutzen zu 
können. Die hätte ich eigentlich auch reinschreiben müssen. ;-)

Es ist aber ein anderer Fehler drin: "void".

Also:
1
copy(src, dst, elements)
2
  register *src, *dst;
3
{
4
  if (elements) {
5
    register *limit = src + elements;
6
    do *dst++ = *src++; while (src < limit);
7
  }
8
}

: Bearbeitet durch User
von Hardware Kenner (Gast)


Lesenswert?

Interessante Messungen:

http://nadeausoftware.com/articles/2012/05/c_c_tip_how_copy_memory_quickly

Fazit:

*wer seinem Compiler blind vertraut hat verloren
*Bibliotheksroutinen sind ideal.

-----

von asdf (Gast)


Lesenswert?

Hat der Guru auch einen Namen? Ich kann gar nicht glauben, dass jemand 
fuer C++ Performance auftritt und dann so einen Quatsch verzapft.

von Walter Tarpan (Gast)


Lesenswert?

Karl Heinz schrieb:
> Ich bin mir jetzt gar nicht mal sicher, ob es in K&R C schon erlaubt
> war, innerhalb von Blöcken neue Variablen zu definieren.

Ja. In meinem K&R steht zu mindest "nur am Anfang eines Blocks".

von Mendax (Gast)


Lesenswert?

asdf schrieb:

> Hat der Guru auch einen Namen? Ich kann gar nicht glauben, dass jemand
> fuer C++ Performance auftritt und dann so einen Quatsch verzapft.

Die Geschichte ist bestimmt erfunden.

von ?!? (Gast)


Lesenswert?

Mendax schrieb:
> Die Geschichte ist bestimmt erfunden.

Glaube nicht. Eher noch untertrieben. Du ahnst ja gar nicht, wie viele 
ausrangierte Möchtegerns sich als Lehrer oder Berater aufspielen. 
Übrigens nicht nur in der IT...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Karl Heinz schrieb:
> Ich bin mir jetzt gar nicht mal sicher, ob es in K&R C schon erlaubt
> war, innerhalb von Blöcken neue Variablen zu definieren.

In der hier:

http://cm.bell-labs.com/cm/cs/who/dmr/cman.pdf

verlinkten Version der 6. Ausgabe von UNIX (1975) offenbar noch nicht,
später dann schon.

von (prx) A. K. (prx)


Lesenswert?

Jörg Wunsch schrieb:
> verlinkten Version der 6. Ausgabe von UNIX (1975) offenbar noch nicht,
> später dann schon.

Für Fans entsprechender Archäologie: In dieser Version gab es weder 
"unsigned" noch "long", und es hiess a =+ 1 statt a += 1 (was bedeutet 
"a=-1"? ;-). Dieser Sprachlevel steht im üblichen Verständnis noch vor 
"K&R".

: Bearbeitet durch User
von Läubi .. (laeubi) Benutzerseite


Lesenswert?

Kaj schrieb:
> das ganze moeglichst effektiv

Ich hoffe mal das Ziel war es möglichst EFFIZIENT zu sein, effektiv ist 
wohl jeder Ansatz der die Kopieroperation fehlerfrei durchführt ob es 
jetzt 1ms oder 1 Jahr dauert...

von C++-Antiguru (Gast)


Lesenswert?

Effizient & Effektiv ist dann die Verwendung von std::copy, zumindest 
wenn man sich C++-"Guru" nennen will.

Und wenn std::copy zu langsam für einen speziellen Fall ist: einfach 
eine neue Template-Specialization anlegen, notfalls mit Handgezimmertem 
Inline-ASM.

von (prx) A. K. (prx)


Lesenswert?

C++-Antiguru schrieb:
> notfalls mit Handgezimmertem Inline-ASM.

Handgezimmerter Assembler-Code für genau solche Aktionen ist mitunter 
eher ineffizient, es sei denn man richtet sich nach dem Optimization 
Guide des konkret verwendeten Prozessors in Abhängigkeit von der Grösse 
der zu kopierenden Daten und hat das Glück, das es im Guide drinsteht 
und dessen Inhalt zufällig auch stimmt. Optimal schneller Code dafür 
kann nämlich bei Prozessoren jenseits der µC Klasse verteufelt 
kompliziert sein.

: Bearbeitet durch User
von Hacker (Gast)


Lesenswert?

Stichwort:

Loop unrolling

von Blubb (Gast)


Lesenswert?

Ich denke, das Beste wird sein, ihr kompiliert mal vor seinen Augen die 
Beispiele und druckt den Code aus, damit ihr es schwarz auf weiß habt. 
Interessant wird vor allem seine Reaktion sein.
Kannst ja mal posten, wie die Geschichte weiter ging. ;) Ist 
wahrscheinlich auch unterhaltsamer als Compilerarchäologie.

von (prx) A. K. (prx)


Lesenswert?

Hacker schrieb:
> Loop unrolling

Das ist nur eines von vielen Stichworten.

von uwe (Gast)


Lesenswert?

Ich würds mit dem DMA Controller machen =;) und die CPU für was andres 
benutzen da bin ich dann auch schneller.
Manche CPUs haben auch Lopp-Hardware drinn (Nen Loop Counter der 
automatisch inkrementiert wird und nen Komparator) dadurch kann 
gleichzeitig die Pipeline perfekt gefüllt werden bzw. Cache vorgeladen 
da die Branch prediction Unit dann weiß wann wohin gesprungen wird(bei 
manchen DSPs). Hängt also vom Compiler ab bzw. bei DMA usw. von den 
Biblioteken die man benutzt und nicht vom C(++) Code.

von uwe (Gast)


Lesenswert?

> Stichwort:
>
>Loop unrolling
Ja schon, aber nicht selber sondern die Compileroption aktivieren.

von (prx) A. K. (prx)


Lesenswert?

uwe schrieb:
> Ich würds mit dem DMA Controller machen =;)

DMA Controller sind nicht unbedingt für grosse Blocktransfers von 
Speicher zu Speicher optimiert. Daher ist das oft der langsamste Weg. 
Ganz besonders, wenn du versuchen solltest, das auch auf dem PC mit dem 
DMA Controller zu erledigen. ;-)

: Bearbeitet durch User
von uwe (Gast)


Lesenswert?

> DMA Controller sind nicht unbedingt für grosse Blocktransfers von
> Speicher zu Speicher optimiert.
Ist zwar etwas weg vom Thema aber...
Bei DSPs schon, die haben Dual ported RAMs bzw. Dual Access RAMs und 
dann auch noch werschieden RAM bereiche (Blöcke) die einen jeweils 
eigenen Adress und Datenbus und Adressberechnungeinheit(also Increment 
und Offset und shifts werden von einer speziellen adressberechnung ALU 
gemacht).
Die CPU kann also aus einem RAM lesen der automatisch inkrementiert wird 
ohne die ALU der CPU zu benutzen gleichzeitig aus einem anderen RAM 
lesen der automatisch dekrementiert wird, gleichzeitig verrechnen 
gleichzeitig zurechshiften, gleichzeitig in einen anderen RAM schreiben 
aus dem gleichzeitig der DMA Controller das Ergebnis ließt und irgendwo 
hinschreibt, alles in einem Takt Die Daten die die CPU aus dem RAM 
ausließt kommen natürlich von einem anderen Kanal des DMA Controllers.

von Markus F. (mfro)


Lesenswert?

Kaj schrieb:
> und hier die "richtige Loesung" der
> Aufgabe

... wenn man schon einen nicht optimierenden Compiler dazu bringen will, 
optimierten Code zu erzeugen, darf man doch das Loop-Unrolling nicht 
vergessen.

Ich hätte also mindestens was von diesem Kaliber erwartet:
1
cpy(int *to, int *from, int count)
2
{
3
    int n = (count + 7) / 8;
4
    switch (count % 8)
5
    {
6
        case 0:  do {    *to++ = *from++;
7
        case 7:         *to++ = *from++;
8
        case 6:         *to++ = *from++;
9
        case 5:         *to++ = *from++;
10
        case 4:         *to++ = *from++;
11
        case 3:         *to++ = *from++;
12
        case 2:         *to++ = *from++;
13
        case 1:         *to++ = *from++;
14
        } while (--n > 0);
15
    }
16
}

von Falk B. (falk)


Lesenswert?

@ A. K. (prx)

>> Ich würds mit dem DMA Controller machen =;)

Me too!

>DMA Controller sind nicht unbedingt für grosse Blocktransfers von
>Speicher zu Speicher optimiert. Daher ist das oft der langsamste Weg.

Nanana, nimmst du den gurkisten DMA-Controller, denn du kennst als 
Standard?

>Ganz besonders, wenn du versuchen solltest, das auch auf dem PC mit dem
>DMA Controller zu erledigen. ;-)

Früher (tm), als es noch richtige Männer gab, die Mammuts mit der Keule 
erlegten und Assembler programmierten, gab es mal einen coolen 
Homecomputer namens Amiga. Der hatte u.a. einen Blitter (Block Image 
Transferer). Der konnte rasend schnell Daten im RAM kopieren, deutlich 
schneller als die CPU. Und wenn es sein sollte, nebenbei noch 
Verschiebeoperationen und logische Verknüpfungen durchführen. Damit 
wurden anno dazumal mordsmäßige Effekte erzeugt, die heute die meisten 
GHz Maschinen in ihrem JAVA-Korsett nicht mal ANSATZWEISE hinkriegen! Es 
ist sooo traurig. seufz Schön war die Zeit.

http://www.eevblog.com/2013/03/13/eevblog-438-amiga-500-retro-computer-teardown/

*Thumbs up!!!*

And last but not least läuft auch auf dem PC schon seit Ewigkeiten 
DMA-Transfer, z.B. über den PCI-Bus!

von Christian B. (casandro)


Lesenswert?

Irgendwie bestätigt das mich in meinem Glauben, dass es auf der Welt 
vielleicht 100 Leute gibt, die C++ wirklich können. Darum scharen sich 
dann ein paar Tausend, die einen kleinen Teil von C++ brauchbar können, 
und hunderttausende von Leuten die glauben sie seien die tollsten C++ 
Programmierer und in Wirklichkeit nichts können.

von Karl H. (kbuchegg)


Lesenswert?

Christian Berger schrieb:
> Irgendwie bestätigt das mich in meinem Glauben, dass es auf der Welt
> vielleicht 100 Leute gibt, die C++ wirklich können. Darum scharen sich
> dann ein paar Tausend, die einen kleinen Teil von C++ brauchbar können,
> und hunderttausende von Leuten die glauben sie seien die tollsten C++
> Programmierer und in Wirklichkeit nichts können.

Die wären mir noch soweit egal. Richtig schlimm wird es dann, wenn 
einige dieser hunderttausend in die Lehre gehen (Uni, FH oder Schule) 
oder Consulting betreiben. Diese Fälle sind nicht so selten, wie man 
glauben könnte oder annehmen sollte.

: Bearbeitet durch User
von Christian B. (casandro)


Lesenswert?

Karl Heinz schrieb:
> Die wären mir noch soweit egal. Richtig schlimm wird es dann, wenn
> einige dieser hunderttausend in die Lehre gehen (Uni, FH oder Schule)
> oder Consulting betreiben. Diese Fälle sind nicht so selten, wie man
> glauben könnte oder annehmen sollte.

Ach, ich finde es schlimmer wenn die hunderttausend schlechten Code 
produzieren. Ich bin ja für mich auf dem Standpunkt, dass ich zu blöd 
bin C++ zu programmieren. Ich bin wohl keiner der paar Hundert Leute die 
das können, und es ist nicht wahrscheinlich, dass ich innerhalb einiger 
Jahre genügend Wissen und Erfahrung ansammeln kann, so weit zu kommen. 
Somit ist das Thema C++ für mich persönlich beendet.

von Ret (Gast)


Lesenswert?

Christian Berger (casandro) schrieb:

> Ach, ich finde es schlimmer wenn die hunderttausend schlechten Code
> produzieren. Ich bin ja für mich auf dem Standpunkt, dass ich zu blöd
> bin C++ zu programmieren. Ich bin wohl keiner der paar Hundert Leute die
> das können, und es ist nicht wahrscheinlich, dass ich innerhalb einiger
> Jahre genügend Wissen und Erfahrung ansammeln kann, so weit zu kommen.
> Somit ist das Thema C++ für mich persönlich beendet.

Und (wenn man fragen darf) wie lautet deine persönliche 
Ausweichstrategie darauf, nun mit "ohne C++" auszukommen, in Bezug aufs 
µC und PC programmieren?

von Thomas E. (thomase)


Lesenswert?

Karl Heinz schrieb:
> Richtig schlimm wird es dann, wenn
> einige dieser hunderttausend in die Lehre gehen (Uni, FH oder Schule)
> oder Consulting betreiben. Diese Fälle sind nicht so selten, wie man
> glauben könnte oder annehmen sollte.
Das reicht doch vollkommen aus.

Ich war schon öfter auf Veranstaltungen, auf denen ich nahezu der 
einzige war, den das interessiert hat. Der Rest war einfach auch mal mit 
einem Lehrgang dran und hat von seiner Firma Arschbacken breitsitzen und 
Spesen bezahlt bekommen.

Pech allerdings, wenn plötzlich alle was wissen und zu allem Überfluss 
auch noch mehr wissen wollen und der Dozent völlig überfordert mit 
dieser unbekannten Situation ist.

Aber davon wird der sich auch nicht entmutigen lassen.

mfg.

: Bearbeitet durch User
von NS32 (Gast)


Lesenswert?

Optimal war (damals) der C-Compiler für die NS32000-Serie.

Das wurde dann direkt durch die CPU mit dem Befehl

MOVSi  (Move String 1 to String 2)


gemacht und fertig.

Solche Op-Codes sind mir bis heute leider nicht mehr über den Weg 
gelaufen.
War schon prima, was diese Prozessoren konnten.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

NS32 schrieb:
> War schon prima, was diese Prozessoren konnten.

Aber nur, bis man auf die Taktzyklenzahl des Befehls geguckt hat. :-)

Irgendwann hat man dann mal bemerkt, dass es mehr Sinn hat, dem
Compiler die Optimierung zu überlassen, statt mehrere Dutzend
Spezialfälle im Microcode „vorzuoptimieren“.

von (prx) A. K. (prx)


Lesenswert?

NS32 schrieb:
> War schon prima, was diese Prozessoren konnten.

CISC to the max, nur NEC hatte es beim Versuch, die DEC VAX auf der 
falschen Spur zu überholen, mit V70/V80 noch weiter getrieben. Solche 
Befehlssätze sind zwar phantastisch für den Assember-Programmierer. Mir 
ging es zunächst nicht anders. Das war jene Ära, in der einerseits die 
CISCs prächtige hoffnungslos überladene Blüten trieben, und andererseits 
die ersten RISCs (MIPS, ARM(!)) diesem barocken Schwulst abschworen.

So war die Codierung der Displacements zwar wunderbar platzsparend. Aber 
sobald du in der Lage sein willst, einen Befehl pro Takt zu dekodieren, 
geschweige denn mehrere, ist das ein Schuss in Knie. Motorola lief bei 
dem Schritt von 68000/10 zu 68020 in eine ähnliche Falle und hat den 
später verflucht.

Letztlich bewies NS damit nur, dass dieser Weg in die falsche Richtung 
führte und der Aufwand für die Verarbeitung der Befehle und den ganzen 
Microcode anderweitig besser investiert worden wäre. Auch, weil solche 
Komplexität zu einer hässlichen Anzahl in Silizium gegossener Bugs 
führt.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

Naja, jeder halbwegs gescheite Compiler nutzt doch wahrscheinlich 
sinnvollerweise eine handoptimierte Assemblerversion für memcpy(), 
Spezialprozessoren gern auch DMA & sonstige Features. Womit die 
Diskussion über Pointer vs. Index überflüssig wird.

von (prx) A. K. (prx)


Lesenswert?

Falk Brunner schrieb:
> Naja, jeder halbwegs gescheite Compiler nutzt doch wahrscheinlich
> sinnvollerweise eine handoptimierte Assemblerversion für memcpy(),

Wenn man freilich genau reinschaut, weil sich in der Anwendung 
Optimierung an diese Stelle wirklich lohnt, und es nicht bloss um ein 
paar Strings geht, dann wird man vielleicht mit nicht zum 
Basisbefehlssatz gehörenden Befehlen, Prefetch, Cache-Hints etc. an die 
Grenze der konkreten Hardware gehen wollen. Denn das bieten 
Compiler-Runtimes aufgrund ihrer Unabhängigkeit von der exakt 
eingesetzten CPU nicht unbedingt.

: Bearbeitet durch User
von Fpgakuechle K. (Gast)


Lesenswert?

Kaj schrieb:

> "Das ist viiieeeeel effektiver, der Geschwindigkeitsvorteil gegenueber
> der Index-Variante ist mindestens Faktor 3. Bei der Index-Variante muss
> ja bei jeder Zuweisung zweimal die neue Adresse berechnet werden, was ja
> die ALU beansprucht und das kostet Zeit. Den Zeiger einfach Hochzaehlen
> passiert nebenbei, das ist viel schneller."

Im Prinzip richtig.


> So, diese Geschichte zum Geschwindigkeitsvorteil hat mir keine Ruhe
> gelassen und ich hab es jetztmal getestet.

Gute Idee!

>
> Umgebung:
> Atmel Studio 6.2
> AVR GCC 4.8.1
> AVR Projekt (Mega 2560)

Schlechte Wahl, nicht vergleichbar.

> So, jetzt die eigentlichen Fragen:
> Stimmt das, was uns der werte Herr da zum Geschwindigkeitsvorteil
> erzaehlt hat prinzipiel und das liegt hier jetzt einfach daran das ich
> es mit dem AVR-Simulator getestet habe, oder ist das Grundsaetzlicher
> bloedsinn was uns da erzaehlt wurde?

Ja die Begründung des Guru ist korrekt. Vorausgesetzt das Target hat 
parallel zur ALU noch Module für die Adressberechnung kann es 
tatsächlich
3 Operatione (1xALU+2xAdressincrement) in einem Maschinenzyklus 
ausführen.

Das ist bei DSP gängig , da es Skalarprodukte wie bei Digitalen Filtern 
deutlich beschleunigt. Bspw. SHARC: siehe 
http://en.wikipedia.org/wiki/Very_long_instruction_word#Design


Da der Atmel AVR solche parallelen inkrement-Blöcke nicht hat, sondern 
jede Berechnung über die ALU geht braucht dieser mind. 3 Taktzyklen.
Deine Test vergleichen also Äpfel mit Birnen.

> 2. Ein moderner Compiler mit eingeschalteter Optimierung dürfte da
> wahrscheinlich keinen Unterschied produzieren.

Das ist leider nur ein frommer Wunsch wie diese Tests
http://nadeausoftware.com/articles/2012/05/c_c_tip_how_copy_memory_quickly
von 2012 anschalich zeigen. Bei diesen wurde speicher auf verschiedenene 
Weisen kopiert darunter pointer,index und memcpy.

Würden die Compiler wirklich optimieren können wäre die Durchlaufzeit 
für jeden Code gleich. Wie die Graphen zeigen ist dem nicht so. Gerade 
mal der intel-compiler schafft das fast perfekt, der gcc liefert bei 
memcpy oft schlechtere Ergebnisse als mit pointern.


MfG,

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

NS32 schrieb:
> Solche Op-Codes sind mir bis heute leider nicht mehr über den Weg
> gelaufen.

Naja, LDIR kannte der Z80 aber auch schon.

Jörg Wunsch schrieb:
> Aber nur, bis man auf die Taktzyklenzahl des Befehls geguckt hat. :-)

Hehehe, darüber musste man beim Z80 dann auch nachdenken.

von Ret (Gast)


Lesenswert?

A. K. (prx) schrieb:

Falk Brunner schrieb:
>> Naja, jeder halbwegs gescheite Compiler nutzt doch wahrscheinlich
>> sinnvollerweise eine handoptimierte Assemblerversion für memcpy(),

> Zumindest so lange, bis man wirklich man genau reinschaut, und bei
> grösserer Speichermenge mit nicht zum Basisbefehlssatz gehörenden
> Befehlen, Prefetch, Cache-Hints etc. an die Grenze der konkreten
> Hardware geht. Denn das bieten Compiler-Runtimes aufgrund ihrer
> Unabhängigkeit von der exakt eingesetzten CPU nicht unbedingt.

Mal ehrlich, solche allgemeinen Aussagen zur Effizienz von C++ - wie 
oder was nun besonderen "Geschwindigkeitsvorteil" bringen soll - beruhen 
doch schlicht größtenteils auf der Annahme, der gerade zur Verfügung 
stehende Compiler möge gefälligst den denkbar bestmöglichen Code ins 
Compilat einpflügen, nach dem Motto, "mein Compiler macht dat scho. Der 
is ja soo schlau!". ;-)

Ob das im ausführenden Programm dann wirklich so ist und ob das dann 
auch die bestmögliche Ausführungszeit erbringt, überprüfen doch die 
Allerwenigsten. Deswegen kann so ein "Seminarguru" auch mal schnell 
Behauptungen wie im Eingangsthread aufstellen und die Teilnehmer 
abqualifizieren. Er selber bringt Null Beweise seiner Aussage. Er ist 
schließlich der "Guru", die Teilnehmer sind aus seiner Sicht die 
Dummchen. Warum stellen solche Leute nicht auch mal ein paar 
Messergebnisse für ihre Aussagen zur Verfügung, dann könnte man ihnen 
wenigstens Glauben schenken und selber mal Vergleiche anstellen. 
Vielleicht gäbe es dann auch mal ein paar Überraschungen. Wer weiß!

Ein anderer Aspekt wäre zudem, ob solche Effizienzhascherei immer den 
Königsweg darstellen, insbesondere dann, wenn dafür der Code schwieriger 
zu schreiben und erst recht zu verstehen ist und somit auch 
fehlerträchtiger wird. Ganz abgesehen vom horrenden Zeitaufwand der 
betrieben werden muss, wenn die Dinge dann nicht so laufen, wie man es 
erwartet.

Hier beißt sich doch die Mietzekatze in ihren Kringelschwanz. C++ 
Protagonisten verführen regelrecht gewollt oder ungewollt mit ihrem 
fortwährenden Betonen ihrer "Überlegenheitskultur" gegenüber anderen 
Programmiersprachen andere dazu, sich auch in C++ zu probieren. Der Ball 
wird dann prompt gerne aufgenommen. Man will schließlich auch irgendwie 
zur "C++ Elite" dazugehören. Dann aber stechen die Nebenwirkungen und 
Begleiterscheinungen aus dem (C++)-Beipackzettel beim Probanden immer 
mehr durch. Ergebnis: Dass gerade daraus dann vielleicht umso mehr 
fehlerhafte und gar nicht immer so Effizient wie behauptet Programme 
erwachsen, weil kaum einer dieses Thema richtig beherrscht, steht dem 
ganzen schönen Thema C++ hässlich gegenüber, wird aber gerne unter den 
Teppich gekehrt, weil die Wenigsten freiwillig und offen zugeben, dass 
sie ihr C++ eben nicht so richtig oder bisweilen sogar gar nicht 
beherrschen. REAL werden dann eher die gewohnten C-Programme 
geschrieben, durch den C++ Compiler geschickt und heraus kommt sowas wie 
.. C+.

;)

von (prx) A. K. (prx)


Lesenswert?

Fpga Kuechle schrieb:
>> ja bei jeder Zuweisung zweimal die neue Adresse berechnet werden, was ja
>> die ALU beansprucht und das kostet Zeit. Den Zeiger einfach Hochzaehlen
>> passiert nebenbei, das ist viel schneller."
>
> Im Prinzip richtig.

Wenn man die Umsetzung C in Assembler genau so versteht, wie es da 
steht, dann landen wir x86 Befehlen bei
   pointer_loop:
     load (src)
     increment src
     store (dst)
     increment dst
     increment count
     compare count to limit
     if ok then goto pointer_loop
und
   index_loop:
     load (src+index*n)
     store (dst+index*n)
     increment index
     compare index to limit
     if ok then goto index_loop

Das sind zunächst 7 Befehle gegenüber 5 zu Lasten der Pointer-Version, 
entsprechend auch internen Befehlen des x86 OOO Cores - evtl. werden die 
beiden letzten gemerged, aber das gilt für beide Versionen.

Zwar ist die Berechnung (src) auf dem Papier weit einfacher als 
(src+index*n), aber dafür kannst du dir nichts kaufen, denn den real 
existierenden x86 ist das egal.

Aber selbst wenn die Index-Rechnung mehr Zeit brauchen würde, wäre es in 
den Cores der Oberklasse seit einiger Zeit egal. Denn die sequentiellen 
Abhängigkeiten bei der Adressrechnung aufeinander folgender Iterationen 
betragen jeweils nur einen Takt und sind damit weit kürzer als der 
Transport bei L1 Hit dauert (IIRC 8 Takte aktuell). Weshalb die Adressen 
des Transports auch dann schon lange vor dem Transport selbst zur 
Verfügung stehen, wenn die Adressrechnung einige Takte benötigt.

Soll heissen: Die in internen Operationen kürzere Version gewinnt, oder 
landet beim gleichen durch den Transport durch die jeweiligen Cache 
Levels entstehenden Limit.

Demgegenüber gewinnt derjenige, der die Breite der Datenpfade auch 
ausnutzt, und das ist Stand heute nicht mit "int" zu machen, sondern 
setzt SSE Befehle voraus. Wenn man dann bei grösseren Datenmengen auch 
noch darauf verzichtet, Cache Lines, die sowieso komplett überschrieben 
werden, überhaupt erst in den Cache zu laden, dann kommt man dem Ziel 
schon näher.

Apropos AVR: Der werte Herr kommt wie berichtet aus dem Bereich Server 
und hochgradiger Parallelisierung. Eine wilde Horde parallel arbeitender 
ATmegas wird er damit nicht gemeint haben. ;-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Fpga Kuechle schrieb:
> Im Prinzip richtig.

Yep.

Auf 'ner PDP-11.

Vor 30 Jahren.

von (prx) A. K. (prx)


Lesenswert?

Ret schrieb:
> Mal ehrlich, solche allgemeinen Aussagen zur Effizienz von C++ - wie
> oder was nun besonderen "Geschwindigkeitsvorteil" bringen soll - beruhen
> doch schlicht größtenteils auf der Annahme, der gerade zur Verfügung
> stehende Compiler möge gefälligst den denkbar bestmöglichen Code ins
> Compilat einpflügen, nach dem Motto, "mein Compiler macht dat scho. Der
> is ja soo schlau!". ;-)

Wenn man in dieser Form auf C Ebene optimiert, also den C Code dem 
anpasst, was der jeweilige Compiler für die jeweilige Maschine an Code 
erzeugt, dann muss man sich mit jedem signifikant neuen Compiler und 
jeder ansdersartigen Maschine neue Gedanken machen, wie man dem Compiler 
in C (oder C++) beibringt, die grad richtigen Befehle zu erzeugen.

Das freilich ist fast immer der falsche Ansatz. Wie grad beschrieben 
kann die Index-Variante allein aufgrund der niedrigeren Anzahl 
Inkrementierungen im Vorteil sein. Auf Maschinen mit Inkrementierung als 
Teil des Befehls löst sich der Unterschied zwar auf, aber dann greift 
bei ausreichend komplexem Core das, was ich grad geschrieben habe.

Ich sehe einfach keinen Sinn mehr darin, auf C/C++ Ebene solchen 
Feinheiten hinterher zu rennen. In den 80ern habe ich das gemacht. Heute 
ist das sinnarm geworden. Wie ich schon schrieb findet eine echte 
Optimierung von Speichertransports im High-Performence Bereich heute 
dort statt, wo der Compiler nicht hinkommt und was eine einfache für 
memcpy Implementierung für Queerbeet-x86/(-64) nicht leisten wird.

Daher spricht für mich nichts dagegen, die simple aber leicht 
verstndliche Index-Version zu verwenden, so lange es nicht wirklich 
kritisch ist. Und wenn es wirklich kritisch ist, dann kommt man mit 
generischem C/C++ sowieso nicht weiter.

> Hier beißt sich doch die Mietzekatze in ihren Kringelschwanz. C++
> Protagonisten verführen regelrecht gewollt oder ungewollt mit ihrem
> fortwährenden Betonen ihrer "Überlegenheitskultur" gegenüber anderen
> Programmiersprachen andere dazu, sich auch in C++ zu probieren.

Die Laufzeit bei C v. C++ hier ins Spiel zu bringen finde ich sowieso 
etwas schräg. Da mag es Optimierungsmöglichkeiten geben. Aber der 
Kernunterschied liegt nicht in irgendwelchen Optimierungen, sondern in 
der Mächtigkeit des Ausdrucks. Und 99% des entstehenden Codes werden 
weniger von der Performance der Laufzeit begrenzt, sondern von der 
Performance des Programmierers, ihn zu schreiben.

Programmierung und Wartbarkeit von Programmen sind näherungsweise 
abhängig von der Anzahl Quellcodezeilen. Da die Mächtigkeit von C++ sehr 
viel grösser ist als die von C, können entstehende Programme letztlich 
in Quellcodezeilen deutlich kompakter ausfallen, vorausgesetzt der 
Programmierer beherrscht es wirklich.

NB: Ich finde C++ eine ziemlich grässliche Konstruktion - übrigens von 
Anfang an seit der Lektüre des ersten Stroustrup. Kann nicht anders 
sein, da schon C schaurig geraten ist und nie für die Dimension gedacht 
war, in der es heute eingesetzt wird. Aber das ist Philosophie auf der 
Wiese. Das Zeug ist nun einmal da.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Das ist doch alles Kindergarten hier ;-)

Schaut doch erst mal in eine konkrete Implementierung von memcpy, hier 
z.B. in der Newlib für ARM 7a:

https://sourceware.org/viewvc/src/newlib/libc/machine/arm/memcpy-armv7a.S?revision=1.2&view=markup

Ich bezweifle dass da irgendeine der o.g. kiki-Schleifen auch nur 
ansatzweise mitkommt — ok, zum Besteck zeitgemäßer Compiler gehören zwar 
auch Auto-Vectorizer, aber das Hand-optimierte Asm dürfte dennoch besser 
aussehen.

In der Newlib für ARM zählt man insgesamt 5 Asm-Implementierungen von 
memcpy für unterschiedliche Derivate, die je nach Derivat, für das 
gelinkt wird, ausgewählt werden.

Davon ab ist die vom OP gegebene Frage bzl. Optimalität total Banane; 
wurde ja auch schon geschrieben.  Ohne zu wissen für welchen Compiler, 
welche Architektur, welche LibC-Implementierung, etc. ist die Frage kaum 
zu beantworden.

Und übrigens haben auch Compiler eine Vorstellung von memcpy, d.h. auf 
einer C-Implementierung mit sizeof(int) = sizeof(float) erzeugt sowas 
wie
1
unsigned type_pun_float (float f)
2
{
3
    unsigned u;
4
    memcpy (&u, &f, sizeof (int));
5
    return u;
6
}
schon überhaupt keine Kopier-Befehle mehr (z.B. gcc -O2), außer 
natürlich das Kopieren vom Parameter-Register zum Return-Register falls 
diese nicht identisch sind.

von (prx) A. K. (prx)


Lesenswert?

Johann L. schrieb:
> Das ist doch alles Kindergarten hier ;-)

Allerdings. Das ist eine Gespensterdebatte auf dem Stand der frühen 
80er. Völlig missachtend, was sich seither bei Compilern und Prozessoren 
getan hat.

Wenn dann Beispiele aus der AVR Welt ins Spiel gebracht werden, dann 
sollte man beachten, dass Mikroarchitektur und Befehlsabläufe dieser 
AVRs technisch auch nicht weiter sind als ebendiese frühen 80er. 
Zwischen diesen und den PC- und Smartphone-Prozessoren liegen Welten, 
und zwar nicht bloss in MHz.

: Bearbeitet durch User
von Irgendwer (Gast)


Lesenswert?

Johann L. schrieb:
> In der Newlib für ARM...

A. K. schrieb:
> Wenn dann Beispiele aus der AVR Welt...

Wer lesen kann:-)

-

Wenn man sich in der x86 Welt mal ansieht welche Unterschiede zwischen 
dem gcc und dem icc möglich sind. Da könnte einem schon der Verdacht 
kommen das beim Thema Optimierung zumindest beim gcc noch einiges an 
Luft für Verbesserungen vorhanden ist.

von (prx) A. K. (prx)


Lesenswert?

Irgendwer schrieb:
> A. K. schrieb:
>> Wenn dann Beispiele aus der AVR Welt...
>
> Wer lesen kann:-)

Rohrkrepierer. Denn so ging der ganze Spass überhaupt erst los:

Kaj schrieb:
> AVR GCC 4.8.1

von (prx) A. K. (prx)


Lesenswert?

Irgendwer schrieb:
> kommen das beim Thema Optimierung zumindest beim gcc noch einiges an
> Luft für Verbesserungen vorhanden ist.

Sicher. Ich würde mich aber doch etwas wundern, wenn der ICC bei der 
Optimierung des anfänglichen Codes automatisch etwas rund um den MOVNTDQ 
Befehl auswirft. Denn in die Richtung läuft der Hase, wenn es um 
grössere Speichertransfers geht.

: Bearbeitet durch User
von Christian B. (casandro)


Lesenswert?

Ret schrieb:
> Und (wenn man fragen darf) wie lautet deine persönliche
> Ausweichstrategie darauf, nun mit "ohne C++" auszukommen, in Bezug aufs
> µC und PC programmieren?

Also mir wäre noch nie ein Fall untergekommen, wo man einen µC in C++ 
programmiert hätte, und in meinem derzeitigen Job arbeite ich als 
"Softwareentwickler und Netzwerkmanager NGN" und da gibts auch kein C++.

Ich hab auch schon in vorherigen Jobs GUI-Anwendungen gemacht. Die hab 
ich in Lazarus, einem freien Delphi Klon erstellt. Das hat den Vorteil, 
dass das schnell und plattformunabhängig ist, und dass man kein 
Framework installieren muss.

Und selbst wenn ich, aus welchen Gründen auch immer, Die Art von 
Objektorientiertheit von C++ brauche, so kann ich die auch einfach in C 
nachprogrammieren. Das hab ich zugegeben sogar schon mal gesehen. Und 
wenn man das selber macht kann man sogar die üblichen Merkwürdigkeiten 
von C++ umschiffen. (implizite Objektkopien? WTF!)

von Kaj (Gast)


Lesenswert?

Woah, rege beteiligung, das freut mich :-)

Zu erstmal scheint es das ein oder andere missverstaendnis zu geben, was 
freilich mein fehler ist! :-/

Es geht nicht speziel um die AVR oder sonstige Mikrocontroller, 
lediglich mein Beispiel hat sich auf einen AVR bezogen. Dass das sehr 
unklug war, ist mir mittlerweile auch aufgefallen, und dafur 
entschuldige ich mich auch. Die Aufgabe des "Dozenten" war mehr 
allgemeiner Natur, ich habe es halt im AVR umfeld getestet, was durch 
aus legitim ist, finde ich.

Das aendert aber, meiner meinung nach, nichts daran, das aussagen wie 
"Variante B ist schneller als Variante A, weil..." keine allgemeine 
gueltigkeit besitzen koennen, da keine Randbedingungen genannt wurden.
Und genau das ist passiert, und da hat natuerlich zu dem Zeitpunkt auch 
keiner von uns dran gedacht nach zu fragen unter welchen umstaenden denn 
dieser geschwindigkeitsvorteil greifen soll.
Das ist so, als wenn ich sage:
1
Nachts sind alle Katzen grau
und dabei die Randbedingung, dass das nur Nachts gilt, weglasse.

Fpga Kuechle schrieb:
>> Umgebung:
>> Atmel Studio 6.2
>> AVR GCC 4.8.1
>> AVR Projekt (Mega 2560)
>
> Schlechte Wahl, nicht vergleichbar.
Dachte ich mir schon. :-/
Deswegen fragte ich ja auch, ob es an meinem Test liegt.
Kaj schrieb:
> liegt hier jetzt einfach daran das ich
> es mit dem AVR-Simulator getestet habe, oder ist das Grundsaetzlicher
> bloedsinn was uns da erzaehlt wurde?

Fpga Kuechle schrieb:
>> So, diese Geschichte zum Geschwindigkeitsvorteil hat mir keine Ruhe
>> gelassen und ich hab es jetztmal getestet.
>
> Gute Idee!
Danke :-)

A. K. schrieb:
> Apropos AVR: Der werte Herr kommt wie berichtet aus dem Bereich Server
> und hochgradiger Parallelisierung. Eine wilde Horde parallel arbeitender
> ATmegas wird er damit nicht gemeint haben. ;-)
Ja, stimmt schon. Wie geschrieben, Ich hab es halt mit AVR getestet. 
Deswegen schrieb ich ja auch:
Kaj schrieb:
> Stimmt das, was uns der werte Herr da zum Geschwindigkeitsvorteil
> erzaehlt hat prinzipiel und das liegt hier jetzt einfach daran das ich
> es mit dem AVR-Simulator getestet habe, oder ist das Grundsaetzlicher
> bloedsinn was uns da erzaehlt wurde?
Apropos: Was ist eigentlich an einer wilden Horde parallel arbeitender 
ATmegas auszusetzen? :-P

Peter II schrieb:
> Denn es ist langsamer eine Variable von 0 bis irgendwas zu zählen als
> andersrum.
Warum ist das eigentlich so? Denn soweit ich weiss, betrifft das ja 
nicht nur Mikrocontroller, sondern auch "normale" Prozessoren. Ist das 
mal wieder so ein Historisch-Gewachsenes-Ding, oder gibt es da irgendwo 
eine handfeste Begruendung, weshalb das so umgesetzt wurde das runter 
schneller ist als rauf? Ich meine, wenn man es so umsetzten kann, dann 
haette man es ja auch andersrum umsetzten koennen, also das rauf 
schneller ist als runter.

uwe schrieb:
>> Stichwort:
>>
>>Loop unrolling
> Ja schon, aber nicht selber sondern die Compileroption aktivieren
Dafuer gibt es eine Compileroption? o_0

Bronco schrieb:
> Überlasst das Optimieren dem Compiler und
> konzentriert Euch auf die Aufgabe!
Da stimme ich dir zu. Trotzdem kann man sich doch bemuehen, von 
vornerein, wenigstens halbwegs guten Code zu schreiben, oder nicht? (Ob 
gut nun schnell oder speichersparend ist, haengt wieder von der 
situation ab.)

Karl Heinz schrieb:
> Diesen sog. Guru hätt ich rausgeschmissen
Sagen wir es mal so:
Er war danach nicht mehr sehr lange im Haus, und wir waren nicht weit 
davon entfernt den Mann mit brennenden Fackeln und Mistgabeln vom 
Firmengelaende zu "begleiten" :D

Läubi .. schrieb:
> Ich hoffe mal das Ziel war es möglichst EFFIZIENT zu sein
Aehem...*hust*.. ja meinte effizient. Mein Fehler :-/

A. K. schrieb:
> Irgendwer schrieb:
>> A. K. schrieb:
>>> Wenn dann Beispiele aus der AVR Welt...
>>
>> Wer lesen kann:-)
>
> Rohrkrepierer. Denn so ging der ganze Spass überhaupt erst los:
>
> Kaj schrieb:
>> AVR GCC 4.8.1
Wie schon geschrieben: Mein Fehler. :( Lediglich mein Test-Setup 
basiert darauf.

Im grossen und ganzen kann ich dieser Diskussion entnehmen, das Herr 
mehr oder minder Recht hat, abhaengig von Compiler, Zielplatform usw.

Ich danke euch aufjedenfall fuer diese erleuchtung und die rege 
teilnahme :-)

von (prx) A. K. (prx)


Lesenswert?

Kaj schrieb:
>> Denn es ist langsamer eine Variable von 0 bis irgendwas zu zählen als
>> andersrum.
> Warum ist das eigentlich so?

Bis 0 runterzählen kann bedeuten, dass man sich den Vergleich mit der 
Endbedingung ersparen kann, weil die Dekrementierung das Zero-Flag 
setzt. Vorausgesetzt freilich, es gibt überhaupt Flags, was keineswegs 
immer der Fall ist.

> Wie schon geschrieben: Mein Fehler. :( Lediglich mein Test-Setup
> basiert darauf.

Das war nicht aggressiv gemeint, nur als Information. Ok, der 
"Rohrkrepierer" an die Adresse von "Irgendwer", der mir Leseschwäche 
vorwarf, der war es. ;-)

> Im grossen und ganzen kann ich dieser Diskussion entnehmen, das Herr
> mehr oder minder Recht hat, abhaengig von Compiler, Zielplatform usw.

Nein, denn mit ...

> "Das ist viiieeeeel effektiver, der Geschwindigkeitsvorteil gegenueber
> der Index-Variante ist mindestens Faktor 3."

... liegt er immer und überall dramatisch daneben, sofern es überhaupt 
einen Unterschied macht und er nicht andersrum ausgeht.

Und er kämpft mit Optimierung auf dieser Ebene mit Gespenstern aus den 
80er Jahren. Das ist einfach nur sinnlos.

> Er war danach nicht mehr sehr lange im Haus, und wir waren nicht weit
> davon entfernt den Mann mit brennenden Fackeln und Mistgabeln vom
> Firmengelaende zu "begleiten" :D

Recht getan. ;-)

: Bearbeitet durch User
von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Ich habe den Spass mal kurz in VC++ eingeklopft und das lustige dabei 
ist, das der Code des Gurus angemeckert wird:
1
#include <stdio.h>
2
#define elements 65535
3
int  a[elements] = {42,42,42};
4
int  b[elements] = {0,0,0};
5
main()
6
{
7
int i;
8
  printf ("Start Test 1\n");
9
for (i=0;i<elements;i++) {
10
      b[i] = a[i];
11
}  
12
  printf ("End Test 1\n");
13
// 
14
15
  printf ("Start Test 2\n");  
16
for (i=0;i<elements;i++) {
17
      *b++ = *a++;               // <<< diese Zeile ist Zeile 29
18
}  
19
  printf ("End Test 2\n");
20
  
21
    return(0);
22
}
Fehler ist: "1>Simple.c(29): error C2105: '++' needs l-value"
Hehehe.

von (prx) A. K. (prx)


Lesenswert?

Matthias Sch. schrieb:
> Fehler ist: "1>Simple.c(29): error C2105: '++' needs l-value"

Musst schon Pointer verwenden, wie er, keine Arrays.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christian Berger schrieb:
> Und selbst wenn ich, aus welchen Gründen auch immer, Die Art von
> Objektorientiertheit von C++ brauche, so kann ich die auch einfach in C
> nachprogrammieren.

Das ist dann vielleicht erst 'ne Krücke.  Vereinigung der Nachteile
von allem.

C++ auf Controllern kann durchaus schön sein (gibt ja gerade einen
Nachbarthread, der die Möglichkeiten aufzeigt) und ist gewiss nichts,
was man gleich verteufeln muss.

Nur die Mikro-Optimierungen des „Gurus“ sind einfach Unfug.

von Peter D. (peda)


Lesenswert?

Also meine Erfahrung mit dem AVR-GCC ist, daß ihm die Schreibweise, ob 
Index oder Pointer, meistens völlig wurscht ist. Er erzeugt den gleichen 
Code (-Os).
Versucht man was zu optimieren, wehrt er sich hartnäckig dagegen.

Man kann also ruhig der besseren Lesbarkeit den Vorzug geben und 
Mikrooptimierungen dem Compiler überlassen.

Was ich mich aber frage, was hat das ganze mit C++ am Hut?
Pointer sind doch schnödes plain C.

von Hardware Kenner (Gast)


Lesenswert?

Kaj schrieb:
> Peter II schrieb:
>> Denn es ist langsamer eine Variable von 0 bis irgendwas zu zählen als
>> andersrum.
> Warum ist das eigentlich so? Denn soweit ich weiss, betrifft das ja
> nicht nur Mikrocontroller, sondern auch "normale" Prozessoren. Ist das
> mal wieder so ein Historisch-Gewachsenes-Ding, oder gibt es da irgendwo
> eine handfeste Begruendung, weshalb das so umgesetzt wurde das runter
> schneller ist als rauf? Ich meine, wenn man es so umsetzten kann, dann
> haette man es ja auch andersrum umsetzten koennen, also das rauf
> schneller ist als runter.

Das ist weniger eine Frage des rauf oder runter sondern ob der Vergleich 
auf 0 oder auf eine Nicht-Null Konstante gemacht wird.

Ein Vergleich auf = macht so eine CPU nebenher, jedes mal wenn eine Null 
als Ergebnis in der ALU steht wird das zero-Flag gesetzt. Das verlassen 
der Schleife ist uber einen Springbefehl realisiert der diese Flag 
testet.
Also gleich nach dem Decrement des Index und damit ohne weiteren befehl 
kann die CPU entscheiden ob sie die Schleife verlässt oder nicht. Für 
einige CPU's ist das sogar auf einen einzigen Befehl zusammengefasst:

DECBRNZ - Dekrementiere, Branch wenn nicht Zero


Zähl man dagegen aufwärts muss auf bspw 10 vergleichen werden. Dazu muss 
ein extra Befehl eingefügt werden:

INC   --Inkrementiere
CMP   --Vergleiche
BRNZ  --Branch wenn nicht Zero


bei dem Compare kann es noch weitere Verzögerungen geben wenn der 
schleifenindex nicht im Register steht sondern auf dem Stack oder im 
externen Speicher.

MfG,

von Hardware Kenner (Gast)


Lesenswert?

Typo: statt "Ein Vergleich auf = macht" bitte
"Ein Vergleich auf 0 macht" lesen

von Hardware Kenner (Gast)


Lesenswert?

A. K. schrieb:
>> "Das ist viiieeeeel effektiver, der Geschwindigkeitsvorteil gegenueber
>> der Index-Variante ist mindestens Faktor 3."
>
> ... liegt er immer und überall dramatisch daneben, sofern es überhaupt
> einen Unterschied macht und er nicht andersrum ausgeht.

Doch es macht genau den Faktor 3 aus ob ich 3 mal auf die ALU zugreife 
um Index, src und dest pointer zu inkrementieren, oder nur 1mal die ALU 
benutze und die Pointerarithmetik den data fetch Units überlasse.


> Und er kämpft mit Optimierung auf dieser Ebene mit Gespenstern aus den
> 80er Jahren. Das ist einfach nur sinnlos.

Nope das sind nicht 80 iger Jahre sondern aktuelle 
Architekturoptimierungen wie sie beispielsweise beim Blackfin von Analog 
devices (produziert seit 2008) eingesetzt werden.

Manche sind einfach nur Thread- resp. Beratungsresistent.

MfG,

von (prx) A. K. (prx)


Lesenswert?

Hardware Kenner schrieb:
> Doch es macht genau den Faktor 3 aus ob ich 3 mal auf die ALU zugreife
> um Index, src und dest pointer zu inkrementieren, oder nur 1mal die ALU
> benutze und die Pointerarithmetik den data fetch Units überlasse.

Nur dass der Guru es genau anders herum ausdrückte, d.h. er sah in der 
Index-Variante die 3fach teurere, weil jedesmal die Adresse berechnet 
werden muss. Was auch wieder stimmen könnte, weil ein IA64 Prozessor 
keine implizte Adressrechnung beherrscht, dafür aber implizite 
Inkrementierungen (allerdings vorher), und wenn der Compiler so blöd 
wäre, den C Code ohne nachzudenken 1:1 umzusetzen.

Ausserdem muss man den Faktor auf die Iteration beziehen und dann ist es 
selbst mit 1:1 Umsetzung des C Codes in Assembler-Code kein Faktor 3 
mehr.

Hatten wir ausserdem nicht längst geklärt, dass es keinen direkten und 
verlässlichen Bezug mehr zwischen dem gibt, was du in C hinschreibst, 
und dem, was der Compiler erzeugt?

Ich bin aber auch der Ansicht, dass man per Default locker die 
Index-Variante verwenden kann, weil Compiler darauf getrimmt sind, 
solche Klassiker zu erkennen und passend zu den Fähigkeiten der 
Zielmaschine zu optimieren. Wer wirklich optimalen Code will muss 
ohnehin in jedem Einzelfall auf den erzeugten Code hin kontrollieren und 
weiter optimieren, weil sich nicht alle Möglichkeiten dem Compiler 
erschliessen.

> Manche sind einfach nur Thread- resp. Beratungsresistent.

Du wirst immer Compiler und Plattformen finden, in denen irgendwelche 
solchen Spitzfindigkeiten mal in der einen und mal in der anderen 
Variante besser sind. Aber ich halte es i.d.R. für völlig sinnlos, in 
normalen Programmen auf normalen Plattformen dem hier gezeigten 
Kleinkram hinterher zu rennen. Wenn Grenzoptimierung gefragt ist, dann 
ist oft genug weder die eine noch die andere Variante optimal.

Daher kann ich mich nur deiner ad hominem Argumentation anschliessen. 
Die Resistenz findet sich auch dort, wo du sie nicht vermutetest.

: Bearbeitet durch User
von Ahab (Gast)


Lesenswert?

Peter Dannegger schrieb:
> Was ich mich aber frage, was hat das ganze mit C++ am Hut?
> Pointer sind doch schnödes plain C.

Weil C++ da mit "std::copy" & co ein schönes Framework für Optimierungen 
bietet.


Du schreibst im Applikations-Code immer denselben "std::copy"-Call.

Wenn auf meiner Ziel-Plattform z.B. das Kopieren von "doubles" über die 
FPU besonders schnell ist, dann schreibt man(*) einmal eine 
spezialisierte Copy-Variante für doubles und schon wird überall der 
neue, verbesserte Code eingebunden.

Bei Plain-C muss man per Suchen&Ersetzen alle memcpy finden und 
herausfinden, welche davon "doubles" kopieren wollen...

*) man == jemand der sich mit C++ und der Zielplattform besser auskennt 
als ich :)


Wobei das Optimieren von Kopier-Operationen eh seltenst wirklich nötig 
ist...

Bei mir das letzte mal auf einem '51er Klon, der einen speziellen 
Selbst-Inkrementierenden Autoptr für schnelle XMEM-Kopieraktionen hatte.

von jemand (Gast)


Lesenswert?

Was diskutiert ihr hier eigentlich rum? Es ist weder der Compiler noch 
der Prozessor bzw. µC bekannt. Damit sind alle Diskusionen nutzlos.

von Hardware Kenner (Gast)


Angehängte Dateien:

Lesenswert?

A. K. schrieb:
> Ich bin aber auch der Ansicht, dass man per Default locker die
> Index-Variante verwenden kann, weil Compiler darauf getrimmt sind,
> solche Klassiker zu erkennen und passend zu den Fähigkeiten der
> Zielmaschine zu optimieren.

Eben nicht, auch heutige C-Compiler sind recht eigenwillig was die 
Erkennung von "Code der anders aussieht, aber dasselbe tut". Im Anhang 
ein Benchmark vom "Speicherbereich kopieren" mit 5 verschiedenen 
Algorithmen in C beschrieben (darunter die pointer und die 
index-Variante) für 4 verschiedenene Compiler ausgemessen. Dargestellt 
wird die Transferrate abhängig von der Blockgröße.

Eigentlich müssten alle 5 Kurven übereinander liegen - dem ist aber 
nicht so. Besonders der GCC scheint seinen Benutzer mit stark 
differierenten Code überraschen zu müssen. Vielleicht will er auch nur 
davon ablenken das der Intel-Compiler zuweilen doppelt so schnelle 
Compilate erzeugt.

Details dort:
http://nadeausoftware.com/articles/2012/05/c_c_tip_how_copy_memory_quickly


Da wundert es nicht das Gott Linus persönlich vor ein paar Wochen den 
gcc als völligen Mist bezeichnete "Because it damn well is
 some seriously crazy shit"): 
http://www.heise.de/developer/meldung/Linus-Torvalds-wettert-gegen-Compiler-Collection-GCC-4-9-2268920.html

MfG,

PS.:
Für jeden der sich etwas in Compilerbau auskennt sei der 
Orginalkommentar empfohlen: 
http://lkml.iu.edu/hypermail/linux/kernel/1407.3/00650.html

Hinweis:
"gcc is stupid in spilling a constant" meint das der Compiler Konstanten 
auf den stack (in den Speicher) kopiert (statt sie als immediate Operand 
beim filling zu verwenden)

von Peter D. (peda)


Lesenswert?

Hardware Kenner schrieb:
> Besonders der GCC scheint seinen Benutzer mit stark
> differierenten Code überraschen zu müssen.

Der GCC formt gleich funktionierenden Code immer in die gleiche Form um.
Daß im Quellcode ein Index verwendet wird, heißt für ihn noch lange 
nicht, daß er ihn auch verwenden muß.
Daher erzeugt er für verschiedene Schreibweisen den gleichen Assembler.
Hier mal für die beiden obigen Routinen (AVR-GCC):
1
void copy_i(int* src, int* dst, int elements)
2
{
3
  c0:  dc 01         movw  r26, r24
4
  c2:  fb 01         movw  r30, r22
5
  c4:  20 e0         ldi  r18, 0x00  ; 0
6
  c6:  30 e0         ldi  r19, 0x00  ; 0
7
  c8:  06 c0         rjmp  .+12       ; 0xd6 <copy_i+0x16>
8
  for(int i = 0; i < elements; i++)
9
    dst[i] = src[i];
10
  ca:  8d 91         ld  r24, X+
11
  cc:  9d 91         ld  r25, X+
12
  ce:  81 93         st  Z+, r24
13
  d0:  91 93         st  Z+, r25
14
#include <util/delay.h>
15
#include <stdlib.h>
16
17
void copy_i(int* src, int* dst, int elements)
18
{
19
  for(int i = 0; i < elements; i++)
20
  d2:  2f 5f         subi  r18, 0xFF  ; 255
21
  d4:  3f 4f         sbci  r19, 0xFF  ; 255
22
  d6:  24 17         cp  r18, r20
23
  d8:  35 07         cpc  r19, r21
24
  da:  bc f3         brlt  .-18       ; 0xca <copy_i+0xa>
25
    dst[i] = src[i];
26
}
27
  dc:  08 95         ret
Und nun für den "völlig" anderen Code:
1
void copy_p(int* src, int* dst, int elements)
2
{
3
  de:  dc 01         movw  r26, r24
4
  e0:  fb 01         movw  r30, r22
5
  e2:  20 e0         ldi  r18, 0x00  ; 0
6
  e4:  30 e0         ldi  r19, 0x00  ; 0
7
  e6:  06 c0         rjmp  .+12       ; 0xf4 <copy_p+0x16>
8
  for(int i = 0; i < elements; i++)
9
    *dst++ = *src++;
10
  e8:  8d 91         ld  r24, X+
11
  ea:  9d 91         ld  r25, X+
12
  ec:  81 93         st  Z+, r24
13
  ee:  91 93         st  Z+, r25
14
    dst[i] = src[i];
15
}
16
17
void copy_p(int* src, int* dst, int elements)
18
{
19
  for(int i = 0; i < elements; i++)
20
  f0:  2f 5f         subi  r18, 0xFF  ; 255
21
  f2:  3f 4f         sbci  r19, 0xFF  ; 255
22
  f4:  24 17         cp  r18, r20
23
  f6:  35 07         cpc  r19, r21
24
  f8:  bc f3         brlt  .-18       ; 0xe8 <copy_p+0xa>
25
    *dst++ = *src++;
26
}
27
  fa:  08 95         ret

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Irgendwer schrieb:
> Johann L. schrieb:
>> In der Newlib für ARM...
>
> A. K. schrieb:
>> Wenn dann Beispiele aus der AVR Welt...
>
> Wer lesen kann:-)

Für avr-gcc gilt ähnliches.  Zwar nicht für die Newlib, aber für die 
AVR-LibC, die wohl von mindestens 99% der Anwender eingesetzt wird.

AVR-LibC enthält Assembler-Optimierte Algorithmen.  Nicht nur für 
Bytegeschiebe sondern z.B. auch für float-Arithmetik oder int <-> String 
Konvertierungen etc etc.

"Optimalität" bei AVR-Anwendungen ist i.d.R. Codegröße; da einen 4x 
größeren Code zu haben und ein, zwei grottige Ticks einzusparen ist da 
nicht angezeigt.

> Wenn man sich in der x86 Welt mal ansieht welche Unterschiede zwischen
> dem gcc und dem icc möglich sind. Da könnte einem schon der Verdacht
> kommen das beim Thema Optimierung zumindest beim gcc noch einiges an
> Luft für Verbesserungen vorhanden ist.

Besser geht immer.  Bei manchen Optimierungen steht sich GCC selbst im 
Weg... Und ICC hat den Vorteil, daß er nur für eine einzige 
Architektur(familie) zu erzeugen brauch.  Und selbst da liegt 
Intel-harware vorne und nicht-Intel hinken in Codegüte hinterher.

: Bearbeitet durch User
von cpp noob (Gast)


Lesenswert?

Wenn wir schon von C++ reden dann stelle ich mal folgendes in den Raum:
1
std::copy(std::begin(src), std::end(src), std::begin(dest));

Sollte eher C++ entsprechen als die C Variante des besagten C++-Gurus!

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

uwe schrieb:
> Ich würds mit dem DMA Controller machen =;)

Ende der 80er habe ich auf 3B-Unix-Rechnern von AT&T gearbeitet. Die 
3B-Prozessoren waren auf die Sprache C optimiert. Sie kannten z.B. 
memcpy() und auch strcpy() als Opcode. Besser gehts nicht :-)

von Bronco (Gast)


Lesenswert?

uwe schrieb:
> Ich würds mit dem DMA Controller machen =;)

Oder mit dem Blitter... in den 80ern auf dem Amiga...

von remy (Gast)


Lesenswert?

Kaj schrieb:
> Der gute Mann hat erstmal einen auf ganz dicke Hose gemacht, und was
> fuer ein toller super Typ er doch sei, er sei ja seit Jahren
> C++-Entwickler fuer Serverprogramme und hoch parallelisierte Prozesse
> bla bla.

Will nur kurz noch auf das eingehen: Die Kopierfunktion von oben schreit 
nach einem Buffer-Overflow und ist auf jeden Fall ein 
Sicherheitsrisiko... so ein Code hat in sicherheitsrelevanten Bereichen 
wie z.B. Serversoftware nichts zu suchen.

von Dr. Sommer (Gast)


Lesenswert?

remy schrieb:
> Die Kopierfunktion von oben schreit
> nach einem Buffer-Overflow
wie das? Die Anzahl der kopierten Elemente ist ja durch "elements" 
begrenzt.

remy schrieb:
> so ein Code hat in sicherheitsrelevanten Bereichen
> wie z.B. Serversoftware nichts zu suchen.
Naja, sowas wie ein E-Mail oder HTTP-Server ist nach üblichem 
Verständnis nicht sicherheitsrelevant. Darunter fallen eher Dinge wie 
ESP-Steuerung im Auto...
Und wie stellst du dir eine "sichere" Kopier-Operation vor? Das normale 
memcpy oder der Kopier-Operator von std::vector hat doch exakt das 
gleiche "Problem".

von remy (Gast)


Lesenswert?

Dr. Sommer schrieb:
> wie das? Die Anzahl der kopierten Elemente ist ja durch "elements"
> begrenzt.

Jein. Das wäre nur eine geeignete Sicherheitsstrategie mit einem 
entsprechendem Reviewprozess und statischer Codeanalyse. Und selbst dann 
kann man nicht vollständig ausschließen, dass auch mal ein falsches 
"elements" übergeben wird.
Und zusätzlich sollte man "-fstack-protector" als Compilerflag setzen. 
Viele Fehler kann man auch mit valgrind entdecken.

Dr. Sommer schrieb:
> Naja, sowas wie ein E-Mail oder HTTP-Server ist nach üblichem
> Verständnis nicht sicherheitsrelevant. Darunter fallen eher Dinge wie
> ESP-Steuerung im Auto...
> Und wie stellst du dir eine "sichere" Kopier-Operation vor? Das normale
> memcpy oder der Kopier-Operator von std::vector hat doch exakt das
> gleiche "Problem".

Naja, wenn ich dank eines Buffer Overflow Zugriff auf einen Server 
deiner Firma habe und dort interne Daten absaugen kann, ist das durchaus 
sicherheitsrelevant...

Wenn man zwei std:vector kopiert, ist das, aufgrund der Codebase der STL 
"secure". Ganz im Gegensatz dazu, wenn man seine eigene Kopierfunktion 
verwendet (da sind wir wieder wie oben angesprochen, bei einem 
Reviewprozess).

von Dr. Sommer (Gast)


Lesenswert?

remy schrieb:
> Wenn man zwei std:vector kopiert, ist das, aufgrund der Codebase der STL
> "secure".
Und wer reviewt die?

remy schrieb:
> Ganz im Gegensatz dazu, wenn man seine eigene Kopierfunktion verwendet
Wenn du so eine triviale Funktion schon als review-pflichtig ansiehst, 
ist das garantiert der gesamte Code des Programms. Also kein besonderer 
Nachteil dieser Funktion.
Und wie man eine nicht-review-pflichtige "sichere" Kopierfunktion 
implementiert hast du immer noch nicht verraten.

PS: in richtig sicherheitskritischen Bereichen (wie Automotive 
Steuerungen) wird natürlich alles reviewed. Aber auch da ist memcpy (mit 
Längen-Argument) erlaubt.

von Tom (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Und wer reviewt die?
Und wer reviewt printf? Oder malloc?

von Dr. Sommer (Gast)


Lesenswert?

Tom schrieb:
> Und wer reviewt printf? Oder malloc?
Der Anbieter der zertifizierten C Library. Da diese Funktionen aber 
offenbar review-bedürftig (von wem auch immer) sind, sind sie auch nicht 
besser als die Funktion vom OP. Damit ist immer noch nicht klar was sich 
remy unter einer sicheren memcpy (artigen) Funktion vorstellt, die auch 
bei falschem Längenargument noch sicher ist...

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Dr. Sommer schrieb:
> Damit ist immer noch nicht klar was sich
> remy unter einer sicheren memcpy (artigen) Funktion vorstellt, die auch
> bei falschem Längenargument noch sicher ist...

void * memcpy (void * dest, const void * src, size_t n)
{
    return (NULL);
}

von Erwin M. (nobodyy)


Lesenswert?

micha schrieb:
> Kaj schrieb:
>> Compilerflags: -O0 -g3 -std=gnu99 -Wall -Wextra
>
> Schalt mal die Optimierung ein und vergleich den Code.

Und teste mal mit

-Ofast -march=native -flto -funroll-loops -Wall -fbounds-check

und mal mit

-Os -march=native -Wall -fbounds-check

Wie schon erwähnt wurde nimmt man zum Kopieren vom Speicherbereichen 
üblicherweise memcpy und keine zusammengefrickelten Schleifen, denn in 
der Realität spielt auch die Wartbarkeit und Portabilität eine Rolle - 
auch das betrifft die Effizienz.

Zudem ist der Weg an einer Stelle etwas rumzubasteln nicht selten 
ineffektiv, denn man kann sowas auch parallelisieren mit mehreren 
CPUs/GPUs oder einem Cluster/einer Cloud.
Das int elements kann ja auf einer 64-Bit-Plattform ziemlich groß sein 
und ohne ein paar Tausend/Millionen Kernen kann es dann ziemlich lange 
dauern.

Also kurz gesagt ist die richtige Antwort auf die Frage "kommt drauf 
an", denn unterschiedliche Lösungen skalieren unterschiedlich.
Daher ist die Frage von dem Guru so sinnvoll wie "ist es nachts kälter 
als draußen?".

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.