Jump to content
Mahdi Safsafi

Typed constants in Delphi.

Recommended Posts

4 hours ago, Mahdi Safsafi said:

The code showed clearly that they do on x64!!! 

Only for a totally unrepresentative example, and only because of implementation defects in that compiler. Not because there is anything conceptual. 

 

Feel free to choose between true and typed constants because of a few milliseconds difference in compilation time. If that means something to you, great. Knock yourself out. For me I will stick to what results in readable and maintainable code, and code that runs most efficiently. 

Share this post


Link to post
Guest
48 minutes ago, David Heffernan said:

Only for a totally unrepresentative example, and only because of implementation defects in that compiler. Not because there is anything conceptual. 

You are wrong here.

 

Does this code show a defected compiler?

procedure Test;
const
  untypedC        = 1234567;
  typedC: Integer = 1234567;
var
  I, J: Integer;
  K, L: Int64;
begin
  I := untypedC;
  J := typedC;
  K:=untypedC;
  L:=typedC;
  if I = J then
    Exit;
  if K = L then
    Exit;
end;

Assembly is like this

Test1.thumb.png.cf184cd73eab08e78c365bf88db49332.png

 

No, the compiler here is generating right code, can be better yes, but that is not the point, the example you need to see is that for typed constants, there is extra assembly instruction(s), generating 2 asm instruction can't and will not have the same speed of generating one, hence untyped constant is faster to compile than typed constant.

 

Can you accept this as example ?

 

Share this post


Link to post
Guest

It is more clear with this, after moving both const and var to global from local

Test2.thumb.png.d163dbee240081340b2ae31a22663122.png

 

resolving relative address's will be take more time from the compiler, noticeable or not. 

Share this post


Link to post
38 minutes ago, Kas Ob. said:

Can you accept this as example ?

Now you are talking about the generated code. Which is a different issue. Hitherto there has been a long, and in my view bogus, discussion about compilation speed. The OP said that compilation speed was a reason to prefer avoiding typed constants.

 

The point that I made which seems to have generated such noise is merely that speed of compilation is no reason to prefer true constants over typed constants.

 

EDIT: I didn't read closely enough. You are claiming that the compile time must be longer because more instructions are emitted? Wow, that's weird. Known to be false also. Consider optimisation. Often this results in fewer instructions emitted, post optimisation. But optimisation is an additional step that can increase compile time

 

There are good reasons to prefer true constants over typed constants. Compilation speed isn't one of them. Efficiency of generated code is one. 

 

Frankly I'm ambivalent about what you both think. If you want to change the way you code to save a couple of milliseconds of each compilation, then do it. It doesn't bother me. 

Edited by David Heffernan
  • Like 1

Share this post


Link to post

My most common use of typed constants is records containing translation strings. These are used for in-place translation of errors and prompts in JsonRPC responses, instead of the typical translated resource solution you would use in a desktop application.  This is done to reduce the cost of lookup, since the same server will be required to respond to requests in multiple languages.  It works so well that we've started using the same system for configuring text for grids, etc. - just to simplify the process of adding new content without requiring more translation work in a separate tool.

 

For me, the benefits are that the translations are kept in one place - so it is next to impossible to mistranslate a text, or forget about adding a translation in a specific place when adding a new language and since the texts are in the source code, it is trivial to add new translations.  

 

I have hundreds of these typed constants and if they do add compile time, it must be next to nothing, because I can't say that I notice any slowdown in the compilation.

 

My main gripe is that I cannot pass these as parameters to an attribute.

 

Here is a simplified albeit rather contrived example but it illustrates my point.

program TypedConsts;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.Classes, System.SysUtils, Vcl.Graphics, Generics.Collections;

type
  TxStyle = record
    font: string;
    size: integer;
    attr: TFontStyles;
  end;

