Forum: PC-Programmierung Externe Funktion inlinen


von Sascha H. (Firma: --) (freeze2046)


Lesenswert?

Hallo zusammen,

ich habe folgendes Problem:

Ich habe mehrere Assembler-Funktionen die ich bereits zu einem 
COFF-Obj-File kompiliert habe.
Nun würde ich gerne diese Funktionen in einem C/C++ Projekt benutzen 
(Visual Studio 2008 TS).

An und für sich kein Problem. Ich habe die Funktionen dort als extern 
"C" deklariert und kann sie dort auch verwenden.

Da diese Funktionen jedoch weit über 2 Millionen mal am Stück aufgerufen 
werden und es sich hier um einen zeitkritischen Prozess handelt, möchte 
ich die ständigen calls vermeiden und die Funktion lieber inlinen.

Da erst dem Linker die Funktion bekannt gemacht wird, hat der Compiler 
keine Chance dazu. Was kann ich tun?


Eine Möglichkeit wäre sicherlich inline assembler zu verwenden. Dies 
mache ich mit dem VC jedoch sehr ungern, da ich viele Redundanzen 
herausnehmen müsste (doppeltes Sichern von Registern).

Zudem stellt sich mir die Frage ob der Compiler an meinem Code 
rummurkst?!

Uber vorweihnachtliche Hilfe wäre ich sehr dankbar ;-)

von Peter (Gast)


Lesenswert?

dir Frage ist ob es sinn macht, wenn du eine Funktion schon in Assembler 
schreibst dann vermutlich aus dem Grund damit sie sehr schnell ist. Ich 
vermute es ist auch nicht bloss ein 3 Zeiler. Die Frage ist also ob 
wirklich der eine Call etwas an der gesamtlaufzeit ändert. Klar sind es 
dann millionen calls weniger aber ob es nun 10 Stunden oder 10Stunden 
und 5minuten dauert ist doch egal.

von Sascha H. (Firma: --) (freeze2046)


Lesenswert?

Meine Messungen (mit anderen Bedinungen) haben ergeben, dass es in etwa 
10 Sekunden ausmacht.

Dass ist in meinem Fall zu viel. Ich habe für meine Funktion eine 
Zeitspanne von etwa. 5 bis max. 10 Sekunden Zeit. Die erreiche ich aber 
nicht.

Kann mir jemand sagen, ob es denn prinzipiell überhaupt möglich ist den 
Code aus einer externen Objektdatei zu inlinen?

von (prx) A. K. (prx)


Lesenswert?

Sascha H. schrieb:

> ob es denn prinzipiell überhaupt möglich ist den
> Code aus einer externen Objektdatei zu inlinen?

Nein.

von Sascha H. (Firma: --) (freeze2046)


Lesenswert?

A. K. schrieb:
> Nein.

Angeblich schon:
http://msdn.microsoft.com/en-us/library/e7k32f4k%28VS.80%29.aspx

Ich habe nur noch nicht ganz verstanden, wie genau dass genutzt werden 
muss.

von Peter (Gast)


Lesenswert?

aus wenn besteht denn die funktion wenn sie so schnell ist? Bist du 
sicher das es mit C ohne asm nicht genauso schnell geht.

von (prx) A. K. (prx)


Lesenswert?

Wo steht dort, dass dies ginge? Voraussetzung für Inlining ist stets, 
dass der Compiler den zu inlinenden Code kennt. Hier ist aber 
vorgegeben, dass er den Code nicht kennt.

PGO sagt dem Compiler nur, wo sich Inling laufzeitmässig lohnt und wo 
nicht.

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:
> Wo steht dort, dass dies ginge?

Ich lese dort allerdings auch, dass der Linker das kann.

So wie ich das verstanden habe, wird aus den Profile-Läufen die 
Information gewonnen, ob sich inlinen für Funktionen lohnen würde und 
der Linker baut das dann entsprechend um.

> PGO sagt dem Compiler nur, wo sich Inling laufzeitmässig lohnt und wo
> nicht.

