Auslösung: DrawText unter Vista gegenüber XP um bis zu Faktor 50 langsamer!

Am 17. Januar habe ich den folgenden Artikel geschrieben: DrawText unter Vista gegenüber XP um bis zu Faktor 50 langsamer!

Ich möchte Euch die Auflösung des Problems nicht vorenthalten.

Eigentlich ist es keine Lösung sondern nur der Fakt, dass auch XP unter gleichen Bedingungen genauso lahm ist wie Vista.
Es liegt an den erweiterten Spracheinstellungen, die unter XP optional sind aber eben nicht mehr unter Vista. Dort sind die immer mit installiert.

So sieht das ganze bei einer normalen XP Installation aus, mit der entsprechenden Performance:
XP-DrawText-Fast

Man kann sehen, dass die zwei unteren Checkboxen aus sind. Wenn man diese nun einschaltet und die entsprechenden Module nachinstalliert werden, dann erlebt man unter XP nach einem Neustart die selben Geschwindigkeitseinbruch wie unter Vista:
XP-DrawText-Slow

Mein Testprogramm läuft fast 50mal langsamer als bei der Standardinstallation und damit genauso schnell/lahm wie unter Vista. Nimmt man die zwei Checks wieder heraus, dann hat man den alten gewohnten Speed.

Wenn man unter Vista in den entsprechenden Dialog der Systemeinstellung sieht, kann man auch sehen, dass man hier nichts mehr beschleunigen kann durch eine eventuelle Deinstallation, denn offensichtlich gehören diese Bestandteile bei Vista zum Inventar:
Vista-DrawText-Slow

So und damit ist auch diese Supportanfrage bei Microsoft „ungelöst“, aber zumindest „erklärt“ geschlossen.

Ich frage mich dennoch warum eine solche EInstellung solche Auswirkungen haben muss. Letzten Endes sind das auch nur Fonts mit denen umgegangen werden muss. Ich finde diesen extremen Unterschied auffällig, allerdings wird sich vermutlich nichts daran ändern…

Ich wünsche allen Lesern einen schönen Juli und verziehe mich jetzt erstmal für die nächsten 2 1/2 Wochen ohne Laptop und PC an die Nordsee, zum Radfahren, Baden und Drachen steigen lassen… 😉

Tipps & Tricks: Warum nicht mal ein anderer Font, wie z.B. Consolas

Eigentlich bin ich sehr konservativ was meine Visual-Studio Einstellungen betrifft. Aber irgendwie hat mich heute mal die Experimentierlaune überkommen, weil ich durch Zufall über einige Color-Schemes gestolpert bin und im C++ Forum eine Diskussion lief ob „hell auf dunkel“ oder „dunkel auf hell“ besser ist.

Am Ende des Experimentierens, habe ich das Farbschema gelassen wie es ist. Ich habe ja auch VA-X, den untentbehrlichen Helfer des Programmieres,  und auch da ist bis auf das Highlighting alles Standard. Aber bei der Schriftart habe ich – für mich gefühlt – was besseres gefunden: „Consolas“.
Eindeutig für mich das schönere Schriftbild, klarer und etwas kompakter und kleiner als „Courier New“.

Beispiel „Courier New“
CourierNew

Beispiel „Consolas“
Consolas
Wer mag, der kann es gleich probieren. Auf meinen Vista Rechnern war diese Schriftart sofort vorhanden. Wer das Font-Paket nicht hat, der kann es herunterladen bei Microsoft Consolas Font Pack for Microsoft Visual Studio 2005 or 2008

Die Funktion ReportFault unter Vista kehrt nicht mehr zurück, entgegen der Dokumentation

Wer unter Windows XP angefangen die FaultRep.dll für Crash-Reports zu verwenden, wird unter Vista eine üble Erfahrung machen. Entgegen der Dokumentation kehrt ReportFault unter Vista nicht zurück.

Das ist besonders lästig, wenn man nach dem Melden des Fehlers aufgrund des Feedbacks des Kunden noch selber Aktivitäten in der eigenen Software vorgesehen hatte.

Wieder ein Fall wo es schwierig ist zwischen verschiedenen Windows-Betriebssystemen kompatibel zu bleiben. Da hat man sich auf Windows XP eingelassen und unter Vista ist das entsprechende Interface schon wieder deprecated.

Die HashKey Implementierung in der MFC in VC-2005 und VC-2008

