Tuesday 30 June 2009

Delphi for iPhone

Will the next version of Delphi Prism officially support the iPhone?

* Miguel de Icaza announces MonoTouch

* MonoTouch

As far as I can see, Delphi Prism can be used to create iPhone apps, today, compiled to native ARM CPU machine code.

Wednesday 24 June 2009

Delphi is fast, very fast

Jesper Hald and others recently did a benchmark of a certain algorithm to figure out, which was fastest. It evolved into a kind of competition to make the fastest algorithm to solve this problem:

Fill a data structure with 1,000,000 random, unsorted values from 0-100
Run through this data structure 200 times, counting
a) number of values = 42
b) average for all 1,000,000*200 values

The benchmark was run on a new T5800 (2GHz, 800MHz) Toshiba laptop running 32-bit Windows Vista. Nothing had been done to the Vista to make it faster or behave in a special way.

The results were interesting, and our conclusions were:

* The fastest algorithm was made in Delphi (2009 version), was reasonably easy to read, and achieved to count the 42-values amongst 200,000,000 values in 55ms. That's pretty fast: 0.275 nanoseconds per value or about 0.5 clock cycles per value.

* Some of the first attempts in C# were 30-50 times slower than the winner.

* C# and Java were generally about 1.5 times slower than Delphi. Using normal generic lists in C# would make it 13 times slower than a simple Delphi implementation with static arrays. Is this comparison fair? Well, that's how the first results were made by the various programmers.

* Using unsafe code in C# seemed obvious, but actually wasn't necessary. It was possible to make it approximately as fast in C# without going unsafe.

* Delphi was approximately same speed inside and outside the IDE, whereas C# was almost 4-5 times slower when running the code inside the IDE.

* PHP was 1000-2500 times slower than Delphi.

* We gave up BASH scripting because it took too long time to fill the array with values (!)

Please do not generalize from this example, because there are many other things in this world than counting and averaging integer values. I'm not saying that Delphi is faster than C# or Java, and always remember, that a performance ratio of less than 2 in a specific algorithm is too little to make a difference in a large application.

Friday 19 June 2009

Floating point values are evil

If you want to do comparisons like > < =, always consider to convert your floating point numbers to integers and store them in integer variables. The basic problem is, that simple values like 0.1 do not exist in the double type.

There are several solutions:

* Instead of showing currencies as the number of dollars, where 0.20 is 20 cents, consider to register the number of cents, instead, so that $0.20 is stored as the integer value 20.

* Instead of comparing timestamps like "if a<b+1/86400", consider to use a time registration in seconds, like on Linux, or even milliseconds.

* Instead of storing fractions like factor:=height/width, keep storing the height and width in two integer values. If you want to calculate value:=factor*xposition, then you can convert this into an integer calculation like value:=height*xposition div width.

* Instead of adding fractions up to get an integer value, like fraction:=height/width and then "for x:=1 to width do y:=y+fraction", keep it as fractions in two integer variables: "for x:=1 to width do begin ynominator:=ynominator+height; y:=ynominator div width; end;".

The benefits are, that your code is much more deterministic, debuggable, explainable. However, it does not always make sense, sometimes it gets more difficult to read, so that's why I kept using the word "consider".

Sunday 14 June 2009

Do not set TStringList.sorted:=True with default comparison

Now, this is a nasty bug, especially for Delphi 2009, reported by an anonymous user in my other blog post here:

The Windows API CompareStr() in Windows XP SP3, using Danish locale (and probably most others), thinks that 59A < 59-A < -59-A < 5-9A < 59-A.

TStringList.Find uses binary lookups on a sorted list of strings, which means that for 1024 items in a TStringList, it does not need to make more than 10 string comparisons in order to find the index of a specific string. This is fast, but it requires the list to be sorted in a deterministic way, and it requires CompareStr() to be able to tell, what direction to go to find the string. On a Danish Windows XP SP3, this code will trigger error #1 and error #3:

var
list1: TStringList;
idx1:Integer;
begin
list1 := TStringList.create;
try
list1.Sorted := True;
list1.Add('59A');
list1.Add('-59-A');
list1.Add('5-9A');
list1.Add('59-A');
if list1.IndexOf('5-9A')=-1 then
ShowMessage ('Error #1: IndexOf does not work.');
if list1.Find('5-9A',idx1) then begin
if list1[idx1]<>'5-9A' then
ShowMessage ('Error #2: Find failed and found the wrong string.');
end else
ShowMessage ('Error #3: Find failed because it did not find the string which is present.');
finally
FreeAndNil (list1);
end;
end;