type
  TextStyle = record const  // typed consts within a record to create a pseudo namespace
    Title: TxStyle = (
      font: 'Arial';
      size: 12;
      attr: [fsBold]);
    Chapter: TxStyle = (
      font: 'Arial';
      size: 10;
      attr: [fsBold]);
    Section: TxStyle = (
      font: 'Georgia';
      size: 10;
      attr: [fsBold]);
    Normal: TxStyle = (
      font: 'Arial';
      size: 9;
      attr: [fsBold]);
    Code: TxStyle = (
      font: 'Courier New';
      size: 9;
      attr: []);
    Hint: TxStyle = (
      font: 'Garamond';
      size: 8;
      attr: [fsItalic]);
    OhNoes: TxStyle = (
      font: 'Comic Sans MS';
      size: 8;
      attr: [fsItalic]);
  end;

type
  FontAttribute = class(TCustomAttribute)
    Style: TxStyle;
    constructor Create(const aStyle: TxStyle); overload;
    constructor Create(const aFont: string; const aSize: Integer; aAttr: TFontStyles); overload;
  end;

type
  TText = class
  private
    FText: String;
    FStyle: TxStyle;
  published
    property Text: String read FText write FText;
    property Style: TxStyle read FStyle write FStyle;
  end;
  TParagraphs = TObjectList<TText>;

//{$define UseRec}  // Define to use the record format, undefine to use the individual fields
// Regardless of defined or not, this does not compile.

  TDoc = class // would have a method to crawl the type RTTI and initialize the style attributes.
  private
    FTitle: TText;
    FBody: TParagraphs;
  public
{$ifdef UseRec}
    [Font(TextStyle.Title)]
{$else}
    [Font(TextStyle.Title.font, TextStyle.Title.Size, TextStyle.Title.attr)]
{$endif}
    property Title: TText read FTitle write FTitle;
{$ifdef UseRec}
    [Font(TextStyle.Normal)]
{$else}
    [Font(TextStyle.Normal.font, TextStyle.Normal.Size, TextStyle.Normal.attr)]
{$endif}
    property Body: TParagraphs read FBody write FBody;
  end;

{ FontAttribute }

constructor FontAttribute.Create(const aStyle: TxStyle);
begin
  Style := aStyle;
end;

constructor FontAttribute.Create(const aFont: string; const aSize: Integer; aAttr: TFontStyles);
begin
  Style.font := aFont;
  Style.size := aSize;
  Style.attr := aAttr;
end;

begin
  try
    { TODO -oUser -cConsole Main : Insert code here }
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

 

  • Like 1

Share this post


Link to post
Guest

The OP wrote a very good and nice post explaining how compiler works with Constants, and shows deep knowledge and understanding of the compiler and compiling in general.

 

It is you who wrote this.

On 6/8/2020 at 11:40 PM, David Heffernan said:

 

On 6/8/2020 at 11:12 PM, Mahdi Safsafi said:

You don't need to measure the elapsed time

You do. If you can't provide a real world example where compilation time is significantly impacted, then I call BS. 

 

David, calling BS for what is not wrong is wrong, and here OP is right (not wrong but right).

 

31 minutes ago, David Heffernan said:

You are claiming that the compile time must be longer because more instructions are emitted? Wow, that's weird. Known to be false also. Consider optimisation. Often this results in fewer instructions emitted, post optimisation. But optimisation is an additional step that can increase compile time

