IntToStr / StrToInt Perf issues VS Delphi? lazarus / 64-FPC development ---------------------------------------------------------------------- dieselfan · 2015-08-13 10:50 So I have this simple code with 3 tests 1. ******   for x := 0 to StrToInt(edit1.Text) do begin     s := IntToStr(x);   end; ****** 2. ******   lMax := StrToInt(edit1.Text);   for x := 0 to lMax do begin     s := IntToStr(x);   end;  ****** 3. ******   lMax := StrToInt(edit1.Text);   repeat     Inc(x)   until x = lMax;  ****** Then I record time taken using GetTickCount. I compiled with D7, XE2, XE7 and (FPC  2.6.4, Laz 1.4.2 - Fedora 22). The times for the EXE's under Win10 and Wine on Fedora 22 were negligible. The FPC was run native on Fedora. In the order (D7, XE2, XE7, FPC + Laz) in seconds. 1)  10 000 000 loops 1.161, 0.287, 0.298, 1.424 2) 10 000 000 loops 1.464, 0.326, 0.315, 1.449 3) 1 000 000 000 loops 0.54, 0.539, 0.541, 1.865 Then I increased the loops for  code 1, 2 by one "0" 1) 100 000 000 loops 16.4, 2.81, 2.873, 15.007 The importance here is at the basic variable level ie string and integers. FPC appears to be significantly slower. When dealing with larger datasets and database access ie we have to convert EACH DB datatype to display in a grid or export as CSV, XML etc. as well as basic integer FOR and REPEAT loops. If the basic "low" level variables are upto 5x slower the resulting complex app should be too. Keep in mind a table might not have 100 000 000 records but you could deal with that amount of strings, when your tables have quite a few fields etc. and longer strings not just couple chars. As you can see it appears as if D7 and FPC are similar in string handling but with straight integer loops D7 - > XE7 are almost 3x faster and XE2+ almost 5x faster with string conversion. I can post the whole code if need be and DFM etc. Anyways I'd appreciate some feedback / comments. Specs are i7, 16GB 2000Mhz. Fedora 22 64bit OS. Windows 7/10 and Wine. ---------------------------------------------------------------------- eny · 2015-08-13 11:06 Is there any underlying problem? Quote from: dieselfan on August 13, 2015, 12:50:29 pmI can post the whole code if need be and DFM Please do so (the Lazarus code that is) so we can verify the results. Maybe it's just a compiler setting... ---------------------------------------------------------------------- marcov · 2015-08-13 11:10 Your conclusions are based on weak benchmarks. The problem is that your examples are very simple, and you don't specify any settings that you use (optimization etc). Also comparison with 3.x -O4 would be nice. Sometimes compilers might detect that the result of "s" is never used and simply omit the whole inttostr step, or replace the whole repeat- until with "x=lmax". That's why scaling such microbenchmarks to "real" performance is relatively useless for real world performance where hopefully the amount of code that does nothing is low. Benchmarking and interpreting the results is an art, and it actually quite hard to get meaningful results. I really would like to see an indepth Delphi vs FPC (with both several versions) benchmark, but I'm afraid that would be a truckload of work. ---------------------------------------------------------------------- dieselfan · 2015-08-13 11:14 same code as Delphi ********** procedure TForm1.Button1Click(Sender: TObject); var x: integer;   s: string;   lT: cardinal; begin   lT := GetTickCount;   for x := 0 to StrToInt(edit1.Text) do begin     s := IntToStr(x);   end;   lT := GetTickCount - lT;   label1.Caption := Format('%s, It took %.3f secs', [s, lT / 1000]);   memo1.Lines.Add(label1.Caption); end; procedure TForm1.Button2Click(Sender: TObject); var x, lMax: integer;   s: string;   lT: cardinal; begin   lT := GetTickCount;   lMax := StrToInt(edit1.Text);   for x := 0 to lMax do begin     s := IntToStr(x);   end;   lT := GetTickCount - lT;   label1.Caption := Format('%s, It took %.3f secs', [s, lT / 1000]);   memo1.Lines.Add(label1.Caption); end; procedure TForm1.Button4Click(Sender: TObject); var x, lMax: integer;   s: string;   lT: cardinal; begin   lT := GetTickCount;   x := 0;   lMax := StrToInt(edit1.Text);   s := edit1.text;   repeat     Inc(x)   until x = lMax;   lT := GetTickCount - lT;   label1.Caption := Format('%s, It took %.3f secs', [s, lT / 1000]);   memo1.Lines.Add(label1.Caption); end; ********** It's a proof of concept problem. I consult to a firm using Delphi with some pretty big DB's. I am investigating Laz, FPC as an option. The above code while not optimal, demonstrates how I would think many people code. The problem if sorted out now will prevent an issue later. 3-5x slower is a hard sell. I fully appreciate the work done here and on FPC, but hopefully it will be seen as constructive feedback or perhaps a compiler option missing . ---------------------------------------------------------------------- dieselfan · 2015-08-13 11:16 Quote from: marcov on August 13, 2015, 01:10:01 pmYour conclusions are based on weak benchmarks. The problem is that your examples are very simple, and you don't specify any settings that you use (optimization etc). Also comparison with 3.x -O4 would be nice. Sometimes compilers might detect that the result of "s" is never used and simply omit the whole inttostr step, or replace the whole repeat- until with "x=lmax". That's why scaling such microbenchmarks to "real" performance is relatively useless for real world performance where hopefully the amount of code that does nothing is low. Benchmarking and interpreting the results is an art, and it actually quite hard to get meaningful results. I really would like to see an indepth Delphi vs FPC (with both several versions) benchmark, but I'm afraid that would be a truckload of work. My experience with FPC and Laz is limited. I would welcome suggestions. "Sometimes compilers might detect that the result of "s" is never used and simply omit the whole inttostr step, or replace the whole repeat- until with "x=lmax"." I agree, compilers *might* but it still showed consistent results. FWIW, I ran each at default ide settings ---------------------------------------------------------------------- Handoko · 2015-08-13 11:43 Interesting to know that FPC/Lazarus is slower than Delphi/XE.. Can you please share the Lazarus file rather than the code? There are many configurable settings can be adjusted to make the result code run faster. The default Lazarus settings are not optimize for speed. ---------------------------------------------------------------------- eny · 2015-08-13 11:52 More fruit: your three scenario's for 100.000.000: 100000000, It took 5,985 secs 100000000, It took 5,921 secs 100000000, It took 0,031 secs Quote from: dieselfan on August 13, 2015, 01:14:32 pmIt's a proof of concept problem. I consult to a firm using Delphi with some pretty big DB'sDB access and data handling/processing are (almost) always the bottleneck. Not the way data is handled internally by Lazarus/FPC. ---------------------------------------------------------------------- dieselfan · 2015-08-13 12:50 Quote from: eny on August 13, 2015, 01:52:23 pmMore fruit: your three scenario's for 100.000.000: 100000000, It took 5,985 secs 100000000, It took 5,921 secs 100 000 000, It took 0,031 secs Quote from: dieselfan on August 13, 2015, 01:14:32 pmIt's a proof of concept problem. I consult to a firm using Delphi with some pretty big DB'sDB access and data handling/processing are (almost) always the bottleneck. Not the way data is handled internally by Lazarus/FPC. Can you run those on Delphi? What specs / versions you using for above results? If #3 was that quick then your compiler is def doing a "lMax =" Vs actually running the loop. You could fool it with a "dosomething;" Put it this way. I wrote a DB migration tool for them. Using IBPump it takes around 19 hours+. Using my code in Delphi it takes 17Mins. I make extensive use of conversions and loops. As it's for HiCare clients they can't afford to be without their DB for long. Obviously my code is more complex but the IO overhead is actually minscule compared. A large percentage is spent converting. ---------------------------------------------------------------------- dieselfan · 2015-08-13 12:59 Quote from: eny on August 13, 2015, 01:52:23 pmMore fruit: your three scenario's for 100.000.000: 100000000, It took 5,985 secs 100000000, It took 5,921 secs 100000000, It took 0,031 secs Quote from: dieselfan on August 13, 2015, 01:14:32 pmIt's a proof of concept problem. I consult to a firm using Delphi with some pretty big DB'sDB access and data handling/processing are (almost) always the bottleneck. Not the way data is handled internally by Lazarus/FPC. You can also see vs the original post if you take 5.985, 5.921. You can reasonably expect D7 to be 5.3 and XE2 > around 2 seconds. Which irrespective of DB overhead etc is still 1/3. Can you confirm in any Delphi V? ---------------------------------------------------------------------- marcov · 2015-08-13 13:01 Quote from: dieselfan on August 13, 2015, 01:16:54 pm FWIW, I ran each at default ide settings TNWM. (That's Not Worth Much) ---------------------------------------------------------------------- dieselfan · 2015-08-13 13:11 Code...then for any delphi versions it's literally a copy and paste...and comment out the one uses clause for GetTickCount. ---------------------------------------------------------------------- eny · 2015-08-13 13:14 Quote from: dieselfan on August 13, 2015, 02:50:16 pmCan you run those on Delphi? What specs / versions you using for above results?Sorry I don't have Delphi. QuoteIf #3 was that quick then your compiler is def doing a "lMax =" Vs actually running the loop. You could fool it with a "dosomething;"The point is that these 'benchmarks' are not meaningful in any way. If I were to 'dosomething' it becomes a different 'test'. And no, the compiler did not skip the loop. ** edit ** See pic. Of course no diff with previous results, except that I enabled the optimization flag in my previous tests. Apparently these optimizations are enabled by default in the Delphi derivates. Second pic is with optimization level O3. ---------------------------------------------------------------------- rvk · 2015-08-13 13:59 Okay, I have a question. When I trace in trunk into the IntToStr-function I end up in the int_str() of fpc\rtl\inc\generics.inc. It has this around the function. Code: [Select]{$ifndef FPC_SYSTEM_HAS_INT_STR_LONGINT} procedure int_str(l:longint;out s:shortstring); ... {$endif} The function itself is in pure pascal and NOT assembler. If I look in fpc\rtl\i386\i386.inc it HAS an assembler function for int_str(). Why is that one not used???? Code: [Select]{$define FPC_SYSTEM_HAS_INT_STR_LONGWORD} {$define FPC_SYSTEM_HAS_INT_STR_LONGINT} ... procedure int_str(l:longword;out s:string);assembler;nostackframe; When using this in your own code you can see by the grayed out portion the FPC_SYSTEM_HAS_INT_STR_LONGINT is not defined but the cpui386 IS defined. I don't think that's quite correct. Shouldn't the i386.inc be compiled on a Windows PC ??? Code: [Select]  for x := 0 to lMax do begin     {$ifdef FPC_SYSTEM_HAS_INT_STR_LONGINT}     s := IntToStr(x);     {$endif}     {$ifdef cpui386}     s := IntToStr(x);     {$endif}   end; As it stands now the IntToStr() is pure pascal from the fpc\rtl\inc\generics.inc and can never reach the speed of pure assembler (like in Delphi). B.T.W. This is also in Lazarus 1.4.2 FPC 2.6.4. ---------------------------------------------------------------------- derek.john.evans · 2015-08-13 14:09 I don't understand this bench marking topic. Anyone that wants to convert 100,000,000 Integers to Strings or Strings to Integers would not be using StrToInt() and IntToStr(). And inside the context of database read/writes, that conversion would amount to a small fraction of the overall process. ie: Where are these numbers coming from or going to? IMO, the only way this topic makes sence is if you had a large dataset which you wanted to convert to a flat binary file or memory for faster lookups. And even then, the conversion would only be a small part of the process. ---------------------------------------------------------------------- rvk · 2015-08-13 14:15 Quote from: derek.john.evans on August 13, 2015, 04:09:48 pmIMO, the only way this topic makes sence is if you had a large dataset which you wanted to convert to a flat binary file or memory for faster lookups. And even then, the conversion would only be a small part of the process. Okay... let's go with a conversion-scenario. You would be correct, it's only a small part... But either way... If Lazarus/FPC is not using the assembler function for int_str (in i386.inc) while it is in code, it's a major design-flow in FPC itself. So that would need to be fixed, regardless. And this test-case just brings it to the surface. Mmm, forcing the assembler (with recompile of FPC) still gives me 38 seconds for (2). ---------------------------------------------------------------------- derek.john.evans · 2015-08-13 14:32 I could be wrong, but on Win32 InToStr() maps to System.Str(). Code: [Select]function IntToStr(Value: integer): string; begin  System.Str(Value, result); end ; StrToInt maps to Val(). Code: [Select]function StrToInt(const S: string): integer; var Error: word; begin   Val(S, result, Error);   if Error <> 0 then raise EConvertError.createfmt(SInvalidInteger,[S]); end ;  Thus, a speed up can be achieved just by bypassing StrToInt/IntToStr. ---------------------------------------------------------------------- rvk · 2015-08-13 14:49 Quote from: derek.john.evans on August 13, 2015, 04:32:03 pmI could be wrong, but on Win32 InToStr() maps to System.Str(). Code: [Select]function IntToStr(Value: integer): string; begin  System.Str(Value, result); end ; And System.Str() is dependent on either the int_str() in generic.inc in pure pascal (for cross-platform) or the int_str() in i386.inc which is pure assembler. So my guess would be that the pure assembler would be much better. But FPC also does A LOT (and I mean A LOT) of ShortToAnsiString and other stuff with a simple IntToStr() while Delphi just has one pure assembler function. So you're correct... if speed is of importance the best thing is to create a new IntToStr() in pure pascal and use that. ---------------------------------------------------------------------- Handoko · 2015-08-13 15:08 Quote from: derek.john.evans on August 13, 2015, 04:32:03 pmI could be wrong, but on Win32 InToStr() maps to System.Str(). Code: [Select]function IntToStr(Value: integer): string; begin  System.Str(Value, result); end ; That's right. On my Lazarus 64-bit Linux, it also maps to System.Str(). Tested the perfomance gain by bypassing StrToInt/IntToStr, unfortunately the gain is very small. Original: 10000000, It took 2.956 secs Using System.Str() directly: 10000000, It took 2.919 secs Performance gain: = (2.956 - 2.919) / 2.956 * 100% = 1.25% I have to admit, FPC/Lazarus is not well optimized compare to Delphi. That's understandable, Delphi is commercially/financially supported, it's expensive (for me). FPC/Lazarus is cross-platform supported by volunteers, the developers spend more time to make it cross-platform rather than optimizing it. ---------------------------------------------------------------------- rvk · 2015-08-13 15:28 Quote from: Handoko on August 13, 2015, 05:08:26 pmThat's right. On my Lazarus 64-bit Linux, it also maps to System.Str(). On all systems and platforms it maps to System.Str(). It's what that function looks like that's important. You'll notice you can't trace through it on Laz1.4 (production version) but if you have trunk you can trace through all the code (F7) and you'll see it ending up in a int_str() function which is platform dependent (or in generics.inc if there is no platform dependent int_str()). (There are only 2 implementations. The pure pascal in generics.inc and the assembler one in i386.inc) So the speed difference you mention with System.Str() doesn't say a lot (it's just one extra less call per iteration). You should replace System.Str() with a real assembler function (like in Delphi). Then you'll see a real difference. The IntToStr() in Delphi is in SysUtils.pas (at least it was in XE) and is pure assembler. I was hopeful when forcing FPC to use the int_str() assembler version in i386.inc but because there is a lot of extra overhead with string- conversion and some other extra functions there was no real speed improvement. Just like you said... FPC is more optimized for cross- platform than for speed.