Nicht ganz.
Am Anfang des Artikels ist eine Übersicht über die durchzuführenden 
Schritte. Nach dem Schritt der Generierung der PGO Info kommt nur noch 
ein Linkerschritt mit dem Optimize Schalter.

Allerdings werde ich aus der Beschreibung des Linker-Schalters auch 
nicht richtig schlau. An einer Stelle steht, dass der Linker dann den 
Compiler benutzt um globale Optimierungen durchzuführen. Beim konkreten 
OPTIMZE Fall steht allerdings nichts davon. Da liest es sich wieder so, 
als ob der Linker das ganz alleine machen könnte. Hmmm


Ich würde allerings auch erst mal die Frage stellen, ob es sich wirklich 
lohnt von C auf Assembler umzusteigen oder ob da nicht auf C-Ebene noch 
was geht.

von Sascha H. (Firma: --) (freeze2046)


Lesenswert?

@Karl heinz Buchegger

Exakt, so habe ich das auch verstanden. Ich bin gerade dabei zu testen, 
ob es funktioniert. Prinzipiell wird offensichtlich, dass durch den 
Testlauf gewonnene *.pgc File dazu genutzt den Code zu optimieren.

von (prx) A. K. (prx)


Lesenswert?

Karl heinz Buchegger schrieb:

> Ich lese dort allerdings auch, dass der Linker das kann.

Sorry, ich bin grad blind und seh's immer noch nicht.

von (prx) A. K. (prx)


Lesenswert?

Sascha H. schrieb:

> Exakt, so habe ich das auch verstanden. Ich bin gerade dabei zu testen,
> ob es funktioniert. Prinzipiell wird offensichtlich, dass durch den
> Testlauf gewonnene *.pgc File dazu genutzt den Code zu optimieren.

Klar. Nur ist das nicht wirklich der Linker, bzw. nicht nur der, auf den 
es dabei ankommt. Zu dieser Form der Optimierung gehört beispielsweise 
auch die Entscheidung, welcher Zweig eines if-else Statements wichtiger 
ist. Der andere wird dann aus dem Weg geräumt um teure ausgeführte 
Sprungbefehle zu vermeiden. Das wird kaum der Linker machen. Wenn doch, 
dann allenfalls weil der Objektcode nur Pseudocode enthält und der 
Linker den eigentlichen Codegenerator.

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:
> Karl heinz Buchegger schrieb:
>
>> Ich lese dort allerdings auch, dass der Linker das kann.
>
> Sorry, ich bin grad blind und seh's immer noch nicht.

Wie gsagt: So sicher bin ich mir auch nicht.
Aber hier die durchzuführenden Schritte

* Compile one or more source code files with /GL.

  logisch. Das Object File muss attributiert werden.

* Link with /LTCG:PGINSTRUMENT.

  auch klar. Da muss noch eine Lib dazu, die die Infos sammelt
  und abspeichert

* Profile the application.

  Auch klar. Jetzt werden Profile Daten generiert

* Link with /LTGC:PGOPTIMIZE.

  Und jetzt werden die Profile Daten eingearbeitet.
  Interessant: Kein Recompile notwendig. Offenbar entfernt dieser
  Schritt auch die Profile-Attributierung

Und das wars.

Allerdings ist beim ersten Schritt ein Zusatz:
However, only those modules compiled with /GL will be instrumented and 
later available for profile-guided optimizations.

Hmm. Wie das bei Assemblermodulen geht .... keine Ahnung. Aber das 
müsste man rauskriegen können.
Auch interessant: /LTCG steht für Link Time Code Generation
Auf der anderen Seite: Namen sind Schall und Rauch :-)

von (prx) A. K. (prx)


Lesenswert?

Karl heinz Buchegger schrieb:

> * Link with /LTGC:PGOPTIMIZE.
>
>   Und jetzt werden die Profile Daten eingearbeitet.
>   Interessant: Kein Recompile notwendig. Offenbar entfernt dieser
>   Schritt auch die Profile-Attributierung

Jo, aber genau da liegt der Hase im Pfeffer. Erklär mit bitte mal, wie 
ohne Recompile eine "Conditional Branch Optimization" möglich sein soll.