Right, no one arguing here about optimization (no one mention it doesn't mean we are oblivious about it), for exactly this reason i provided the second one, which faster in compiling than the first one, as all were global instead of local, and specially pointed to the "rel" in 64bit, do you see any optimization needed here? no.

 

You were asking for one example but here you took words out of the context of one example that been requested, and made a claim that we recommend to change your coding style.

 

 

David, no one suspect that you have more knowledge than many ( may be all) us here in many fields, though you can't be knowing everything all the time in every detail, so please prove you point or drop it, don't steer the subject left and right.

And saying "i was wrong" will not make you lesser person or developer, on contrary you will gain more respect instead of losing some by offending person in person without proof, just using you reputation not your knowledge.

 

 

@Mahdi Safsafi Thank you for this thread, and i really hope you will continue posting here such good articles, if you have blog post then please share with us, if not please consider posting threads here, many or some will find it helpful and will appreciate it.

Note: i would love to read some about optimization window size of the compiler conditional jumping in modern CPU's ( how to trigger out-of-order-execution), this will not only help me but might help Delphi guys too, as Delphi compiler have 0 consideration for this part, i researched this subject many times over many years, but genuinely believe you can provide better research info or at least resource's to read.  

Share this post


Link to post
1 hour ago, Kas Ob. said:

David, no one suspect that you have more knowledge than many ( may be all) us here in many fields, though you can't be knowing everything all the time in every detail, so please prove you point or drop it, don't steer the subject left and right.

And saying "i was wrong" will not make you lesser person or developer, on contrary you will gain more respect instead of losing some by offending person in person without proof, just using you reputation not your knowledge.

A few milliseconds during a typical compilation is insignificant. That's my opinion. Others may have a different opinion. They are welcome to it.

  • Like 1

Share this post


Link to post

The biggest issue with constants in Delphi are that typed constants can't be used in certain settings which require true constants to be used. The ones that come to mind are:

 

1. When declaring typed constants. 

2. When declaring attributes. 

 

The inability to used typed constants in these settings is clear weakness in the language. 

 

There are probably more issues, but these are the ones that bite me. 

 

I'm tired of hearing justification for these limitations based on the current implementation. All this talk about single pass, interface vs implementation, writeable typed constants, etc. If the implementation limits you, change it. 

  • Like 2

Share this post


Link to post
Guest
25 minutes ago, David Heffernan said:

The biggest issue with constants in Delphi are that typed constants can't be used in certain settings which require true constants to be used. The ones that come to mind are:

 

1. When declaring typed constants. 

2. When declaring attributes. 

True, and adding the the compiler inconsistency between typed and untyped consistent ( the claim i was trying to prove in other thread), here an example 

procedure Test2;
const
  untypedC         = $7FFFFFFFFFFFFFFF ;         // max positive value for int64 (signed integer)
  typedC   : Int64 = $7FFFFFFFFFFFFFFF ;         // max positive value for int64 (signed integer)
var
  I1, I2, I3, I4, I5, I6, I7, I8, I9: Integer;  // max positive for this type should be $7FFFFFFF
  I64: Int64;
begin
  I1          :=       untypedC;           // compiler will warn here
  I2          := Int64(untypedC);          // compiler will warn here
  Integer(I3) :=       untypedC;           // compiler will warn here
  Integer(I4) := Int64(untypedC);          // compiler will warn here

  I5          :=       typedC;             // compiler is not warning here
  I6          := Int64(typedC);            // compiler is not warning here
  Integer(I7) :=       typedC;             // compiler is not warning here
  Integer(I8) := Int64(typedC);            // compiler is not warning here

  I64 := $1234;
  I9  := I64;
  //Integer(I64) := I9;     // error : Left side cannot be assigned to
  //PInteger(@I64)^ := ShortInt(I9);    // workaround

  if I1 = untypedC then Exit;
  if I2 = untypedC then Exit;
  if I3 = untypedC then Exit;
  if I4 = untypedC then Exit;
  if I5 = typedC then Exit;
  if I6 = typedC then Exit;
  if I7 = typedC then Exit;
  if I8 = typedC then Exit;
  if I9 = $1234 then Exit;          // on 64bit with optimization. if is using I64 as Integer(I64) and (I9  := I64) is skipped above
end;

The in ability to compile "Integer(I64) := I9;" is very annoying, yet i am living without it.

But how warning are generated ( or lack of them), they will bite you when you add RangeCheck only with overflow value, this might skip you in developing and even in testing, and manifest in production.

 

I think this should be in the other thread to continue my fight with Mahdi 😄 , he managed to evade providing direct answer if the Delphi compiler is right or wrong.

about attributes, i hate them, not saying i don't use them, just simple pure hate, due the lack of ability to see them in code when browsing, my brain handle them as comments in the first pass, but on the second pass i get it.

 

Now to this

43 minutes ago, David Heffernan said:

All this talk about single pass

I think that is unfair to ask this, single pass or two, the problem with this term that it is refer (or can be used with different things), it is unclear, so may be you fully understand the difference between a pass inside compiler within the of window generating code for one function, and the pass afterward when building it with different functions, see, you yourself provided an answer to that exactly (different thread)  when you hinted that record forward declaration can be done as class forward declaration, i know what my words means and fully understand it, here in this case we are talking about the declaration and interface, and this can be done in one pass ( the second one ) in the first pass ( the interface building), so you go and list with what you have then expand later, we didn't leave the sector/section or the interface declaration pass itself, right ?

 

Share this post


Link to post
Guest

About the pass's term and definition , i would love to read Mahdi explanation here, of course correcting me if i miss explained it or i don't fully understand it, so Mahdi if you got time please shed some light on that, clearly he got better English, as i got the bad English and Memory.

 

EDIT:

Ops, i forgot the main point if that example and pass, proving i have short living memory, in that example i point that the compiler had skipped this line "I9  := I64;", how could it did that if there wasn't second pass within that function ?!

On other hand a compiler with more than one pass, here i am refereeing to real modern compiler, would have that function skipped in all !, it has 0 input and 0 output, means no effect on any logic of the code and can be ditched in full.

Hope this clear things.

Edited by Guest

Share this post


Link to post

@Kas Ob.

Quote

David, calling BS for what is not wrong is wrong, and here OP is right (not wrong but right).

That made me angry ... He showed a disrespect for me. He didn't appreciate the detailed explanation I gave neither the time I waste nor the experience I have.

First, I said that the reason behind why Delphi's compiler not allowing using complex-type constants (e.g: array, record) as true constants was compile time speed (and I clearly explained why ? as this involves processing the data for each unit that uses the const ) ... He refused to believe and said that the reason because the types are complex ! ... I gave him a day to day example from the D programming language just to prove that the complexity is not a factor ! And yet he answered : That's not a Delphi Style. 

Second, I said that any developer should avoid using typed-ordinal-type-constants(Byte, Integer, ...)  as this will impact optimization and adds an extra overhead at the compilation-time... Yet he strongly believed that's not true ! ... Again, I gave him a good explanation (how linker work) ... He called my explanation BS and asked for tests ! I made a test and showed clearly that at least the x64 compiler was affected ... Yet he said that the test isn't a real-world test and blamed the poor implementation for x64 compiler ... And again I found my self clarifying his wrong beliefs (explaining why it's hard to an x64-toolchain to outperform an x86-toolchain).
Indeed he is experienced in Delphi development, but still has a lot to learn -thinking out of the box- (using other toolchain, knowing how a linker work wan't hurt too, ...) ... this certainly will change his mind about many things and would make him open for argumenting (not because Delphi is doing it in an X way means that Y is wrong). This applies to everyone ... including me (learning is a good way to open your mind)! 

  • Like 3

Share this post


Link to post
Guest
47 minutes ago, Mahdi Safsafi said:

but still has a lot to learn -thinking out of the box- (using other toolchain, knowing how a linker work wan't hurt too, ...)

And i might here add as suggestion to him and few others: to refrain from using the straw man fallacy https://en.wikipedia.org/wiki/Straw_man , many developer in this forum are using this broken logic repeatedly, we are not in enemy or in a contest for likes, right ?

Please show respect, all in all there is no reason go after a person himself (by offending or disrespect) because you think he is wrong, even if he is wrong.

 

 

Share this post


Link to post
57 minutes ago, Mahdi Safsafi said:

I said that any developer should avoid using typed-ordinal-type-constants(Byte, Integer, ...)  as this will impact optimization and adds an extra overhead at the compilation-time

Compilation time is not a valid reason to make that choice. I stand by that. Your own measurements backed up my point of view.

58 minutes ago, Mahdi Safsafi said:

Yet he said that the test isn't a real-world test

It wasn't.

 

58 minutes ago, Mahdi Safsafi said:

explaining why it's hard to an x64-toolchain to outperform an x86-toolchain

I understand that, but there's no good reason why they shouldn't have the same relative characteristics.  So whilst the x64 compiler may well be slower than x86, the x64 compiler should not blow up when presented with huge numbers of typed constants, in a way that the x86 compiler does not.

 

59 minutes ago, Mahdi Safsafi said:

I said that the reason behind why Delphi's compiler not allowing using complex-type constants (e.g: array, record) as true constants was compile time speed

A more plausible explanation to me is simply the history of writeable typed constants.

 

At this point we all know what each other thinks, and there's little point repeating ourselves any more.

Share this post


Link to post
4 minutes ago, Kas Ob. said:

And i might here add as suggestion to him and few others: to refrain from using the straw man fallacy https://en.wikipedia.org/wiki/Straw_man , many developer in this forum are using this broken logic repeatedly, we are not in enemy or in a contest for likes, right ?

Please show respect, all in all there is no reason go after a person himself (by offending or disrespect) because you think he is wrong, even if he is wrong.

 

 

I don't think that anybody here has used a straw man.

 

And only two people have made any statements directed at an individual, rather than concentrating on the arguments.  

Share this post


Link to post
1 hour ago, Mahdi Safsafi said:

He showed a disrespect for me

I didn't mean to show you any disrespect.  I'm sorry that it came across that way.

 

All I meant was that I thought the argument that compilation speed was important was bogus.  I really did not mean to upset you.  I try hard never to make anything I write personal.  To me it's always about the technical details.

  • Like 4

Share this post


Link to post

Overly simple Compilation Speed Test

 

The attached project has a define.

{$define AsTyped}

When defined, the Texts record contains 500 typed constants.

When not defined, the Texts record contains 500 constant strings.

 

On my rather busy laptop, the compile time varies between 0.8 to 1.3s for both with or without the define.

 

 

TypedConsts2.dpr

Share this post


Link to post
Quote

A more plausible explanation to me is simply the history of writeable typed constants.

Adopting the historical reason is in somehow wrong ... because the Pascal-ISO didn't introduce typed-constants neither did the Extended-Pascal-ISO. FYI, I'm studying both ISO for a personnel usage.

//  A definition of constant in Pascal-ISO:

constant-definition = identifier '=' constant .
constant = [ sign ] ( unsigned-number | constant-identifier )
           | character-string .
constant-identifier = identifier .

// A definition of constant in Extended-Pascal-ISO:

constant-definition = identifier '=' constant-expression .
constant-identifier = identifier .
constant-name = [ imported-interface-identifier '.' ] constant-identifier .

// Moreover : extended pascal allowed this :
const
	BlankCard = PunchedCard[1..80: ' '];
	blank = ' ';
	Origin = subpolar[r,theta:0.0];
	column1 = BlankCard[1]; 
	MaxMatrix = 39;
	pi = 4 * arctan(1);
	mister = 'Mr.';

As you can see the Extended-Pascal allowed a more relaxed form of constants ! So saying that the reason is related to historical reason can't be adopted. 

const MyData: array[ 0 .. 1000] of integer = (...);
const element = MyData[1] + 1; // if the compiler allowed this ... it will come at a cost of compilation-speed (I already explained why).

 

Quote

Compilation time is not a valid reason to make that choice. I stand by that.

I didn't say it's a reason ! I said typed-ordinal-constants are volatile and not well optimized moreover they add extra overhead on compilation. They should not never be used. The example you give(Fortran) could be an exception.

Quote

Your own measurements backed up my point of view.

It would if x64 showed a close result to x64.

Quote

So whilst the x64 compiler may well be slower than x86, the x64 compiler should not blow up when presented with huge numbers of typed constants, in a way that the x86 compiler does not.

I believe(I'm not sure) that's related to the OMF. A good way to find that is to check Delphi LLVM based compilers. If all compilers gave a close result to the dcc64 then it would enforce that ! Unfortunately I don't have any Delphi LLVM compiler on my machine. I'd appreciate from you or anyone else to do the check.

Share this post


Link to post
58 minutes ago, Lars Fosdal said:

Overly simple Compilation Speed Test

 

The attached project has a define.


{$define AsTyped}

When defined, the Texts record contains 500 typed constants.

When not defined, the Texts record contains 500 constant strings.

 

On my rather busy laptop, the compile time varies between 0.8 to 1.3s for both with or without the define.

 

 

TypedConsts2.dpr

 
 
 
 
 
 
 
 
 
 
 
 

That was for 32-bit

 

64-bit compiler appears to have a tad longer compile time for typed constants.  
1.2s vs 0.9s for string consts, but the margins are so small and the variation so large, that it is hard to say.

Share this post


Link to post
Guest
18 minutes ago, Lars Fosdal said:

64-bit compiler appears to have a tad longer compile time for typed constants.  
1.2s vs 0.9s for string consts, but the margins are so small and the variation so large, that it is hard to say.

Same here, 

Using XE8 FMX: the time is same for 32bit ( either 0.9 or 1), but on 64bit have 1s for untyped and (1.4-1.5) for typed.

 

@Lars Fosdal  i think your test might work and show better and cleaner result, but need small adjustment.

1) remove the strings from those records to amplify the difference, as handling the strings load itself, will keep the compiler busy for same amount of time.

2) 500 is too little , if you looked at windows api files, the constants easily be few folds more than 500, so increase them to few thousands ( 5k-10k might show visible difference)

Share this post


Link to post
Guest

I thought you used some script to generate it.

Share this post


Link to post

@Lars Fosdal

Thanks for your example ... nice addon ! but I believe there is a misunderstood here. There is a difference between ordinal-type-constant(Char, Byte, Int16, Integer, UInt64, ...) and structured-type-constant(record, array, string, PChar, ...).

// these two are different definition :
const A: Integer = 2; // Involves linker cooperation.
const B = 2;         // Does not invlove linker cooperation ... the compiler is able to handle it itself.

// -----------------------------------------------------------------------------------------------------------

const Str : PChar = 'this is foo'; // Involves linker cooperation.
const Str2 = 'this is foo';        // Involves linker cooperation too.

But why it involves linker cooperation ? that's because structured-data must be allocated at the heap ! When compiling, the compiler have no information at witch address Str, Str2 would terminate to ! It will just emit an empty address and add some information about all empty case he left ... later when compiler finished, it gives the linker the necessary information and ask him to fix all the blank address. Now why the linker is able to know the address and compiler does not ? that's because the linker can see all modules (compiler compiles only one module at a time).

I believe now you understand why your example isn't a real fair test for demonstrating typed-constants.

Share this post


Link to post

Cut'n paste and RX replace.

 

That said: It is unlikely that I'd have more than a few thousand typed record constants.

I.e. The increase in compiler time is for all practical purposes insignificant.

  • Like 1

Share this post


Link to post
1 minute ago, Mahdi Safsafi said:

I believe now you understand why your example isn't a real fair test for demonstrating typed-constants.

 

Dude, why do you think I wrote

1 hour ago, Lars Fosdal said:

Overly simple Compilation Speed Test

 

And again - I really don't care about the speed penalty.

 

I just want constants that are actual constants also when typed and that cannot be changed at runtime.

Share this post


Link to post
2 hours ago, David Heffernan said:

I didn't mean to show you any disrespect.  I'm sorry that it came across that way.

 

All I meant was that I thought the argument that compilation speed was important was bogus.  I really did not mean to upset you.  I try hard never to make anything I write personal.  To me it's always about the technical details.

Don't worry ... it's not a big deal :classic_smile:

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×