This is a little tip that took me sometime to figure it out:
This kind of issue happened when I was debugging a IntraWeb application created with Delphi XE3 in Windows 7. I often run Delphi XE3 as local administrator. I know, I know... but sometimes it is required for proper ISAPI debugging.
This application renders a control that supports drag and drop from Windows Explorer, when the browser also supports it (Firefox, Chorme, etc.). The issue then is: When you start the browser using ShellExecute (or CreateProcess for that matter), from Delphi XE3 (or any other version or software) running with administrator credentials, the browser is also running as administrator, right? But Windows Explorer IS NOT. So, drag and drop from Windows Explorer to Firefox, for instance, won't work. Applications running with elevated privileges and applications running as regular user just won't do drag and drop... Simple, yes?
Wednesday, December 19, 2012
Thursday, July 12, 2012
My IntraWeb optimizations are not required anymore
Yes! If you are using the latest 12.2.6 IntraWeb version you won't need my "speed patches" anymore because IntraWeb 12.2.6 are using these same faster routines, and more! :-)
You may download the new IntraWeb 12.2.6 version from here (follow the Test Releases link)
http://www.atozed.com/IntraWeb/Download/Download.EN.aspx
Enjoy!
You may download the new IntraWeb 12.2.6 version from here (follow the Test Releases link)
http://www.atozed.com/IntraWeb/Download/Download.EN.aspx
Enjoy!
Sunday, April 15, 2012
Debug ISAPI extensions in Windows 7 and IIS 7.5 using Delphi XE2
Many things have changed in IIS from version 6 to 7 (and 7.5 that comes with Windows 7). If you are not familiarized with IIS 7 (and 7.5) debugging, maybe this post may help you.
In fact, debugging ISAPI in Winodws 7 is easy if you follow a few steps:
1) In IIS, create the web application under Default Web Site.
2) Every application under Default Web Site should be using the same application pool, DefaultAppPool.
If you ignore these two rules, you will have additional configuration steps to run w3wp later.
2) In Delphi XE2 Ide, choose Run -> Parameters. Inform Host application and Parameters as you can see in the following picture:
W3WP.exe is the IIS Worker Process executable, and we will run it interactively to debug the ISAPI app. If you didn´t follow rules (1) and (2) of IIS Setup, then you will have to do additional configuration:
- Inform the web site using the parameter: -s "TheSiteId"
-Inform the application pool using the parameter: -ap "TheAppPoolName"
(This doesn't work with IIS 7 and above. The -ap parameter is not recognized by w3wp, so it's better to stay with tip (2) above)
Setting up IIS
First, you have to create a new application in IIS and set it up as usual. Two things are important during IIS and application setup:1) In IIS, create the web application under Default Web Site.
2) Every application under Default Web Site should be using the same application pool, DefaultAppPool.
If you ignore these two rules, you will have additional configuration steps to run w3wp later.
Setting up the application in Delphi XE2
1) First and more important: Run Delphi XE2 as administrator! If you don't, Delphi may not be able to start the IIS Worker process (W3WP.exe).2) In Delphi XE2 Ide, choose Run -> Parameters. Inform Host application and Parameters as you can see in the following picture:
W3WP.exe is the IIS Worker Process executable, and we will run it interactively to debug the ISAPI app. If you didn´t follow rules (1) and (2) of IIS Setup, then you will have to do additional configuration:
- Inform the web site using the parameter: -s "TheSiteId"
-
Ready to go
Now that everything is ready, you have to stop the WWW publication service (W3SVC). You may use "net stop W3SVC" from an elevated command prompt, or use the Windows services console. Once the W3SVC is stopped, just run the application from Delphi XE2 IDE and call it from your browser. When the application is loaded all your breakpoints will be activated and you can easily debug the application.Additional notes
I´ve found some references saying that you have to set the linking options "Map file -> Detailed" and "Include remote symbols -> On", in your project options. Well, in fact, the debuggin of ISAPI apps as explained here works perfeclty without the map file and the remote symbols.Monday, April 9, 2012
Faster IntToStr functions for Delphi 32 bits
Last week I was trying to use the excellent "Enhanced Run time library" from A. Bouchez, in a Delphi 2006 project. Unfortunately, due copyright restrictions, the modifications are released as a code patch and for Delphi 7 only.
Use all the modifications in a Delphi 2006 project is not an easy task. So I decided to use some of its code in another unit, patching the RTL original functions with new code, as I did a lot lately ;-)
The first two functions ported are faster IntToStr functions written 100% in assembly. The functions are 500 up to 1000% faster than original RTL functions, depending on the compiler (almost 1000% faster in Delphi 2006, almost 500% faster in Delphi XE2 - 32 bits). The code cannot be used in XE2 64 bit projects, because ASM 64 is a different beast. Maybe I will port other functions as well (things that don't violate copyright material), but many of them are already addressed in RtlVclOptimize (from Andreas Hausladen).
I created an unit called uCBRTLPatch.pas, containing two functions:
These functions will replace (patch) original IntToStr functions in runtime (for Integer and Int64 parameters).
To use it, you just have to add uCBRTLPatch.pas to your .DPR uses clause and you are done. You can donwload uCBRTLPatch.pas here.
Enjoy!
The first two functions ported are faster IntToStr functions written 100% in assembly. The functions are 500 up to 1000% faster than original RTL functions, depending on the compiler (almost 1000% faster in Delphi 2006, almost 500% faster in Delphi XE2 - 32 bits). The code cannot be used in XE2 64 bit projects, because ASM 64 is a different beast. Maybe I will port other functions as well (things that don't violate copyright material), but many of them are already addressed in RtlVclOptimize (from Andreas Hausladen).
I created an unit called uCBRTLPatch.pas, containing two functions:
function FastIntToStr(Value: Integer): string; function FastInt64ToStr(Value: Int64): string;
These functions will replace (patch) original IntToStr functions in runtime (for Integer and Int64 parameters).
To use it, you just have to add uCBRTLPatch.pas to your .DPR uses clause and you are done. You can donwload uCBRTLPatch.pas here.
Enjoy!
TCodeRedirect rewrite
A few weeks ago I wrote a post about TCodeRedirect. After that, I had a few problems using that code in Delphi 2006, specifically internal compiler errors (Internal Compiler Errors = useless error message!). The errors were occurring in some projects, not in all...
After a few tests I thought that could be something related to naming conflicts with original CodeRedirect function in Andreas Hausladen's RtlVclOptimize.pas (also used in my projects). So I decided to rename everything to avoid naming conflicts and it worked! No more internal compiler errors! Now my unit is named CodePatch.pas. I've updated my IntrawebPatch to use this new unit, and I'm publishing my new code here.
Enjoy!
After a few tests I thought that could be something related to naming conflicts with original CodeRedirect function in Andreas Hausladen's RtlVclOptimize.pas (also used in my projects). So I decided to rename everything to avoid naming conflicts and it worked! No more internal compiler errors! Now my unit is named CodePatch.pas. I've updated my IntrawebPatch to use this new unit, and I'm publishing my new code here.
Enjoy!
Monday, March 12, 2012
Show "loading animation" during long Intraweb AJAX requests
Yesterday I was talking to another Intraweb developer and I've created a demo application to show him how I lock the screen, showing the loading AJAX animation (IWLocker), during an AJAX request that takes long time to return. After that, I've improved my demo using 3 different techniques do do that. In this post I will show you all the 3 techniques.
Note that to show the IWLocker, I'm calling ShowBusy function. ShowBusy is declared in IWPreScript.js, a JavaScript library that is available to all your IWForms. ShowBusy expects a single boolean parameter. If you pass true the IWLocker will be shown, and if you pass false the IWLocker will be hidden. Also note that executeAjaxEvent will call IWFORM1.DoMyAjaxFunc method of your form. This method must be registered so IW core can call it. To register that method, first you have to create the method DoMyAjaxFunc in your IWForm1:
Then we must register DoMyAjaxFunc callback, using:
That's it! If you run your application and click on the IWButton1, you will see that IWLocker will become visible for 5 seconds (the time that DoMyAjaxFunc() method takes to run) and then will be hidden again.
When the event OnAsyncClick is fired the second time, there will be a SecondCall parameter in EventParams list and then we know that this time we must do the "real" work. When the method finishes, it will then call ShowBusy(false); hiding the IWLocker again.
Enjoy!
The problem
Suppose you have a IWButton in a form and want to trigger an async (AJAX) request that will take a long time to return. For instance, you will create a complex report, or generate a complex file to be downloaded later. You want your user to click the button and have some kind of feedback that your application is working, yes? At the same time you don't want your user to keep clicking all over the place... The best option, IMO, is tho show the IWLocker (the IW AJAX "loading animation") during the request. I will show you three different techniques to do that. Very similar but you may prefer one over the other.First solution
For this solution, I will create a JavaScript function that will be called by your button directly, without any OnAsyncClick event handler. First, you have to create the JavaScript function. Let's say:function myAjaxFunc() { var myData = "Nicole Scherzinger,Scarlett Johansson,Jennifer Lawrence,Doutzen Kroes,Charlize Theron"; ShowBusy(true); executeAjaxEvent("&data="+myData, null,"IWFORM1.DoMyAjaxFunc", false, null, false); return true; }This is a simple js function: There is a variable myData that holds the data that I want to send to the server, in this case, a comma separated list of names, but it can be whatever you want. Then I call ShowBusy, and after that I call the real AJAX request, executeAjaxEvent.
Note that to show the IWLocker, I'm calling ShowBusy function. ShowBusy is declared in IWPreScript.js, a JavaScript library that is available to all your IWForms. ShowBusy expects a single boolean parameter. If you pass true the IWLocker will be shown, and if you pass false the IWLocker will be hidden. Also note that executeAjaxEvent will call IWFORM1.DoMyAjaxFunc method of your form. This method must be registered so IW core can call it. To register that method, first you have to create the method DoMyAjaxFunc in your IWForm1:
const CDATATag = '< ! [CDATA[%s] ] >'; // spaces added so blogger won't mess the CDATA string procedure TIWForm1.DoMyAjaxFunc(EventParams: TStringList); var ResponseFunc: string; sl: TStrings; s: string; begin sl := TStringList.Create; try sl.StrictDelimiter := True; sl.CommaText := EventParams.Values['data']; s := sl.Strings[Random(sl.Count)]; finally sl.Free; end; Sleep(5000); // simulate a long operation IWLabel1.Caption := 'The hot chick chosen is: ' + s; ResponseFunc := Format(CDATATag, ['ShowBusy(false);']); WebApplication.CallBackResponse.AddJavaScriptToExecute(ResponseFunc); end;The code above shows the DoMyAjaxFunc. It retrieves the "data" parameter sent from myAjaxFunc() and simulates a long operation (in this case, 5 seconds). When this method finishes, it sends ShowBusy(false) to the browser using WebApplication.CallBackResponse.AddJavaScriptToExecute method.
Then we must register DoMyAjaxFunc callback, using:
WebApplication.RegisterCallBack(UpperCase(Self.Name) + '.DoMyAjaxFunc', DoMyAjaxFunc); // Self.Name -> IWForm1Finally you must call the JavaScript function myAjaxFunc from some JavaScript event directly, let's say in the onClick button event handler:
procedure TIWForm1.IWAppFormCreate(Sender: TObject); begin IWButton1.ScriptEvents.HookEvent('onClick', 'myAjaxFunc();'); end;I hooked myAjaxFunc() to the onClick event handler of IWButton1 in runtime. It can be done in design time, using Object Inspector as well.
That's it! If you run your application and click on the IWButton1, you will see that IWLocker will become visible for 5 seconds (the time that DoMyAjaxFunc() method takes to run) and then will be hidden again.
Second solution
The second solution is very similar to the first, but instead of calling your myAjaxFunc() directly from your onClick event handler, you can use IW async events to call it, in this case, IWButton2 (another button in IWForm1) onAsyncClick event:const CDATATag = '< ! [CDATA[%s] ] >'; // spaces added so blogger won't mess the CDATA string procedure TIWForm1.IWButton2AsyncClick(Sender: TObject; EventParams: TStringList); var ResponseFunc: string; begin ResponseFunc := Format(CDATATag, ['ShowBusy(true);myAjaxFunc();']); WebApplication.CallBackResponse.AddJavaScriptToExecute(ResponseFunc); end;Inside IWButton2 OnAsyncClick event handler I add JavaScript to execute, again using AddJavaScriptToExecute method. The JavaScript code that will be executed is a call to ShowBusy (showing the IWLocker) and then a call to myAjaxFunc();.
Third solution
The third solution is a little different from the others. It uses the IWButton3 OnAsyncClick twice. Let's see how:procedure TIWForm1.IWButton3AsyncClick(Sender: TObject; EventParams: TStringList); var ResponseFunc: string; FirstCall: boolean; begin FirstCall := (EventParams.Values['SecondCall'] <> 'true'); if FirstCall then // this code will run in the first IWButton3AsyncClick call begin ResponseFunc := Format(CDATATag, ['ShowBusy(true);executeAjaxEvent("&SecondCall=true", null,"' + IWButton3.HTMLName + '.DoOnAsyncClick", false, null, false);']); end else begin // this code will run in the second IWButton3AsyncClick call Sleep(5000); // simulate a long operation ResponseFunc := Format(CDATATag, ['ShowBusy(false);']); IWLabel1.Caption := 'Result returned'; end; WebApplication.CallBackResponse.AddJavaScriptToExecute(ResponseFunc); end;When you click the IWButton3, the OnAsyncClick event is fired. You then search for some parameter, in this case "SecondCall" in the EventParams list. If SecondCall is not there, then this is the first OnAsyncClick call. In the first call we add some JavaScript code to be executed. This code shows the IWLocker (calling ShowBusy(true)), and then calls the OnAsyncClick event again, this time passing the SecondCall parameter to the method.
When the event OnAsyncClick is fired the second time, there will be a SecondCall parameter in EventParams list and then we know that this time we must do the "real" work. When the method finishes, it will then call ShowBusy(false); hiding the IWLocker again.
Conclusions
The three techniques have the same effect: Show IWLocker during a long AJAX async request. Using the first method, your application will use a single AJAX request to your server, but you have to write some JavaScript code. Using the third method you won't have to write a single line of JavaScript but there are two AJAX requests involved. If you are not comfortable writing JavaScript you may prefer this solution. The second has the worst of both worlds: Two AJAX requests involved and some JavaScript coding... ;-)Download the sample project
You can download a complete project showing all the three methods here.Enjoy!
Wednesday, March 7, 2012
Crossbrowser, multi-line hints in Intraweb
Yesterday I saw a post in Embarcadero forums asking how to create multi-line hints in a IWGrid, inside an Intraweb application.
Well, this doesn't have a simple answer because IWGrid cells are TD HTML tags, and the hint is the title attribute. There is no reliable way to make the title attribute break lines across browsers. It is doable in IE but not in Firefox, for instance.
I will show you a technique that can be used in your Intraweb application to customize the hints. I will use a small javascript library in this example, but other library can be used as well. I'm using one of the simplest solutions I've found, from Craige Erskine, old qTip. The advantage of this release of qTip is that it is simple, compact and doesn't require another JavaScript library (jQuery, etc). It is composed of two files only: qTip.js and qTip.css. I have modified this release of qTip a little because it was replacing the windows.onload event with its own handler - not a good thing to do in JavaScript...
1) IWContextHelper expect CSS files to be in /files/css/ subfolder, and JavaScript files to be in /files/js/ sub-folder. This can be changed in IWContextHelper.
2) If the default sub-folders are kept, then you have to set AllowSubFolder of your ServerController instance to TRUE.
3) The JavaScript file must be added to the BODY part of the HTML document. That's why the third parameter of TIWContextHelper.AddJavaScriptFile is TRUE.
2) qTip, as included in the zip file, will "qTip-ize" a, label, input and td HTML tags. If you want to add other tags, modify the qTipTag var, inside qTip.js file.
3) Hint windows style can be customized editing qTip.css file.
Enjoy!
Well, this doesn't have a simple answer because IWGrid cells are TD HTML tags, and the hint is the title attribute. There is no reliable way to make the title attribute break lines across browsers. It is doable in IE but not in Firefox, for instance.
I will show you a technique that can be used in your Intraweb application to customize the hints. I will use a small javascript library in this example, but other library can be used as well. I'm using one of the simplest solutions I've found, from Craige Erskine, old qTip. The advantage of this release of qTip is that it is simple, compact and doesn't require another JavaScript library (jQuery, etc). It is composed of two files only: qTip.js and qTip.css. I have modified this release of qTip a little because it was replacing the windows.onload event with its own handler - not a good thing to do in JavaScript...
Include qTip files in your IWForm
Each form rendered that will use qTip hints need to include qTip files (qTip.js and qTip.css). This is done using one of my utility classes TIWContextHelper (file included in the download), in the FormRender event:uses IWContextHelper; procedure TMyIWForm.IWAppFormRender(Sender: TObject); begin TIWContextHelper.AddCSSFile(Self.PageContext, 'qTip.css'); TIWContextHelper.AddJavaScriptFile(Self.PageContext, 'qTip.js', True); end;Notes:
1) IWContextHelper expect CSS files to be in /files/css/ subfolder, and JavaScript files to be in /files/js/ sub-folder. This can be changed in IWContextHelper.
2) If the default sub-folders are kept, then you have to set AllowSubFolder of your ServerController instance to TRUE.
3) The JavaScript file must be added to the BODY part of the HTML document. That's why the third parameter of TIWContextHelper.AddJavaScriptFile is TRUE.
Deploy your application
You will have to deploy qTip.js and qTip.css with your application, inside the sub-folders used in step 1. And you are done!Testing
This is a test using the default "Features" Intraweb demo. I've set the hints to a multi-line string, for each cell in the grid:Download simple demo
You can download a simple demo with all required files here.Further notes
1) The project was created using Delphi 2006 + IW 10.0.23 but should run in newer versions without problems.2) qTip, as included in the zip file, will "qTip-ize" a, label, input and td HTML tags. If you want to add other tags, modify the qTipTag var, inside qTip.js file.
3) Hint windows style can be customized editing qTip.css file.
Enjoy!
Tuesday, March 6, 2012
Intraweb TextToHTMLStringLiteral in a multi-threaded test
In a recent blog post (Speeding Intraweb up again) I wrote that I suspected that in a multi-threaded test, running in a multi-core computer, the modified version of TextToHTMLStringLiteral would perform even better. So I decided to test it in this context. Now I can tell you that I was right ;-)
Note that there are two threads but CPU utilization is only slightly above 50%.
Now the second CPU utilization chart:
Note that now CPU utilization is almost 100% (and always above 90%). Of course there are a few LOCKed instructions here and there, but it is clearly more multi-core friendly.
Modified TextToHTMLStringLiteral time: 3.6 seconds (using ~ 98% CPU x 2) The time measures showed that the modified code performs almost 7 times better than the original, using 2 threads (It was 6 times better in a single thread app). We can expect that the more cores you have, the bigger will be time difference.
The test
Today I created a new multi-threaded test. Two threads running in parallel doing the same thing: Taking a string and using TextToHTMLStringLiteral to convert it multiple times, inside a tight loop. During the first test, both threads used the original TextToHTMLStringLiteral implementation. In the second test, I used my modified version. Both threads execute the following code:procedure TMyThread.Execute; begin FCount := 0; repeat FStr := 'ABCD XPTO %^&* ABCD XPTO %^&* ABCD XPTO %^&*'; FStr := TextToHTMLStringLiteral(FStr); Inc(FCount); until FCount = MaxCount; end;
CPU Utilization
The fist thread CPU utilization chart can be seen below.Note that there are two threads but CPU utilization is only slightly above 50%.
Now the second CPU utilization chart:
Note that now CPU utilization is almost 100% (and always above 90%). Of course there are a few LOCKed instructions here and there, but it is clearly more multi-core friendly.
The results
Original TextToHTMLStringLiteral time: 24.4 seconds (using ~ 55% CPU x 2)Modified TextToHTMLStringLiteral time: 3.6 seconds (using ~ 98% CPU x 2) The time measures showed that the modified code performs almost 7 times better than the original, using 2 threads (It was 6 times better in a single thread app). We can expect that the more cores you have, the bigger will be time difference.
Conclusion
We can see clearly that Delphi code written with special care, specially code dealing with string writing/concatenation, can greatly improve performance and make the compiled code much more multi-core friendly.Monday, March 5, 2012
TCodeRedirect redux
In my latests posts I was dealing a lot with Delphi classes runtime patching. Thanks to Andreas Hausladen, Chau Chee Yang (among others) most of the hard work is already done, but...
I've created a new and different version of Yang's TCodeRedirect that I consider easier to work with, because it works as a singleton. This way, you won't need to create a new TCodeRedirect instance for each patch you apply during runtime.
I'm releasing the source code of this new TCodeRedirect class because many Delphi developers may use this technique in their own applications and frameworks, so here is it!
Chau Chee Yang covered most of TCodeRedirect utilization cases in his original blog post. What I've changed is: Using Yang's code you would call TCodeRedirect this way:
I've created a new and different version of Yang's TCodeRedirect that I consider easier to work with, because it works as a singleton. This way, you won't need to create a new TCodeRedirect instance for each patch you apply during runtime.
I'm releasing the source code of this new TCodeRedirect class because many Delphi developers may use this technique in their own applications and frameworks, so here is it!
Chau Chee Yang covered most of TCodeRedirect utilization cases in his original blog post. What I've changed is: Using Yang's code you would call TCodeRedirect this way:
TCodeRedirect.Create(@MyOldProc, @MyNewProc);With my new TCodeRedirect, you may use it with another syntax, like this:
TCodeRedirect.GetInstance.AddPatch(@MyOldProc, @MyNewProc);This code was tested with Delphi 6, 7, BDS 2006 and XE2 but should work with other versions as well. Enjoy!
Modifying Intraweb Patches
Today I received a message from a blog reader, pointing that my patches were not compiling under more recent versions of Delphi, specially Delphi 2010. Really, I haven't checked D2010 compatibility before releasing my code. The problem is not in IntrawebPatch.pas but inside my modified RtlVclOptimize.pas (from Andreas Hausladen). Trying to compile it under Delphi XE2, for instance, showed me that it has many incompatibilities...
So, I decided to create a new patching mechanism. It is not original, but my own version of CodeRedirect, based on Chau Chee Yang TCodeRedirect (his work is also based on Andreas Hausladen, I guess).
This code was tested with BDS 2006 and XE2, and IW 10 and XI but should work with other versions as well.
Enjoy!
So, I decided to create a new patching mechanism. It is not original, but my own version of CodeRedirect, based on Chau Chee Yang TCodeRedirect (his work is also based on Andreas Hausladen, I guess).
This code was tested with BDS 2006 and XE2, and IW 10 and XI but should work with other versions as well.
The downloads
You can download a zip file containing IntrawebPatch.pas, CodeRedirect.pas and other required files here.Enjoy!
Friday, March 2, 2012
Speeding Intraweb up again
A few weeks ago I wrote about how to hack Intraweb classes to speed it up a little. I've found another method that could be faster, using a similar approach:
This method is TextToHTMLStringLiteral, from TIWBaseForm class, declared in IWBaseForm.pas. This method is used inside the TIWForm.GenerateForm method, once for each hidden field:
Original code: 5.10 seconds
Modified code: 0.82 seconds
for 1,000,000 function calls. Quite impressive, isn't it? Of course, this benchmark used a specific string, and a different string will give different results.
This time, the new method is more than 6 times faster than the original method, in a single thread. I suspect, based on my previous post about String concatenation in Delphi, that the new code can be even faster - and more multi-core friendly - in a multi-threaded application (like IW applications). I will create this benchmark application and write a blog post about the results soon.
Checking one of my IW applications, I've found forms with more than 100 hidden fields in it. The time gain will be insignificant (less than 1 millisecond), but this is not the point. The main point when I try to make my applications faster, specially Intraweb applications, is: make it run as fast as you can AND make it more multi-core friendly.
Enjoy!
class function TIWBaseForm.TextToHTMLStringLiteral(aText: string): string; var f : integer; begin Result := ''; for f := 1 to Length(aText) do begin if (AnsiChar(aText[f]) in ['A'..'Z', 'a'..'z', '0'..'9', ',', '.', '-', '_']) then begin Result := Result + aText[f]; end else begin Result := Format('%s%d;', [Result, Ord(aText[f])]); end; end; end;Here we can see the same pattern we found in the previous modified methods. We will use the same kind of construction - and the same runtime patching technique - to replace this method with a faster one, during execution. The new TextToHTMLStringLiteral is shown below:
class function TIWBaseForm.TextToHTMLStringLiteral(aText: string): string; const NoConversion = ['A'..'Z', 'a'..'z', '0'..'9', ',', '.', '-', '_']; var Sp, Rp: PChar; begin SetLength(Result, Length(AText) * 8); Sp := PAnsiChar(AText); Rp := PAnsiChar(Result); while Sp^ <> #0 do begin if (Sp^ in NoConversion) then begin Rp^ := Sp^; end else begin Inc(Rp, FormatBuf(Rp^, 7, '%.4d;', 7, [Ord(Sp^)]) - 1); end; Inc(Rp); Inc(Sp); end; SetLength(Result, Rp - PAnsiChar(Result)); end;I will not enter in much detail about the implementation, but I think it is quite simple, and it uses the same approach used in the previous post.
The results
Again, I did a simple benchmark application so I could see if there is some gain using this new method.Original code: 5.10 seconds
Modified code: 0.82 seconds
for 1,000,000 function calls. Quite impressive, isn't it? Of course, this benchmark used a specific string, and a different string will give different results.
This time, the new method is more than 6 times faster than the original method, in a single thread. I suspect, based on my previous post about String concatenation in Delphi, that the new code can be even faster - and more multi-core friendly - in a multi-threaded application (like IW applications). I will create this benchmark application and write a blog post about the results soon.
Checking one of my IW applications, I've found forms with more than 100 hidden fields in it. The time gain will be insignificant (less than 1 millisecond), but this is not the point. The main point when I try to make my applications faster, specially Intraweb applications, is: make it run as fast as you can AND make it more multi-core friendly.
The downloads
You can download an updated IntrawebPatch.pas unit containing this new patch (and the old ones) here.Enjoy!
Tuesday, February 7, 2012
Investigating string concatenation performance in Delphi
I got really puzzled when I read Arnaud Bouchez blog post Delphi doesn't like multi-core CPUs (or the contrary). Delphi's generated code should be fast, shouldn't it?? Maybe it is very fast in a single threaded program or even in a multi-threaded program running on a single core computer. As pointed by Arnaud, the asm LOCK prefix is used to ensure exclusive access to the memory address. In a multi-core CPU, all cores just freeze during LOCK execution.
Running with TMyThread_String:
Running with TMyThread_StrBuilder:
Note that the test with TStrBuilder class is burning 100% of my CPU, while the test with standard string type just can't use all the resources, even if my PC is idle.
Running with TMyThread_String: 26.4 seconds
Running with TMyThread_StrBuilder: 7.3 seconds. Almost 4 times faster!
Please note:
What about Delphi?
Quoting the original post:String types and dynamic arrays just use the same LOCKed asm instruction everywhere, i.e. for every access which may lead into a write to the string.So, if you are writing to a string, there are a few LOCK instructions being generated by the compiler, every single time. Even if your computer is a super-duper multi-core machine, it will behave - during these instructions - if they were a single-core CPU.
What about string concatenation in Delphi?
Well, if you do a lot of string concatenation, then a really big part of the time is being spent writing to strings, isn't it? Maybe, during the string concatenation, your cores are being LOCKed and your software is not using all the CPU power at its disposal... So, I decided to create a simple test case to show me if this is true, and how much this may affect the performance.The test
I've created 2 different thread types, doing the same work during their execute method (a simple string concatenation inside a tight loop). The first uses a standard string type:type TMyThread_String = class(TThread) private FStr: string; public procedure Execute; override; end; procedure TMyThread_String.Execute; begin FCount := 0; repeat FStr := 'string 1'; FStr := FStr + ' + string 2'; Inc(FCount); until FCount = MaxCount; end;The second thread type uses a TStrBuilder class (my own TStringBuilder implemenation):
type TMyThread_StrBuilder = class(TThread) private FStr: TStrBuilder; public procedure Execute; override; end; procedure TMyThread_StrBuilder.Execute; begin FCount := 0; repeat FStr.Clear; FStr.Append('string 1').Append(' + string 2'); Inc(FCount); until FCount = MaxCount; end;Both threads do exactly the same. So I've put both on test: First I've created two instances of TMyThread_String and started both in parallel (using a dual-core machine). Then I repeated the test, but using TMyThread_StrBuilder.
The results
Impressive results! These are the task manager CPU charts:Running with TMyThread_String:
Running with TMyThread_StrBuilder:
Note that the test with TStrBuilder class is burning 100% of my CPU, while the test with standard string type just can't use all the resources, even if my PC is idle.
More results
The times spent to do the string concatenation are even more impressive:Running with TMyThread_String: 26.4 seconds
Running with TMyThread_StrBuilder: 7.3 seconds. Almost 4 times faster!
The ShortString case
Following Arnaud advice, I've tried the ShortString type also, so I've created a third thread type, using ShortString concatenation, and not a standard string type. The results are identical to those obtained using standard string type: The same CPU usage chart (ranging from 60-70%) and a slightly worse time (28.5 seconds).Conclusion
Using my simple test, I've discovered that if you have heavy string concatenation (writing) in a multi-threaded environment (for instance: COM+ servers, Intraweb applications, multi-threaded services, etc.) maybe you should avoid standard string types, and use another approach like a TStringBuilder class.Please note:
- I did not use the TStringBuilder class from Delphi's own RTL, but my own TStrBuilder class. AFAIK, standard TStringBuilder has the same problem because it uses a standard string internally...
- I have to investigate a little further to know why ShortStrings are as bad as standard strings (or even worse).
Tuesday, January 17, 2012
Hack Intraweb and make it faster!
I'm always looking for a way to make my applications faster. During last couple of days I've been investigating an - already fast - Intraweb application.
Update: I'm not using Andreas RtlVclOptimize.pas anymore because it has a few incompatibilities with recent Delphi versions. I'm using another unit CodeRedirect.pas, included in the download file.
Enjoy!
1) Intraweb rendering implementation
Lots of Intraweb controls use an utility function to render their HTML. This function called TextToHTML is implemented as a class function of TIWBaseHTMLControl class. This function is used because if you have an IWText component with a caption containing a special character, lets say, an ampersand (&) it must be properly encoded to be shown by the browser. An ampersand declares the beginning of an entity reference (a special character) in HTML, so to be shown in the page it must be replaced by "&" (without quotes). If you have IW sources you can search for TextToHTML and see that it is used in a lot of places, most inside RenderHTML and RenderAsync methods.2) TextToHTML implementation
Well, TextToHTML is not as fast as it could be. If you imagine that a lot of strings should be processed by TextToHTML before they get to the browser you may improve your application performance if you make that function faster, isn't it? Let's look at TextToHTML code:class function TIWBaseHTMLControl.TextToHTML(const AText: string; const AConvertEOLs: Boolean; const AConvertSpaces: Boolean): string; var f : integer; xIsCallBack: Boolean; begin Result := ''; xIsCallBack := GGetWebApplicationThreadVar.IsCallBack; for f := 1 to Length(AText) do begin case AText[f] of '<' : Result := Result + '<'; '>' : Result := Result + '>'; '"' : Result := Result + '"'; '''' : Result := Result + '''; '&' : Result := Result + '&'; else {$ifdef UNICODE} if (Char(AText[f]) in [#10, #13]) then begin {$else} if (AnsiChar(AText[f]) in [#10, #13]) then begin {$endif} if AConvertEOLs then begin case AText[f] of #10 : Result := Result + '< br >'; #13 : Result := Result + ''; end; end else begin Result := Result + AText[f]; end; end else begin if (AText[f] = #32) and AConvertSpaces then begin if xIsCallBack then begin Result := Result + ' '; end else begin Result := Result + ' ' end; end else begin Result := Result + AText[f]; end; end; end; end; end;We can see that there is a main loop interating throught all characters of the AText string, concatenating char by char to generate the result. This approach has two drawbacks: (1) Every string concatenation requires a new memory allocation and (2) it is SLOW!
3) A New TextToHTML implementation
We can implement TextToHTML using another approach, used by Delphi's own RTL in functions like HTMLEncode (unit HTTPApp.pas):class function TIWBaseHTMLControlHack.TextToHTML(const AText: string; const AConvertEOLs: Boolean; const AConvertSpaces: Boolean): string; var Sp, Rp: PChar; xIsCallBack: Boolean; begin xIsCallBack := GGetWebApplicationThreadVar.IsCallBack; SetLength(Result, Length(AText) * 10); Sp := PChar(AText); Rp := PChar(Result); while Sp^ <> #0 do begin case Sp^ of '&': begin FormatBuf(Rp^, 5, '&', 5, []); Inc(Rp, 4); end; '<', '>': begin if Sp^ = '<' then FormatBuf(Rp^, 4, '<', 4, []) else FormatBuf(Rp^, 4, '>', 4, []); Inc(Rp, 3); end; '"': begin FormatBuf(Rp^, 6, '"', 6, []); Inc(Rp, 5); end; '''': begin FormatBuf(Rp^, 5, ''', 5, []); Inc(Rp, 4); end; '\': begin FormatBuf(Rp^, 5, '\', 5, []); Inc(Rp, 4); end; #10: if AConvertEOLs then begin FormatBuf(Rp^, 4, '< br >', 4, []); Inc(Rp, 3); end else Rp^ := Sp^; #13: if AConvertEOLs then begin Dec(Rp); end else Rp^ := Sp^; #32: if AConvertSpaces then begin if xIsCallBack then begin FormatBuf(Rp^, 10, '&nbsp;', 10, []); Inc(Rp, 9); end else begin FormatBuf(Rp^, 6, ' ', 6, []); Inc(Rp, 5); end; end else Rp^ := Sp^; else Rp^ := Sp^ end; Inc(Rp); Inc(Sp); end; SetLength(Result, Rp - PChar(Result)); end;This new implemenation has two main advantages over the former: (1) There is less overhead due memory allocations, and (2) it is FASTER! :)
4) The problem
TextToHTML is a public method of TIWBaseHTMLControl but it is not virtual. Even if it were virtual, we would have to create descendant classes to override this method and it is not acceptable.5) The solution: Hack it!
Thanks to great code (RtlVclOptimize.pas) created and made available by Andreas Hausladen we can change TIWBaseHTMLControl implementation on the fly, patching the class. I will not enter in detail about Andreas code, but the hack is done replacing, in memory, the TIWBaseHTMLControl.TextToHTML method by another method from some other class. I needed to change RtlVclOptimize.pas a little, putting the declaration of the function "CodeRedirect" in the interface section, so it could be used by my code, outside that unit.Update: I'm not using Andreas RtlVclOptimize.pas anymore because it has a few incompatibilities with recent Delphi versions. I'm using another unit CodeRedirect.pas, included in the download file.
6) Speed comparison
The use of Andy's RtlVclOptimize unit alone can significantly improve IW applications performance. Moreover, in my tests, my TextToHTML implemenation is more than 4 times faster than the original. Original code: 14.7 seconds versus 3.4 seconds using my code (1,000,000 function calls). Another advantage is less memory allocations for strings (less memory fragmentation and less LOCK+multicore related issues - read more about it here).7) More Hacks!
Well, not satisfied hacking TextToHTML code, I did the same thing with another TIWBaseHTMLControl method, TextToJSStringLiteral . Finally I've packed all these little things inside a unit. To speed up your IWApplication a bit just declare RtlVclOptimize (from Andreas) and my unit, IntrawebPatch.pas inside your .DPR file and you are done!8) Finally, the downloads!
You can download my patch unit (IntrawebPatch.pas) and other required files here.Enjoy!
Friday, January 13, 2012
Delphi 5 Update packs available again!
Sorry folks, my links to Delphi 5 Update packs were broken. Now I've fixed them :-)
You may find my original post with download links here.
Tuesday, January 10, 2012
BDE and Windows 7, 32 and 64 bits
I know, I know... BDE is discontinued and stuff. But many people still need BDE running in new Windows 7 32 and 64 machines. Here is a working Setup for BDE 5.2.0.2 (the latest version released by Borland) plus all SQL Links.
Notes:
Download BDE 5.2.0.2 Setup in English:
http://www.alex7691.com/download/Setup_BDE52.zip
Download BDE 5.2.0.2 Setup in brazilian portuguese:
http://www.alex7691.com/download/Setup_BDE52_PTB.zip
Note: During last months the number of downloads increased a LOT. Some users were receiving messages like "bandwidth limit exceeded". Now I have upgraded my host service plan, so you can easily download it!Enjoy! :-)
Some user gave a false alert about malware/trojan/virus/whatever inside the files. According to VirusTotal (http://www.virustotal.com) this is completely untrue.
Latest scan report of this file: https://www.virustotal.com/en/file/7d37616710f8d5da8d2f859560814fd89b22aef7b6fb47902161bc9f7289145a/analysis/
In short: 0 detections!
Please feel free to re-submit it ;-)
PS: Rene Nussbaum, from Germany wrote me asking about the setup license model/type. Well... This setup is 100% free including its usage in commercial software. You can include this setup in your deployment package, and you can also redistribute it.
Enjoy! :-)
Notes:
- Installs BDE 5.2.0.2 + all SQL Links
- During setup there is an option to remove previous BDE registry entries.If you have an old corrupted BDE installation, maybe this option can fix it (It will remove EVERYTHING below HKEY_LOCAL_MACHINE\SOFTWARE\Borland\Database Engine registry entry, and you have been warned!).
- This setup was tested and works perfectly under Windows 2000, 2003 Server, XP, 2008 Server, Vista and Seven, 32 and 64 bits.
- The setup correctly prompts for UAC elevation. By the way, it was created using Jordan Russell's excellent installer creator Inno Setup (also created with Delphi)!
Download BDE 5.2.0.2 Setup in English:
http://www.alex7691.com/download/Setup_BDE52.zip
Download BDE 5.2.0.2 Setup in brazilian portuguese:
http://www.alex7691.com/download/Setup_BDE52_PTB.zip
Note: During last months the number of downloads increased a LOT. Some users were receiving messages like "bandwidth limit exceeded". Now I have upgraded my host service plan, so you can easily download it!Enjoy! :-)
Some user gave a false alert about malware/trojan/virus/whatever inside the files. According to VirusTotal (http://www.virustotal.com) this is completely untrue.
Latest scan report of this file: https://www.virustotal.com/en/file/7d37616710f8d5da8d2f859560814fd89b22aef7b6fb47902161bc9f7289145a/analysis/
In short: 0 detections!
Please feel free to re-submit it ;-)
PS: Rene Nussbaum, from Germany wrote me asking about the setup license model/type. Well... This setup is 100% free including its usage in commercial software. You can include this setup in your deployment package, and you can also redistribute it.
Enjoy! :-)
Subscribe to:
Posts (Atom)