Recent

Author Topic: Is it possible to pass an intrinsic function as a parameter?  (Read 467 times)

glorfin

  • Jr. Member
  • **
  • Posts: 61
If I define
  TConversionFunction = function(X:integer):integer;

and
  MyProc(V:TIntegerArray; Func:TConversionFunction);

then I can pass any function I write.

However, if I pass an intrinsic, say, abs, I get an error:
Error: Incompatible type for arg no. 2: Got "<address of function(LongInt):LongInt;InternProc>", expected "<procedure variable type of function(LongInt):LongInt;Register>"

So, is there a way to pass such a function?

Thank you in advance!

lucamar

  • Hero Member
  • *****
  • Posts: 1986
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #1 on: July 26, 2019, 01:48:14 am »
AFAIK not directly, but you can pass a function that itself just calls the intrinsic:
Code: Pascal  [Select]
  1. function DummyAbs(X: Integer): Integer;
  2. begin
  3.   Result := Abs(X);
  4. end;
  5.  
  6. begin
  7.   AnInt := MyProc(AnInt, @DummyAbs);
  8. end.

Not the ideal soultion, I know. :(
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.2/2.0.4  - FPC 3.0.4 on:
(K|L)Ubuntu 12..16, Windows XP SP3, various DOSes.

glorfin

  • Jr. Member
  • **
  • Posts: 61
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #2 on: July 26, 2019, 01:53:15 am »
Thank you! Yes, I thought about it, but of course it will be quite slow.

I am jealous to Fortran 90 programmers with direct operations on arrays, and want to have something like Apply. But then efficiency is an issue.

lucamar

  • Hero Member
  • *****
  • Posts: 1986
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #3 on: July 26, 2019, 02:27:50 am »
Thank you! Yes, I thought about it, but of course it will be quite slow.

I am jealous to Fortran 90 programmers with direct operations on arrays, and want to have something like Apply. But then efficiency is an issue.

It won't slow you too much; after all it's just one level of indirection. It may (or not) be sped up by using:
Code: Pascal  [Select]
  1. function DummyAbs(constref X: Integer): Integer; inline;
  2. begin
  3.   Result := Abs(X);
  4. end;
If the inline works (which isn't guaranteed) the indirection will dissapear and a constref might be passed in a register, which is usually quicker than pushing it to the stack.

It depends on a lot of things (not the less on whether I'm correct ;) ); you can but try various ways  of implementing it and keep the fastest.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.2/2.0.4  - FPC 3.0.4 on:
(K|L)Ubuntu 12..16, Windows XP SP3, various DOSes.

glorfin

  • Jr. Member
  • **
  • Posts: 61
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #4 on: July 26, 2019, 02:39:31 am »
Of course, another solution is to keep generic Apply function for user-supplied functions, and write for intrinsics specific procedures, something like

  procedure AbsArray(V:TFloatArray);

and call intrinsic directly.

I will look for possibilities tomorrow.

lucamar

  • Hero Member
  • *****
  • Posts: 1986
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #5 on: July 26, 2019, 02:48:57 am »
Of course, another solution is to keep generic Apply function for user-supplied functions, and write for intrinsics specific procedures, something like [...snip...]

I will look for possibilities tomorrow.

Good luck!
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.2/2.0.4  - FPC 3.0.4 on:
(K|L)Ubuntu 12..16, Windows XP SP3, various DOSes.

jamie

  • Hero Member
  • *****
  • Posts: 1899
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #6 on: July 26, 2019, 03:22:49 am »
There is a such thing as open arrays and overloads. the compiler handles them well.
 

glorfin

  • Jr. Member
  • **
  • Posts: 61
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #7 on: July 27, 2019, 03:22:15 am »
Finally, it looks like intrinsic functions are implemented in a special way and often inlined, so, approach of lucamar is slow.
Indeed, following program
Code: Pascal  [Select]
  1. program testcalls;
  2. {$mode objfpc}{$H+}{$inline on}
  3. uses SysUtils, DateUtils, Math;
  4. type
  5.   Float = single;
  6.   TVector = array of Float;
  7.   TTransformFunc = function(X:float):float;
  8.  
  9.   function Seq(Lb, Ub : integer; first, increment:Float; Vector:TVector = nil):TVector;
  10.   var
  11.     I:integer;
  12.   begin
  13.     if Vector = nil then
  14.       SetLength(Vector, Ub+1)
  15.     else
  16.       Ub := Max(Ub,High(Vector));
  17.     if Lb <= Ub then
  18.       Vector[Lb] := 0
  19.     else
  20.       Exit;
  21.     for I := Lb+1 to Ub do                 // 2 cycles to avoid rounding error if
  22.       Vector[I] := Vector[I-1]+increment;  //First is very large and increment small
  23.     for I := Lb to Ub do
  24.       Vector[I] := Vector[I]+first;
  25.     Result := Vector;
  26.   end;
  27.  
  28. procedure Apply(V: TVector; Lb, Ub: integer; Func: TTransformFunc);
  29. var
  30.   I:integer;
  31. begin
  32.   Ub := max(High(V),Ub);
  33.   for I := Lb to Ub do
  34.     V[I] := Func(V[I]);
  35. end;
  36.  
  37. function EAbs(X:float):float; inline;
  38. begin
  39.   result := abs(X);
  40. end;
  41. procedure AbsArray(V:TVector; Lb, Ub: integer);
  42. var
  43.   I:integer;
  44. begin
  45.   Ub := max(High(V),Ub);
  46.   for I := Lb to Ub do
  47.     V[I] := abs(V[I]);
  48. end;
  49.  
  50. var
  51.   Vec:TVector; // TVector = array of Float; Float = double
  52.   I:integer;
  53.   time1,time2:tdatetime;
  54. begin
  55.   Vec := Seq(0,256000,-3000,2); // creates array with maximal index 128000
  56.   time1 := time;                // and initializes with sequence -3000, -2998 etc
  57.   for I := 0 to 20 do
  58.     AbsArray(Vec,0,High(Vec));
  59.   time2 := time;
  60.   writeln('it takes ',inttostr(millisecondsbetween(time2, time1)), ' ms. for direct call of Abs.');
  61.   time1 := time;
  62.   for I := 0 to 20 do
  63.     Apply(Vec,0,High(Vec),@EAbs);
  64.   time2 := time;
  65.   writeln('it takes ',inttostr(millisecondsbetween(time2, time1)), ' ms. Apply of indirect call.');
  66.   readln;
  67. end.
  68.  

prints:

it takes 6 ms. for direct call of Abs.
it takes 21 ms. Apply of indirect call.

If Float = double thendifference is much bigger:

it takes 7 ms. for direct call of Abs.
it takes 63 ms. Apply of indirect call.

and if Float = extended then

it takes 20 ms. for direct call of Abs.
it takes 81 ms. Apply of indirect call.

So, it looks like Apply for intrunsic functions is not a good idea.

lucamar

  • Hero Member
  • *****
  • Posts: 1986
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #8 on: July 27, 2019, 04:11:09 am »
Woah! That's indeed a huge difference.

I'd not have expected that but as they say: "seeing is believing". The ABS results are specially damning.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 2.0.2/2.0.4  - FPC 3.0.4 on:
(K|L)Ubuntu 12..16, Windows XP SP3, various DOSes.

PascalDragon

  • Hero Member
  • *****
  • Posts: 571
  • Compiler Developer
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #9 on: July 27, 2019, 10:26:29 am »
Well, there is an alternative: generics. The following example requires 3.2 or newer, but the code could be reworked to work in older versions as well (by using types instead of functions). It's the example from glorfin with my code added above the program's beginend. block.

Code: Pascal  [Select]
  1. program ttestcalls;
  2. {$mode objfpc}{$H+}{$inline on}
  3. {$modeswitch advancedrecords}
  4. uses SysUtils, DateUtils, Math;
  5. type
  6.   Float = single;
  7.   TVector = array of Float;
  8.   TTransformFunc = function(X:float):float;
  9.  
  10.   function Seq(Lb, Ub : integer; first, increment:Float; Vector:TVector = nil):TVector;
  11.   var
  12.     I:integer;
  13.   begin
  14.     if Vector = nil then
  15.       SetLength(Vector, Ub+1)
  16.     else
  17.       Ub := Max(Ub,High(Vector));
  18.     if Lb <= Ub then
  19.       Vector[Lb] := 0
  20.     else
  21.       Exit;
  22.     for I := Lb+1 to Ub do                 // 2 cycles to avoid rounding error if
  23.       Vector[I] := Vector[I-1]+increment;  //First is very large and increment small
  24.     for I := Lb to Ub do
  25.       Vector[I] := Vector[I]+first;
  26.     Result := Vector;
  27.   end;
  28.  
  29. procedure Apply(V: TVector; Lb, Ub: integer; Func: TTransformFunc);
  30. var
  31.   I:integer;
  32. begin
  33.   Ub := max(High(V),Ub);
  34.   for I := Lb to Ub do
  35.     V[I] := Func(V[I]);
  36. end;
  37.  
  38. function EAbs(X:float):float; inline;
  39. begin
  40.   result := abs(X);
  41. end;
  42. procedure AbsArray(V:TVector; Lb, Ub: integer);
  43. var
  44.   I:integer;
  45. begin
  46.   Ub := max(High(V),Ub);
  47.   for I := Lb to Ub do
  48.     V[I] := abs(V[I]);
  49. end;
  50.  
  51. generic procedure ApplyFunctor<T>(V: TVector; Lb, Ub: Integer);
  52. var
  53.   i: Integer;
  54. begin
  55.   Ub := max(High(v), Ub);
  56.   for i := Lb to Ub do
  57.     V[i] := T.Call(V[i]);
  58. end;
  59.  
  60. type
  61.   TAbsFunctor = record
  62.     class function Call(aValue: Float): Float; static; inline;
  63.   end;
  64.  
  65. class function TAbsFunctor.Call(aValue: Float): Float;
  66. begin
  67.   Result := Abs(aValue);
  68. end;
  69.  
  70. var
  71.   Vec:TVector; // TVector = array of Float; Float = double
  72.   I:integer;
  73.   time1,time2:tdatetime;
  74. begin
  75.   Vec := Seq(0,256000,-3000,2); // creates array with maximal index 128000
  76.   time1 := time;                // and initializes with sequence -3000, -2998 etc
  77.   for I := 0 to 20 do
  78.     AbsArray(Vec,0,High(Vec));
  79.   time2 := time;
  80.   writeln('it takes ',inttostr(millisecondsbetween(time2, time1)), ' ms. for direct call of Abs.');
  81.   time1 := time;
  82.   for I := 0 to 20 do
  83.     Apply(Vec,0,High(Vec),@EAbs);
  84.   time2 := time;
  85.   writeln('it takes ',inttostr(millisecondsbetween(time2, time1)), ' ms. Apply of indirect call.');
  86.   time1 := time;
  87.   for i := 0 to 20 do
  88.     specialize ApplyFunctor<TAbsFunctor>(Vec,0,High(Vec));
  89.   time2 := time;
  90.   writeln('it takes ',inttostr(millisecondsbetween(time2, time1)), ' ms. Apply of functor.');
  91.   readln;
  92. end.

The output then is this:
Code: [Select]
it takes 10 ms. for direct call of Abs.
it takes 22 ms. Apply of indirect call.
it takes 10 ms. Apply of functor.
Note: the first and third time varies between 10 and 12 ms for both entries.

And the generated assembly for the call to T.Call in ApplyFunctor<TAbsFunctor> is this:
Code: [Select]
# [57] V[i] := T.Call(V[i]);
movq -8(%rbp),%rax
movslq -28(%rbp),%rdx
leaq (%rax,%rdx,4),%rax
movss (%rax),%xmm0
andps FPC_ABSMASK_SINGLE(%rip),%xmm0
movq -8(%rbp),%rdx
movslq -28(%rbp),%rax
movss %xmm0,(%rdx,%rax,4)
cmpl -28(%rbp),%ecx
jle .Lj90
jmp .Lj88
Also please note that this is not Delphi compatible as their generic implementation is less like the C++ one and more like the C# one (it will work in mode Delphi however ;) ).

Thaddy

  • Hero Member
  • *****
  • Posts: 8679
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #10 on: July 27, 2019, 11:35:04 am »
I have this as an alternative abs{()
Code: Pascal  [Select]
  1. {$mode objfpc}
  2. function abs2(const v:integer):integer;inline;
  3. begin
  4.   Result := v * -integer(v<0); // computes sign
  5. end;
  6.  
  7. begin
  8.   writeln(abs2(-2));
  9. end.
« Last Edit: July 27, 2019, 11:50:43 am by Thaddy »
Most people that want to use threading should learn to patch their jeans first: use a needle.

LemonParty

  • New Member
  • *
  • Posts: 28
Re: Is it possible to pass an intrinsic function as a parameter?
« Reply #11 on: July 27, 2019, 12:02:26 pm »
This is a professional https://github.com/mikerabat/mrmath method about gaining the real performance.
Nor generics, nor instrincs, nor even the best compilers can't do the high quality vectorization today.