Die Maps in der MFC werden ja auch zum Teil gerne verwendet. Auch wenn viele Programmierer eher auf die std::map bzw. std::hash_map verwenden.

Zwei interne Dinge sind jedoch vielen Entwicklern nicht klar.

  1. Die Standard-Maps der MFC werden mit 17 Hash-Buckets erzeugt. Man sollte sich also über die Anzahl der Elemente klar werden, die gespeichert werden sollen.
    Bei großen Datenmengen sollte man evtl. darüber nachdenken, die Anzahl der Buckets zu erhöhen.
  2. Ist die Hash Funktion zu beachten, die hier verwendet wird. Denn deren Effektivität entscheidet ja, wie die Einträge auf die Buckets verteilt werden.

Zu diesem zweiten Punkt macht man eine erstaunliche Entdeckung, wenn man sich ansieht, was Microsoft für eine Funktion vorgesehen hat um den Hask-Key zu erzeugen. Diese Funktion ist in der MFC für primitive Werte vordefiniert, als primitive Werte sehe ich alle numerischen Werte, Zeiger und Handle an.
Man findet eine Implementierung als Template-Funktion in afxtempl.h:

template<class ARG_KEY>
AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key)
{
 // default identity hash - works for most primitive values
 return (DWORD)(((DWORD_PTR)key)>>4);
}

Das erstaunliche ist für mich, dass hier die untersten 4 Bits einfach abgeschnitten werden. Für mich hätte für Handles, Zeiger und Integer Werte einfach ein cast gelangt.

Nehmen wir jetzt mal an, wir haben eine einfache Datenmenge, die die Integer 1-100 auf eine Struktur abbilden. Man wird die erstaunliche Entdeckung machen, dass alle Werte nur in den Buckets 0-6 abgespeichert werden. Die Buckets 7 bis 16 werden nicht verwendet.

Der Nachteil ist offensichtlich. Man nutzt das Datenrauschen auf den untersten 4 Bits nicht.
Das macht sich sogar bei Fensterhandles bemerkbar. Bekanntlich sind das immer gerade Werte, aber auch auf den Bits 1-3 finden wir hier Informationen. Auch hier werden nützliche „zufällige“ Informationen nicht genutzt. Dadurch werden mehr Kollisionen in Kauf genommen als notwendig wären.

Ich möchte nicht unerwähnt lassen, dass die MFC Maps wirklich eine Berechtigung haben, weil sie grundsätzlich einen Pool-Allocator verwenden. Häufige Allokationen und Löschungen werden weitaus schneller behandelt, als durch die std::map, oder std::hash_map mit normalen Allocator.

Ich habe diesen Bug (oder miese Verhalten) bereits in der Beta für VC-2008 gemeldet. Es wurde aber nicht mehr geändert. Nun habe ich es an die Produktgruppe erneut  für VC-10 eingereicht.
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100772
Nachtrag 22.06.2009: Da der Bug von Microsoft nicht neu geöffnet wird habe ich einen neuen für VS-2010 Beta 1 angelegt.
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=468860
Wer Lust hat, der kann ja abstimmen.

BTW: Die HaskKey Funktion in afxtempl.h ist für andere Maps (z.B. CMapPtrToPtr) direkt in der Klasse definiert. Dieses Template wird nur vom CMap-Template verwendet.

Tipps & Tricks: Wie man einen permanenten ASSERT direkt mit einem Kommentar versieht

Ich verwende gerne ASSERT’s in meinem Code.
Sie sind ein wirksames Mittel Zustände abzufragen und bereist in der Testphase unzulässige Konstellationen oder Funktionsaufrufe zu entdecken.

Nun gibt es ja auch if, else oder switch Blöcke an denen das Programm normalerweise nicht vorbei kommen sollte. So eine Stelle versieht man dann schon mal mit einem

_ASSERT( FALSE );
// oder wer die MFC benutzt eben ASSERT,
// obwohl dies auch nur ein Synonym für den CRT _ASSERT makro ist
ASSERT(FALSE);

Jetzt müsste man noch einen Kommentar davor setzen, damit klar wird was hier schief läuft. Man kann das Ganze aber auch einfach kombinieren und noch einen Zusatznutzen erreichen indem man den etwas unbekannteren Makro _ASSERTE verwendet:

_ASSERTE( !"MyFuncFooHandler: This is not allowed in my special Foo Handler" );