Bloss weil du nur den Linker aufrufst, heisst das ja noch lange nicht, 
dass dabei nur das läuft, was man traditionell unter einem Linker 
versteht.

von Sascha H. (Firma: --) (freeze2046)


Lesenswert?

Karl heinz Buchegger schrieb:
> Hmm. Wie das bei Assemblermodulen geht .... keine Ahnung. Aber das
> müsste man rauskriegen können.
> Auch interessant: /LTCG steht für Link Time Code Generation
> Auf der anderen Seite: Namen sind Schall und Rauch :-)

Ja, darüber bin ich auch gestolpert. Hört sich ja so an, als könnten 
dann nur solche Module optimiert werden.
Zur Not benutze ich MASM. Da wird evtl. eine solche Möglichkeit 
bestehen.


A. K. schrieb:
> Bloss weil du nur den Linker aufrufst, heisst das ja noch lange nicht,
> dass dabei nur das läuft, was man traditionell unter einem Linker
> versteht.

Behauptet ja niemand, dass das NUR der Linker macht. Und um ehrlich zu 
sein ist mir dass auch erst mal egal, so lange es funktioniert.
Ich gehe sowieso davon aus, dass der VS-Linker mehr macht als sein Name 
sagt.

von (prx) A. K. (prx)


Lesenswert?

Aber dann kommt's sehr drauf an, was aus dem Compiler im LTCG-Modus 
rauskommt und ob sich das mit extern zugefüttertem Assembler-Code 
verträgt.

Meine unmassgebliche Vermutung ist, dass der Compiler dabei halbgaren 
Zwischencode auswirft. Vom Assembler her steht aber nur der Binärcode 
(plus relocations) zur Verfügung. Ob das wohl zusammengeht? Denn 
eigentlich müsste dieser Linktime-Codegenerator den Fall eigens vorsehen 
und den Binärcode analysieren und tracen, um festzustellen welche 
Register der verwendet und wo der wirklich aufhört. Ist ja nicht in 
Stein gemeisselt, dass Assembler-Code immer mit dem letzten Byte Code 
aufhört.

Meine Prognose: Externer Binärcode bleibt externer Binärcode. Mit oder 
ohne LTCG.

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:
> Karl heinz Buchegger schrieb:
>
>> * Link with /LTGC:PGOPTIMIZE.
>>
>>   Und jetzt werden die Profile Daten eingearbeitet.
>>   Interessant: Kein Recompile notwendig. Offenbar entfernt dieser
>>   Schritt auch die Profile-Attributierung
>
> Jo, aber genau da liegt der Hase im Pfeffer. Erklär mit bitte mal, wie
> ohne Recompile eine "Conditional Branch Optimization" möglich sein soll.

Ich hab keine Ahnung :-)

> Bloss weil du nur den Linker aufrufst, heisst das ja noch lange nicht,
> dass dabei nur das läuft, was man traditionell unter einem Linker
> versteht.

Ich geh mal stark davon aus, das das was MS da hat, mit einem 
traditionellen Linker nicht mehr viel zu tun hat.
So wie MS bei vielen Dingen ihr eigenes Süppchen kocht (oder klaut)

von Karl H. (kbuchegg)


Lesenswert?

A. K. schrieb:

> Meine unmassgebliche Vermutung ist, dass der Compiler dabei halbgaren
> Zwischencode auswirft. Vom Assembler her steht aber nur der Binärcode
> (plus relocations) zur Verfügung. Ob das wohl zusammengeht?

Mir gehts wie dir: Ich kanns mir nicht recht vorstellen.

Aber wie heißt es so schön:
Versuch macht kluch.

von Sascha H. (Firma: --) (freeze2046)


Lesenswert?

Erstes Testergebnis:

Es wird versucht folgende Funktion in einem C-Projekt zu inlinen:
1
TITLE PGOTest.asm
2
3
.686P
4
.XMM
5
.MODEL FLAT
6
7
8
PUBLIC _MakeSth
9
EXTRN  __imp__printf:PROC
10
11
12
.CODE
13
14
CONST SEGMENT
15
  FORMAT DB '%d %d', 13, 10, 0