Error #1 is triggered, because .IndexOf uses .Find on sorted lists in Delphi 2009. CodeGear cannot fix this problem easily, because it's actually a bug in Windows. This bug is worse for Delphi 2009, because it optimizes the .IndexOf function by using .Find, for sorted strings.

You can make Windows Explorer demonstrate the same problem. Create 4 files like this:

Date             Size  Name  
14-06-2009 09:00 54 -59-A
14-06-2009 08:58 0 5-9A
14-06-2009 09:01 4 59-A
14-06-2009 09:02 168 59A


Then, show the folder using Windows explorer in Detailed view, and do this:

* Click the column Size, to sort by size
* Click the column Name, to sort by name.
* You can now see that the order is 5-9A, 59A, 59-A, -59-A
* Click the change date column, to sort by change date
* Click the column Name, to sort by name
* The order is now 59A, 59-A, -59-A, 5-9A

I didn't try this on 32-bit Vista or 64-Vista, yet.

Wednesday 10 June 2009

The performance of the "as" operator

Some people wonder, if the "as" operator is slow or not. A programmer well known in the Danish Delphi community, Thomas Vedel, has made some investigations into this, using Delphi 2009, and I have got his permission to publish them.

The blue instructions below are those that are only executed if the type does not match, and they are therefore not relevant for normal execution. The conclusion seems to be, that the "as" operator does not slow down your application in any amount that is worth spending time on. So keep using the "as" operator!

Example 1

Main1.pas.28: TButton(Sender).Caption := 'Klik her!';
00464808 8BC2 mov eax,edx
0046480A BA24484600 mov edx,$00464824
0046480F E8E0CFFDFF call TControl.SetText


----------

Example 2

Main1.pas.28: (Sender as TButton).Caption := 'Klik her!';
0046480B 8BC3 mov eax,ebx
0046480D 8B15CC014300 mov edx,[$004301cc]
00464813 E8A0F4F9FF call @AsClass
00464818 BA30484600 mov edx,$00464830
0046481D E8D2CFFDFF call TControl.SetText

@AsClass:
00403CB8 85C0 test eax,eax
00403CBA 7416 jz $00403cd2
00403CBC 89C1 mov ecx,eax
00403CBE 8B09 mov ecx,[ecx]
00403CC0 39D1 cmp ecx,edx
00403CC2 740E jz $00403cd2
00403CC4 8B49D0 mov ecx,[ecx-$30]
00403CC7 85C9 test ecx,ecx
00403CC9 75F3 jnz $00403cbe
00403CCB B00A mov al,$0a
00403CCD E93EF4FFFF jmp Error

00403CD2 C3 ret


----------

Example 3

Main1.pas.28: if (Sender is TButton) then
0046480B 8BC3 mov eax,ebx
0046480D 8B15CC014300 mov edx,[$004301cc]
00464813 E87CF4F9FF call @IsClass
00464818 84C0 test al,al
0046481A 740C jz $00464828

Main1.pas.29: TButton(Sender).Caption := 'Klik her!';
0046481C BA38484600 mov edx,$00464838
00464821 8BC3 mov eax,ebx
00464823 E8CCCFFDFF call TControl.SetText

@IsClass:
00403C94 53 push ebx
00403C95 56 push esi
00403C96 8BF2 mov esi,edx
00403C98 8BD8 mov ebx,eax
00403C9A 85DB test ebx,ebx
00403C9C 740D jz $00403cab
00403C9E 8BD6 mov edx,esi
00403CA0 8B03 mov eax,[ebx]
00403CA2 E875000000 call TObject.InheritsFrom
00403CA7 84C0 test al,al
00403CA9 7505 jnz $00403cb0
00403CAB 33C0 xor eax,eax
00403CAD 5E pop esi
00403CAE 5B pop ebx
00403CAF C3 ret

00403CB0 B001 mov al,$01
00403CB2 5E pop esi
00403CB3 5B pop ebx
00403CB4 C3 ret