Die Negation macht aus dem Zeiger auf den konstanten String FALSE, und damit schlägt der ASSERT an.
Wenn man jetzt wie hier gezeigt noch den _ASSERTE Makro verwendet, dann wird diese Expression, also der Text, sofort mit anzeigt. Man sieht dann sofort was das Problem ist sobald der ASSERT Dialog angezeigt wird.

VC-2010: Breaking Changes in der STL

Ich habe mein erstes größeres Projekt mal testweise in VS-2010 übernommen und bin sofort über ein Problem in der STL gestolpert. Ein std::set<FOO>::iterator liefert in VC-2010 jetzt eine const FOO & Referenz!

Das Ändern von Objekten in einem std::set war sowieso nur erlaubt, wenn sich die Reihenfolge nicht ändert.

Die Folge ist, dass der nachfolgende Code in VC-2010 nicht mehr kompiliert.

#include <set>
struct S_FOO
{
    unsigned long     m_dw1, m_dw2;
    // simplified sample without bool operator<
};

class CMySet : public std::set<S_FOO>
{
public:
    void SomethingSpecial()
    {
        // Order must not be changed!
        for (iterator it=begin(); it!=end(); ++it)
            it->m_dw1 = 0;   // <----- C3892!
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    CMySet myset;
    myset.SomethingSpecial();
    return 0;
}

Man erhält den Fehler C3892: ‚it‘ : you cannot assign to a variable that is const

Das und noch einige andere Breaking Changes  wurden gestern in VC-Blog veröffentlicht:

http://blogs.msdn.com/vcblog/archive/2009/05/25/stl-breaking-changes-in-visual-studio-2010-beta-1.aspx

MSDN Abonnenten können ab Montag den 18.05.2009 Visual Studio 2010 Beta 1 herunterladen

Jihad Dannawi kündigt in seinem Blog die Veröffentlichung von Visual Studio 2010 Beta 1 für MSDN Subscriber an:
http://blogs.msdn.com/dannawi/archive/2009/05/15/visual-studio-2010-beta-1-available-for-the-msdn-subscribers-on-monday-may-18th.aspx

Dito kann man es auf ZDNet lesen:
http://blogs.zdnet.com/microsoft/?p=2769

Ich freue mich schon drauf, ich hoffe es findet sich Zeit mal wirklich damit spielen zu können und um herauszufinden ob der Slogan „The new 10 is the next 6“ wirklich trägt… 😉

AfxOleInit versus CoInitializeEx in MFC Programmen

Immer wieder mal, taucht MFC Code auf und es wird CoInitializeEx verwendet.
Nicht unbedingt ein Fehler, aber ich denke man sollte auch hier die MFC-Art-und-Weise verwenden: AfxOleInit. Und man sollte sich klar sein, welche Apartment Modes man verwenden darf!

AfxOleInit selbst ist ein per Thread Wrapper für ::OleInitialize(NULL);.
Zudem sorgt AfxOleInit dafür, dass bei Beenden des Threads automatisch AfxOleTerm aufgerufen wird.

::OleInitialize(NULL) wiederum ist ein Wrapper für den Aufruf von CoInitializeEx im STA Kontext:
Siehe MSDN http://msdn.microsoft.com/en-us/library/ms690134(VS.85).aspx 

OleInitialize calls CoInitializeEx internally to initialize the COM library on the current apartment. Because OLE operations are not thread-safe, OleInitialize specifies the concurrency model as single-thread apartment.

Die MFC benutzt und verlangt genau diesen STA Modus. Sofern man also nicht wirklich weiß was man mit CoInitializeEx macht, sollte man es in der MFC vermeiden. Zudem man sich eben auch nicht mehr um den Cleanup kümmern muss, da die MFC dies selber macht.

Siehe auch MSDN Doku zu AfxOleInit
http://msdn.microsoft.com/de-de/library/e91aseaz(VS.80).aspx

MFC applications must be initialized as single threaded apartment (STA). If you call CoInitializeEx in your InitInstance override, specify COINIT_APARTMENTTHREADED (rather than COINIT_MULTITHREADED). For more information, see PRB: MFC Application Stops Responding When You Initialize the Application as a Multithreaded Apartment (828643) at http://support.microsoft.com/default.aspx?scid=kb;en-us;828643.

Aber auch hier sei erwähnt, dass man sicherlich auch andere Apartment Modes verwenden kann. Allerdings muss man dann wirklich wissen was man macht und darf sich nicht auf die MFC Funktionen stützen. Für solche Fälle verwende ich hier komplett ATL in meinen MFC Programmen.
Man sollte aber in diesen Fällen wirklich wissen was man macht 😉

BTW: Automatisch aufgeräumt bei einem gestarteten Thread wird nur dann, wenn auch AfxBeginThread verwendet wird. Siehe auch: AfxBeginThread versus _beginthreadex

Refactoring mit Hilfe des Compilers kann eine tückische Sache werden

Wieder mal eine nette Falle: Implizite Konvertierungen und ein Refactoring-Versuch.

Folgende Methoden wurden in einer Klasse verwendet:

...
bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);
bool GetTableCoreData(long lIdAddrSet, long lIdProject,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);
bool GetTableCoreData(long lIdAddrSet,
            CDataConnection &dataConnection,
            CAgvipTableCoreData &coreData);