16
CONST ENDS
17
18
_TEXT    SEGMENT
19
  _i1$  = 8
20
  _i2$  = 12
21
_MakeSth PROC  
22
  push ebp
23
  mov  ebp, esp
24
  
25
  mov  eax, DWORD PTR _i2$[ebp]
26
  mov  edx, DWORD PTR _i1$[ebp]
27
  
28
  push eax
29
  push edx
30
  push OFFSET FORMAT
31
  call DWORD PTR __imp__printf
32
  add  esp, 12
33
    
34
  pop  ebp
35
  ret  
36
_MakeSth ENDP
37
_TEXT    ENDS 
38
39
END

Das ganze sieht in meinem Main-C-Projekt dann so aus:
1
#include <stdio.h>
2
3
4
extern void MakeSth(int i1, int i2);
5
6
7
int main()
8
{
9
  //MakeSth(77, 999);
10
11
  unsigned i = 0, j = (unsigned)(1 << 8);
12
  for( ; j; j--, i++ ) {
13
    MakeSth(i, j);
14
  }
15
16
  return 0;
17
}


Nach der PG-Optimierung habe ich folgendes Assembly-Listing erhalten:
1
; Listing generated by Microsoft (R) Optimizing Compiler Version 15.00.30729.01 
2
3
  TITLE  main.cpp
4
  .686P
5
  .XMM
6
  include listing.inc
7
  .model  flat
8
9
INCLUDELIB OLDNAMES
10
11
EXTRN  @__security_check_cookie@4:PROC
12
EXTRN  _MakeSth:PROC
13
PUBLIC  _main
14
; Function compile flags: /Ogtpy
15
;  COMDAT _main
16
_TEXT  SEGMENT
17
_main  PROC            ; COMDAT
18
; 2056 dynamic instrs
19
; entered 1 times
20
; Line 9
21
  push  esi
22
  push  edi
23
; Line 12
24
  xor  edi, edi
25
  mov  esi, 256        ; 00000100H
26
  npad  7
27
$LL3@main:
28
; Line 14
29
  push  esi
30
  push  edi
31
  call  _MakeSth
32
  dec  esi
33
  add  esp, 8
34
  inc  edi
35
  test  esi, esi
36
  jne  SHORT $LL3@main
37
;    taken 255(99%), not-taken 1(0%)
38
  pop  edi
39
; Line 17
40
  xor  eax, eax
41
  pop  esi
42
; Line 18
43
  ret  0
44
_main  ENDP
45
_TEXT  ENDS
46
END

Wie man sieht hat er hier nur "Optimierungen" hinsichtlich 
Sprunganweisungen getroffen und eine Analyse als Kommentar hinzugefügt. 
Der Grund für den Call von MakeSth kann natürlich auch die Tatsache 
sein, dass es nicht mit dem Schalter /GL kompiliert wurde, da ja durch 
ml.exe (MASM) kompiliert wurde.

von Peter (Gast)


Lesenswert?

sehe ich das richtig das du dir wegen dem call gedanken machst aber 
selber in der Funktion ein printf aufrufst? Printf macht doch bestimmt 
99.9% der Rechenzeit aus.

von Sascha H. (Firma: --) (freeze2046)


Lesenswert?

Peter schrieb:
> sehe ich das richtig das du dir wegen dem call gedanken machst aber
> selber in der Funktion ein printf aufrufst? Printf macht doch bestimmt
> 99.9% der Rechenzeit aus.

Glaubst du ehrlich, dass DAS meine ach so performante Funktion ist, die 
ich unbedingt inlinen will?

Da kann ich mir ein schmunzeln nicht verkneifen ;-).
Ich hab einfach mal ein paar Zeilen dahin geklatscht, um euch was zeigen 
zu können bzw. das Ganze mal zu testen.

Und ob der das inlined oder nicht, ist denke ich mal unabhängig davon, 
ob ich darin etwas aufrufe.

Meinen original Code kann ich nicht posten, da 1. zu viel und 2. 
Firmeneigentum.

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.