Performance-Monitoring des eigenen Codes (z.B.für Optimierun

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
Helle
Beiträge: 566
Registriert: 11.11.2004 16:13
Wohnort: Magdeburg

Performance-Monitoring des eigenen Codes (z.B.für Optimierun

Beitrag von Helle »

Moderne Intel-CPU´s bieten die Möglichkeit eines Performance-Monitorings über die MSR´s (Model-Specific-Registers) der CPU.
Dies kann prima auch zur (Geschwindigkeits-)Optimierung des eigenen Codes verwendet werden. Anstelle der doch von vielen Faktoren abhängigen Zeit-Messung kann die Anzahl benötigter CPU-Takte, CPU-Instruktionen und Sprünge (sind immer eine Bremse!) zum Vergleich verschiedener Code-Versionen verwendet werden. Und interessant sind diese Werte allemal!
Für die MSR-Nutzung sind allerdings unter Windows Ring0-Zugriffs-Rechte notwendig (Administrator-Rechte sowieso). Ich verwende dafür die WinRing0-Dll´s (32/64-Bit, http://sourceforge.net/projects/winring0), die Rings "ausgegraben" und auch für Vista64 getestet hat (an dieser Stelle Dank an Rings!).
Hinweise dazu siehe auch http://www.purebasic.fr/german/viewtopi ... 17&t=20728 (Hardware-Ecke). Benutzung auf eigene Gefahr!

Code: Alles auswählen

;- Ermittlung Anzahl der benötigten CPU-Takte, Instruktionen und Sprünge für einen Testcode, ab Intel Core 2
;- "Helle" Klaus Helbing, 22.05.2011
;- Nutzung der WinRing0-Dll´s (32/64-Bit). Copyright (c) 2007-2009 OpenLibSys.org. All rights reserved.

#IA32_PERFEVTSEL0 = $186                    ;Unique, d.h. bei Multi-Cores jeder Core eigenes MSR
#IA32_PERF_GLOBAL_CTRL = $38F               ;Unique
#IA32_PMC_0 = $C1                           ;Unique
#Bit0 = $1
#Bit16 = $10000                             ;User Mode = Bit16
#Bit17 = $20000                             ;Operating System Mode = Bit17
#Bit22 = $400000                            ;Enable Counters = Bit22
#UnHalted_Core_Cycles = $3C                 ;Event Select Zählung Core-Cycles
#Instructions_Retired = $C0                 ;Event Select Zählung Instructions
#Branch_Instructions_Retired = $C4          ;Event Select Zählung ausgeführte Sprünge
#PROCESSOR_ARCHITECTURE_AMD64 = $9
#IA32_TIME_STAMP_COUNTER = $10              ;Unique

Core.l
Hi.l
Lo.l
Kali_Hi.l
Kali_Lo.l
Mess_Hi.l
Mess_Lo.l
TMask.l
Cycles.q
Instructions.q
Jumps.q

;mittels CPUID ermitteln, ob die CPU Performance Monitoring beherrscht 
!mov eax,0h                  ;Ermittlung Largest Standard Function Number
!cpuid
!cmp eax,0Ah
!jae @f
MessageRequester("Abbruch !", "Performance Monitoring wird von dieser CPU nicht unterstützt !")
End
!@@:
!mov eax,0Ah                 ;Ermittlung Performance Monitor Features
!cpuid
!and ebx,100011b             ;Bit0=Core Cycles, Bit1=Instructions, Bit5=Branch Instructions
!jz @f
MessageRequester("Abbruch !", "Es werden nicht alle benötigten Funktionen unterstützt !")
End
!@@:

Procedure InstructionsTest()                ;Testcode, hier sehr einfach
  For i = 1 To 10000
    j + 1
  Next
EndProcedure

;ermitteln, ob 32- oder 64-Bit-Betriebssystem 
SI.SYSTEM_INFO                              ;Structure System_Info
GetSystemInfo_(@SI)
If SI\wProcessorArchitecture = #PROCESSOR_ARCHITECTURE_AMD64 
  OS3264 = 1 
EndIf 
;die entsprechende DLL öffnen
If OS3264 
  DLLOK = OpenLibrary(0, "WinRing0x64.dll") ;64-Bit-Version laden
 Else
  DLLOK = OpenLibrary(0, "WinRing0.dll")    ;32-Bit-Version laden
EndIf 

If DLLOK
  Prototype.i ProtoWinRing0_0()
  WR0_InitializeOls.ProtoWinRing0_0   = GetFunction(0, "InitializeOls")
  WR0_DeinitializeOls.ProtoWinRing0_0 = GetFunction(0, "DeinitializeOls")
  WR0_IsMsr.ProtoWinRing0_0           = GetFunction(0, "IsMsr")

  Prototype.i ProtoWinRing0_4(V1l.l, V2l.l, V3l.l, V4l.l)
  WR0_RdmsrTx.ProtoWinRing0_4 = GetFunction(0, "RdmsrTx")  
  WR0_WrmsrTx.ProtoWinRing0_4 = GetFunction(0, "WrmsrTx") 
  
  If WR0_InitializeOls()
    If WR0_IsMsr()
      Core = 1
      hThread = GetCurrentThread_()
      TMask = SetThreadAffinityMask_(hThread, Core) ;Thread nur von Core0 ausführen lassen (wenn Multi-Core-CPU)
      Old_Priority = GetThreadPriority_(hThread)
      SetThreadPriority_(hThread, #THREAD_PRIORITY_TIME_CRITICAL)

      WR0_RdmsrTx(#IA32_PERF_GLOBAL_CTRL, @Lo, @Hi, Core)
      Hi_Old = Hi                           ;sichern
      Lo_Old = Lo
      Lo = Lo | #Bit0                       ;Counter0 aktivieren
      WR0_WrmsrTx(#IA32_PERF_GLOBAL_CTRL, Lo, Hi, Core)

      UMode = #UnHalted_Core_Cycles | #Bit16 | #Bit22  ;nur User Mode, hier für 1.Wert
     ;AMode = #UnHalted_Core_Cycles | #Bit16 | #Bit17 | #Bit22  ;All (User Mode und Operating System Mode)

      For k = 1 To 3                        ;3 Werte ermitteln
;Kalibrierung, d.h. Ausführung ohne Testcode      
        WR0_WrmsrTx(#IA32_PERFEVTSEL0, UMode, 0, Core)    ;oder AMode für Tests
        WR0_WrmsrTx(#IA32_PMC_0, 0, 0, Core)    ;auf Null setzen     
      
        WR0_RdmsrTx(#IA32_PMC_0, @Kali_Lo, @Kali_Hi, Core)    ;Core0 auslesen
        WR0_WrmsrTx(#IA32_PMC_0, 0, 0, Core)    ;wieder auf Null setzen 
;Messung, d.h. Ausführung mit Testcode
        WR0_WrmsrTx(#IA32_PERFEVTSEL0, UMode, 0, Core)
        WR0_WrmsrTx(#IA32_PMC_0, 0, 0, Core)    ;auf Null setzen     

;--------------------------------------
;zu testender Code; sollte für Takte-Ermittlung eine gewisse Mindestlänge haben 
        InstructionsTest()                  ;oder direkt einfügen
;--------------------------------------

;Werte auslesen 
        WR0_RdmsrTx(#IA32_PMC_0, @Mess_Lo, @Mess_Hi, Core)     ;Core0 auslesen
        WR0_WrmsrTx(#IA32_PMC_0, 0, 0, Core)    ;wieder auf Null setzen 
        
        Select k
          Case 1
            Cycles = ((Mess_Hi - Kali_Hi) << 32) + Mess_Lo - Kali_Lo ;kleine Differenzen sind systembedingt
            C$ = "Anzahl der durchgelaufenen Core-Takte : " + StrU(Cycles, #PB_Quad) + #LFCR$         
            UMode = #Instructions_Retired | #Bit16 | #Bit22     ;für nächsten Wert  
          Case 2
            Instructions = ((Mess_Hi - Kali_Hi) << 32) + Mess_Lo - Kali_Lo
            I$ = "Anzahl der ausgeführten CPU-Instruktionen : " + StrU(Instructions, #PB_Quad) + #LFCR$         
            UMode = #Branch_Instructions_Retired | #Bit16 | #Bit22
          Case 3
            Jumps = ((Mess_Hi - Kali_Hi) << 32) + Mess_Lo - Kali_Lo  ;auch Calls und Returns!
            J$ = "Anzahl der ausgeführten Sprünge (incl. Calls und Returns) : " + StrU(Jumps, #PB_Quad) + #LFCR$
        EndSelect
      Next       

      MessageRequester("Auswertung Testcode", C$ + I$ + J$)
    EndIf 
    
    WR0_WrmsrTx(#IA32_PERF_GLOBAL_CTRL, Lo_Old, Hi_Old, TMask)  ;Counter0 wieder auf Ursprungs-Wert setzen   
    WR0_DeinitializeOls()  
    SetThreadPriority_(hThread, Old_Priority)    ;Ordung muss sein!
  EndIf
EndIf      
Gruß
Helle

Edit 5.10.2009: MissesJumps entfernt, da mein Test-I7 dies so nicht mochte. Rest Kosmetik.
Edit 22.05.2011: Core-Zuordnung korrigiert.
Zuletzt geändert von Helle am 22.05.2011 18:58, insgesamt 3-mal geändert.
Benutzeravatar
Thorium
Beiträge: 1722
Registriert: 12.06.2005 11:15
Wohnort: Germany
Kontaktdaten:

Re: Performance-Monitoring des eigenen Codes (z.B.für Optimierun

Beitrag von Thorium »

Sehr genial, werd ich mir auf jeden Fall mal anschauen. Der Zufall will es das ich gerade mit Optimierungsarbeiten beschäftigt bin wo jeder gesparte Cycle in den Schleifen merkbare Performance bringt.
Zu mir kommen behinderte Delphine um mit mir zu schwimmen.

Wir fordern mehr Aufmerksamkeit für umfallende Reissäcke! Bild
Benutzeravatar
Thorium
Beiträge: 1722
Registriert: 12.06.2005 11:15
Wohnort: Germany
Kontaktdaten:

Re: Performance-Monitoring des eigenen Codes (z.B.für Optimierun

Beitrag von Thorium »

Und grad schon was gefunden.

Unter x64 sind die 16bit Register extrem langsam.
Ich habe einen Bytefilter in PB geschrieben, der unter x86 im Benchmark 6,3s benötigt für 1000 Durchläufe. Unter x64 benötigt er 16,4s! :o
Wobei ich nur Integer und Byte Datentypen verwendet habe, kein Word.

Habe den Filter dann in Assembler geschrieben um die Performance zu verbessern: x86: 5,1s x64: 11,2s.
Da bin ich doch etwas stutzig geworden. Der Filter ist eine Kombination aus 2 anderen, welche jeder für sich 2,5s benötigen. (Assemblercode, PB Code benötigt 5,8s)

Dann ist mir aufgefallen das ich r11w nutze. Da 2 Bytes ohne Überlauf miteinander verrechnet werden müssen. Das Entergebniss passt wieder in ein Byte aber für die Berechnung benötige ich ein Word. Habe einfach r11w in r11 geändert und sie da, Dauer von 11,2s auf 7,8s runter.

Allerdings ist das immernoch langsamer als der x86 Code. Wobei ich eigentlich davon ausgegangen bin das er etwas schneller ist, da ich durch die neuen Register beim x64 Code den Stack nicht nutzen muss. Bei x86 fehlen mir Register und ich muss den Stack nutzen.

Nun werd ich mich mal mit deinem Code drannhängen und schauen ob ich noch was finde.
Zu mir kommen behinderte Delphine um mit mir zu schwimmen.

Wir fordern mehr Aufmerksamkeit für umfallende Reissäcke! Bild
Benutzeravatar
dige
Beiträge: 1239
Registriert: 08.09.2004 08:53

Re: Performance-Monitoring des eigenen Codes (z.B.für Optimierun

Beitrag von dige »

WinRing0SampleCpp.exe funzt, aber wenn ich Dein Code
starte bekomme ich nur "wird nicht unterstützt".

Muss man ausser der Dll noch etwas anders installieren?
"Papa, ich laufe schneller - dann ist es nicht so weit."
Benutzeravatar
Helle
Beiträge: 566
Registriert: 11.11.2004 16:13
Wohnort: Magdeburg

Re: Performance-Monitoring des eigenen Codes (z.B.für Optimierun

Beitrag von Helle »

Hallo dige,
das Performance Monitoring funktioniert erst ab Intel Core 2, wenn ich das richtig überblicke.
Deine CPU ist...?
Zur Installation der WinRing0-Treiber kann auch folgendes verwendet werden:

Code: Alles auswählen

;Installation des Treibers von WinRing0(x64), 64/32-Bit 
;Benötigt werden jeweils die DLL- und die SYS-Datei
;Es sind Administrator-Rechte erforderlich
;Diese Datei ist abzuspeichern; im selben Verzeichnis muss auch die SYS-Datei liegen (wegen Kopieren)
;Dann diese Datei ausführen
;Für Windows 7 (=Vista ?):
; 1. Als User "Administrator" anmelden (dieses Konto aktivieren in Computerverwaltung/Lokale Nutzer und Gruppen/Benutzer)
; 2. Diesen Code ausführen
; 3. Administrator-Konto wieder deaktivieren (s.o.)
; 3. Neustart und als üblicher User anmelden
;Zur Nutzung der installierten Treiber ist erforderlich:
; Die jeweilige DLL und SYS müssen im selben Verzeichnis liegen wie die auszuführende PB- oder EXE-Datei
; In den Compiler-Optionen ist "Temporäres Executable im Quellcode-Verzeichnis erstellen" einzuschalten   
;Copyright und Lizenz beachten!
;Benutzung auf eigene Gefahr!

Global hReg.l
Global hMgr.l
Global Messages.l = 0                  ;0=Meldungen abschalten, 1=Meldungen anzeigen 
Global WR0$ 
Global DriverDir$

Global SS.SERVICE_STATUS               ;(auch) PB-Structure
 
#PROCESSOR_ARCHITECTURE_AMD64 = $9

;ermitteln, ob 32- oder 64-Bit-Betriebssystem 
SI.SYSTEM_INFO                         ;Structure System_Info
GetSystemInfo_(@SI)
If SI\wProcessorArchitecture = #PROCESSOR_ARCHITECTURE_AMD64 
  OS3264 = 1 
EndIf 
;die entsprechenden Dateien verarbeiten
If OS3264 
  WR0$ = "WinRing0x64"                 ;64-Bit 
 Else
  WR0$ = "WinRing0"                    ;32-Bit
EndIf  

SystemDir$ = Space(255) 
FileL = GetSystemDirectory_(SystemDir$, 255) 
DriverDir$ = Left(SystemDir$, FileL) + "\drivers\"
OpenFile(0, DriverDir$ + WR0$ + ".sys")
LFile = Lof(0)
CloseFile(0)
If LFile = 0                           ;(komplette) Datei existiert also noch nicht
  CopyFile(WR0$ + ".sys", DriverDir$ + WR0$ + ".sys") ;die SYS im selben Verzeichnis wie diese PB!
EndIf 

;------------------ Überprüfung, ob Registry-Eintrag für WinRing0(x64) existiert
Procedure RegTest()
hReg = RegOpenKeyEx_(#HKEY_LOCAL_MACHINE, "SYSTEM\CurrentControlSet\Services\" + WR0$, 0, #KEY_ALL_ACCESS, @hKey) 
If Messages
  If hReg = 0
    MessageRequester("Statusmeldung von RegTest", WR0$ + " ist im System schon registriert")
   Else
    MessageRequester("Statusmeldung von RegTest", WR0$ + " ist dem System nicht bekannt")
  EndIf
EndIf
RegCloseKey_(hKey) 
EndProcedure 
;------------------  

;------------------ Dienst installieren
Procedure DienstInst()
hMgrC = OpenSCManager_(0, 0, #SC_MANAGER_CREATE_SERVICE) 
hInst = CreateService_(hMgrC, WR0$, WR0$, #SERVICE_ALL_ACCESS, #SERVICE_KERNEL_DRIVER, #SERVICE_SYSTEM_START, #SERVICE_ERROR_NORMAL, DriverDir$ + WR0$ + ".sys", #Null, #Null, #Null, #Null, #Null)
If Messages
  If hInst <> 0
    MessageRequester("Statusmeldung von DienstInst", WR0$ + " wurde als Dienst installiert !")
   Else 
    MessageRequester("Statusmeldung von DienstInst", WR0$ + " konnte nicht als Dienst installiert werden ! (ist evtl. schon installiert)")
  EndIf   
EndIf
CloseServiceHandle_(hInst)
CloseServiceHandle_(hMgrC)
EndProcedure   
;------------------

;------------------ DienstStatus ermitteln
Procedure DienstStatus()
hSvc = OpenService_(hMgr, WR0$, #SERVICE_QUERY_STATUS)
QueryServiceStatus_(hSvc, @SS)
If Messages  
  If SS\dwCurrentState = 4
    MessageRequester("Statusmeldung von DienstStatus", WR0$ + " ist gestartet !")
   Else 
    MessageRequester("Statusmeldung von DienstStatus", WR0$ + " ist nicht gestartet !")
  EndIf   
EndIf
CloseServiceHandle_(hSvc)
EndProcedure 
;------------------

;------------------ Dienst starten 
Procedure DienstStart()
hSvc = OpenService_(hMgr, WR0$, #SERVICE_ALL_ACCESS)
IsStart = StartService_(hSvc, 0, #Null)     ;0=war schon gestartet (=lässt sich nicht starten)  1=wurde hiermit gestartet
If Messages  
  If IsStart = 1
    MessageRequester("Statusmeldung von DienstStart", WR0$ + " wurde gestartet !")
   Else
    MessageRequester("Statusmeldung von DienstStart", WR0$ + " konnte nicht gestartet werden ! (ist evtl. schon gestartet)")
  EndIf     
EndIf
CloseServiceHandle_(hSvc)
EndProcedure 
;------------------

;------------------ Dienst beenden 
Procedure DienstEnd()
hSvc = OpenService_(hMgr, WR0$, #SERVICE_ALL_ACCESS)
IsEnd = ControlService_(hSvc, #SERVICE_CONTROL_STOP, @SS)
If IsEnd = 1
  MessageRequester("Statusmeldung von DienstEnd", WR0$ + " wurde beendet !")   ;1=beendet
 Else
  MessageRequester("Statusmeldung von DienstEnd", WR0$ + " wurde nicht beendet !")
EndIf 
CloseServiceHandle_(hSvc)
EndProcedure 
;------------------

;------------------ Dienst entfernen, Dateien können gelöscht werden
Procedure DienstRemove()
hSvc = OpenService_(hMgr, WR0$, #SERVICE_ALL_ACCESS)
IsDel = DeleteService_(hSvc)
If IsDel = 1
  MessageRequester("Statusmeldung von DienstRemove", WR0$ + " wurde entfernt !")    ;1=entfernt
 Else
  MessageRequester("Statusmeldung von DienstRemove", WR0$ + " wurde nicht entfernt !")
EndIf 
CloseServiceHandle_(hSvc)
EndProcedure 
;------------------

;------------------ WinRing0(x64) installieren
Messages = 1                 ;für Fehlersuche oder reine Neugier
RegTest()                    ;ob WinRing0(x64) in Registry vorhanden
If hReg
  Abfrage = MessageRequester("Abfrage", "Soll " + WR0$ + " installiert werden ?", #PB_MessageRequester_YesNo)
  If Abfrage = #PB_MessageRequester_Yes
    DienstInst()             ;war noch nicht installiert
   Else
    End 
  EndIf  
EndIf 
;Dienst-Start hier mehr für Testzwecke 
hMgr = OpenSCManager_(#Null, #Null, #GENERIC_READ)    ;Handle für Zugriff auf den Dienst-Manager
DienstStatus()               ;bei Programmstart überprüfen
If SS\dwCurrentState <> 4    ;4=schon gestartet
  DienstStart()              ;noch nicht gestartet, also jetzt starten
EndIf

;bei Bedarf verwenden
;DienstEnd()
;DienstRemove()               ;entfernt auch aus der Registry

CloseServiceHandle_(hMgr)    ;hier erst beenden
Gruß
Helle
Stephanie
Beiträge: 1
Registriert: 19.10.2010 23:12

Re: Performance-Monitoring des eigenen Codes (z.B.für Optimi

Beitrag von Stephanie »

Ich mache mir gerade auch Gedanken wie man auf Performance Counter bei X86(INTEL)
zugreifen kann.
Kannst du mir sagen wo man weitere Informationen findet bzw.
wie kann man deinen Code in C++/Assembler schreiben?

Vielen Dank fuer deine Antwort schonmal im Voraus!

Stephanie
Antworten