...

Die dritte Methode passte mir nicht von der Reihenfolge der Argumente. und ich änderte sie wie folgt um:

bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            CDataConnection &dataConnection);

Ich habe mich nun einfach darauf verlassen, dass der Compiler mir alle entsprechenden Code Stellen schon anmeckern wird, an denen hier was nicht passt. Da ich noch einiges anderes an der Klasse geändert hatte, dauerte es noch eine Weile bis ich den nächsten Build angeworfen habe, und ehrlich gesagt, habe ich das Refactoring dieser Funktion vergessen.
Typischer Fall von: Zu viel auf einmal & Der Compiler macht einfach nicht was ich will 😉

Was passierte? Nichts ❗
Ich bekam keine Fehlermeldung zu dieser Änderung, denn CDataConnection hat eine implizite Konvertierung auf bool. Die Folge war, dass die erste Signatur der Funktion auch dieser Folge von Argumenten entsprach.

bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);

Logisch, dass diese Funktion natürlich eine anderes Verhalten hatte und hier nicht mehr das passierte was ich eigentlich wollte.
Dämlicherweise rutschte diese Änderung auch noch durch die Tests und eine ganze Funktionsgruppe unserer Software wurde lahmgelegt und so ausgeliefert… Ein Bug, dazu noch von der Kategorie vermeidbar.
Was lernen wir:

  1. Es gibt keine fehlerfreie Software!
  2. Die kleinen Änderungen bringen die größten Fehler!
  3. Sich beim Refactoring auf den Compiler zu verlassen kann tückisch werden!

Manche Änderungen in den UIs machen einen wahnsinnig, heute das MS-SQL Management Server Studio

Ich nutze viel das MS-SQL Server Management Studio für MS-SQL 2005. Besonders um eben auch SQL-Statements zu testen und zu entwerfen.

Häufig lasse ich mir dazu einfach eine aktuelle Tabelle anzeigen. Mit Strg+3 kann man sich nun das SQL-Statement anzeigen lassen und es ändern. Also z.B. eine WHERE Bedingung hinzufügen.
Jeder der den Enterprise Manager gewohnt ist haut nun auf die F5-Taste, es passiert natürlich nichts 😮 . OK times are changing, wir benutzen also brav Strg+R um das neue Statement auszuführen.

OK nun klicken wir auf den Datenbanknamen und wählen hier im Kontextmenü Neue Abfrage aus. Ein bisschen SQL Statement schreiben und wir drücken Strg+R . 😮 Was denn nun? Wir bemühen also das entsprechende Menü und finden das hier der entsprechende Hotkey F5 ist.

Wer nun wieder mit MS-SQL Server 2008 arbeitet wird nun mit Strg+R kein Glück haben. Jetzt ist alles wieder gut und hier funktioniert nur noch F5. Jetzt ist dafür der Abfrage-Designer ist nun ein separater Dialog geworden, der das entsprechende Statement in das Abragefenster überträgt.
(Nachtrag 2009-05-06) Wenn man aus im Management Studio 2008 den Punkt Zeilen bearbeiten auswählt, hat man das selbe miese Verhalten wie im Management Stusio 2005. Man kommt nur nicht mehr so offensichtlich in diesen Bearbeitungsmodus und landet eher auf einer Abfrage, in der man dann aber das Ergebnis nicht bearbeiten kann.

Es gibt Momente in denen ich mich beherrschen muss nicht in die Tischplatte zu beißen… 😡