Lazarus

Free Pascal => General => Topic started by: Seenkao on October 14, 2021, 12:32:20 am

Title: compilation and arithmetic operations with numbers
Post by: Seenkao on October 14, 2021, 12:32:20 am
Приветствую!
Я хотел бы знать, что вообще происходит? Это банальное сложение чисел, но компилятор не переводит 30 + 33 в 63. Он делает двойное сложение.
Я уже умалчиваю о том, что компилятор сам не создаёт ссылочный тип для структур данных. Вполне возможно это далеко не так просто и надо достаточное время на обработку данных, для перевода структуры в ссылку.

Но здесь мне приходится банальные вещи вручную писать. Я расписываю эти данные для себя, а не для компилятора. )))

Yandex translate:
Greetings!
I would like to know what is going on at all? This is a banal addition of numbers, but the compiler does not translate 30 + 33 into 63. He does double addition.
I am already silent about the fact that the compiler itself does not create a reference type for data structures. It is quite possible that this is not so simple and it takes enough time to process the data to translate the structure into a link.

But here I have to write banal things manually. I'm writing this data for myself, not for the compiler.)))

FPC 3.2.1, Linux-Debian 10
-al
Code: Pascal  [Select][+][-]
  1.   useEl^.rh2 := ry1 + 30 + 33;
  2. // assembler
  3. //      movaps  %xmm1,%xmm2
  4. //      movq    _$DRAWALL$_Ld1@GOTPCREL(%rip),%rdx
  5. //      addss   (%rdx),%xmm2
  6. //      movq    _$DRAWALL$_Ld48@GOTPCREL(%rip),%rdx
  7. //      addss   (%rdx),%xmm2
  8. //      movss   %xmm2,28(%rax)
  9. .Ll56:
  10.   useEl^.tx21 := rx1 + 5;
  11. // assembler
  12. //      movaps  %xmm0,%xmm2
  13. //      movq    _$DRAWALL$_Ld16@GOTPCREL(%rip),%rdx
  14. //      addss   (%rdx),%xmm2
  15. //      movss   %xmm2,56(%rax)

changing the code (there's a lot of data further on, so it makes sense).
Code: Pascal  [Select][+][-]
  1.   n30_7 := 30 - 7;
  2. // assembler
  3. //      movq    _$DRAWALL$_Ld45@GOTPCREL(%rip),%rdx
  4. //      movss   (%rdx),%xmm3
  5.   useEl^.ty11 := ry1 + n30_7;
  6. // assembler
  7. //      movaps  %xmm1,%xmm2
  8. //      addss   %xmm3,%xmm2
  9. //      movss   %xmm2,44(%rax)
Получается, что я даже в таком случае в выгоде, сокращая код на одну команду, а если данных немного больше, то автоматически мы выигрываем чуть ли не в два раза (как минимум приближаемся к этому). Я не считаю, что это мало!
Может я что-то упустил?

Yandex translate:
It turns out that even in this case I benefit by reducing the code by one command, and if there is a little more data, then automatically we win almost twice (at least we are approaching this). I think that's a "lot"!
Maybe I missed something?
Title: Re: compilation and arithmetic operations with numbers
Post by: avk on October 14, 2021, 10:06:12 am
...
I would like to know what is going on at all? This is a banal addition of numbers, but the compiler does not translate 30 + 33 into 63. He does double addition.
...

Just curious if something changes if the code changes a bit?
Code: Pascal  [Select][+][-]
  1.   useEl^.rh2 := ry1 + single(30 + 33);
  2.  
Title: Re: compilation and arithmetic operations with numbers
Post by: Seenkao on October 14, 2021, 01:20:57 pm
Да, компилятор объединил числа. Но подобное решение - это дополнительная нагрузка на разработчика. )))

Yandex translate:
Yes, the compiler combined the numbers. But such a solution is an additional burden on the developer.)))
Title: Re: compilation and arithmetic operations with numbers
Post by: alpine on October 14, 2021, 01:26:36 pm
...

Just curious if something changes if the code changes a bit?
Code: Pascal  [Select][+][-]
  1.   useEl^.rh2 := ry1 + single(30 + 33);
  2.  
Then maybe it is worth trying also:
Code: Pascal  [Select][+][-]
  1.   useEl^.rh2 := 30 + 33 + ry1;
  2.   useEl^.rh2 := ry1 + (30 + 33);

Title: Re: compilation and arithmetic operations with numbers
Post by: Seenkao on October 14, 2021, 01:52:10 pm
Then maybe it is worth trying also:
Code: Pascal  [Select][+][-]
  1.   useEl^.rh2 := 30 + 33 + ry1;
  2.   useEl^.rh2 := ry1 + (30 + 33);
Это сработало! Компилятор не разбирает, что в выражении присутствуют числа и не анализирует код вообще? Работает компилятор с SIMD и тут такой код:

Yandex translate:
It worked! The compiler does not understand that there are numbers in the expression and does not analyze the code at all? The compiler works with SIMD and here is the following code:
Code: Pascal  [Select][+][-]
  1.   useEl^.rh2 := 33 + 30 + ry1;
  2. // assembler
  3. //      movq    _$DRAWALL$_Ld51@GOTPCREL(%rip),%rdx   <<<< why not immediately XMM?
  4. //      movss   (%rdx),%xmm2
  5. //      addss   %xmm1,%xmm2
  6. //      movss   %xmm2,28(%rax)

Проверил дальше. Если число стоит впереди всей цепочки выражения, то это число не отправится сразу в регистр XMM. Почему? Ведь вся цепочка будет вычисляться с помощью XMM регистров. Почему компилятор не производит проверку полного выражения до его компиляции?

Yandex translate:
I checked further. If the number is ahead of the entire expression chain, then this number will not be sent immediately to the XMM register. Why? After all, the entire chain will be calculated using XMM registers. Why doesn't the compiler check the full expression before compiling it?
Title: Re: compilation and arithmetic operations with numbers
Post by: alpine on October 14, 2021, 02:15:25 pm
Nothing to wonder, because + operator is with left-to-right associativity. It depends whether the two int constants (30, 33) will be at the same node in the parser tree. In your example:
Code: [Select]
useEl^.rh2 := ry1 + 30 + 33; // := ((ry1 + 30) + 33)they'll be not.

Besides, It's bad thing to rearrange FP arithmetic, will yield a different result.
Title: Re: compilation and arithmetic operations with numbers
Post by: Seenkao on October 14, 2021, 02:50:21 pm
Это в принципе можно так не рассматривать. И это не правильное поведение!
Правильное поведение: если результат тип Single, а выражение тип Integer и тип Single, то мы переводим все значения в тип Single (по значению результата) и пересчитываем.

Другое правильное поведение, когда в выражении все размерности одного типа (допустим Integer). Тогда мы вычисляем значение и переводим в значение типа Single.

Мы не должны смешивать типы в выражениях - это приводит к накладным расходам. Поэтому все значения должны быть сразу приведены к одному типу.

Yandex translate:
In principle, this can not be considered that way. And this is not the right behavior!
Correct behavior: if the result is type Single, and the expression is type Integer and type Single, then we convert all values to type Single (by the value of the result) and recalculate.

Another correct behavior is when all dimensions of the same type are in the expression (let's say Integer). Then we calculate the value and translate it into a Single type value.

We should not mix types in expressions - this leads to overhead. Therefore, all values should be immediately converted to the same type.
Title: Re: compilation and arithmetic operations with numbers
Post by: alpine on October 14, 2021, 03:11:14 pm
*snip*
In principle, this can not be considered that way. And this is not the right behavior!
Correct behavior: if the result is type Single, and the expression is type Integer and type Single, then we convert all values to type Single (by the value of the result) and recalculate.

Another correct behavior is when all dimensions of the same type are in the expression (let's say Integer). Then we calculate the value and translate it into a Single type value.

We should not mix types in expressions - this leads to overhead. Therefore, all values should be immediately converted to the same type.
I'm just explaining why, I had not assessed it as neither right or wrong.
BTW, I am just OK with how it works, and it is on the safe side.
Title: Re: compilation and arithmetic operations with numbers
Post by: PascalDragon on October 15, 2021, 05:08:47 pm
In principle, this can not be considered that way. And this is not the right behavior!
Correct behavior: if the result is type Single, and the expression is type Integer and type Single, then we convert all values to type Single (by the value of the result) and recalculate.

Pascal does not consider the result type when determining the result type of an expression. Also as y.ivanov wrote, expressions are evaluated left-to-right and its completely legitimate for the compiler not to optimize this.

That said: the compiler does store the 30 and the 33 as Single values, cause if you look at the location of e.g. _$DRAWALL$_Ld51 in your assembly output you'll see something like this (this is from my own test that's similar to your code cause you didn't provide a full example):

Code: [Select]
.section .rodata.n__$TTEST$_Ld1,"a"
.balign 4
.globl _$TTEST$_Ld1
_$TTEST$_Ld1:
# value: 0d+3.000000000E+01
.byte 0,0,240,65

.section .rodata.n__$TTEST$_Ld2,"a"
.balign 4
.globl _$TTEST$_Ld2
_$TTEST$_Ld2:
# value: 0d+3.300000000E+01
.byte 0,0,4,66
Title: Re: compilation and arithmetic operations with numbers
Post by: Seenkao on October 15, 2021, 07:12:51 pm
Pascal does not consider the result type when determining the result type of an expression. Also as y.ivanov wrote, expressions are evaluated left-to-right and its completely legitimate for the compiler not to optimize this.
Лично моё мнение - это не правильно. Но возможно ни кто и не занимался данным вопросом. Если это интересно для развития FPC, то я могу заняться созданием анализатора кода и оптимизацией кода паскаля (не ассемблерного, а именно паскаля). Протестируете, посмотрите как работает и есть ли от него смысл.

Я ни в коем случае не говорю, что FPC не оптимизирован. Я даже могу сказать, что достаточная часть компилятора обрабатывает код лучше меня.
Но я зачастую вижу что происходит в конечном коде и понимаю, где можно сделать код быстрее и (или) меньше. Я ставлю подсказки компилятору и компилятор успешно пользуется этими подсказками.
Но это ручная работа!!! А это не очень хорошо.
Я всё равно занимаюсь своими разработками и попутно могу заниматься анализом кода. По полученным данным составлю анализатор кода. Впоследствии такого анализа код на паскале вероятнее всего вырастет, но конечный компилируемый код с большой вероятностью уменьшится и будет работать быстрее.

Интересно вам моё предложение?

Yandex translate:
Personally, my opinion is wrong. But perhaps no one has dealt with this issue. If this is interesting for the development of FPC, then I can create a code analyzer and optimize Pascal's code (not assembler, namely pascal). Test it, see how it works and whether it makes sense.

I am by no means saying that FPC is not optimized. I can even say that a sufficient part of the compiler handles the code better than me.
But I often see what happens in the final code and understand where it is possible to make the code faster and (or) smaller. I put hints to the compiler and the compiler successfully uses these hints.
But it's handmade!!! And this is not very good.
I'm still doing my own development and I can do code analysis along the way. Based on the received data, I will compile a code analyzer. After such an analysis, the pascal code is likely to grow, but the final compiled code is likely to decrease and run faster.

Are you interested in my offer?
Title: Re: compilation and arithmetic operations with numbers
Post by: ASerge on October 16, 2021, 10:55:46 am
Interestingly, Delphi considers constants as a Double type, but sums constants at compile time, no matter in what order it go.
From Delphi 10.4 disassembler:
Code: ASM  [Select][+][-]
  1. Project1.dpr.10: R.rh2 := 30 + 33 + D + 28;
  2. 000000000040D51F F3480F5A0580280000 cvtss2sd xmm0,qword ptr [rel $00002880]
  3. 000000000040D528 F20F580528000000 addsd xmm0,qword ptr [rel $00000028]
  4. 000000000040D530 F2480F5AC0       cvtsd2ss xmm0,xmm0
  5. 000000000040D535 F30F1105A38C0000 movss dword ptr [rel $00008ca3],xmm0
Title: Re: compilation and arithmetic operations with numbers
Post by: alpine on October 16, 2021, 04:10:42 pm
I'd repeat myself from the reply #5
*snip*

Besides, It's bad thing to rearrange FP arithmetic, will yield a different result.

Consider:
Code: Pascal  [Select][+][-]
  1. program Project1;
  2. {$mmx on}
  3.  
  4. uses
  5.   Math;
  6.  
  7. var
  8.   x, y, z, r1, r2: Float;
  9.  
  10. begin
  11.   x := Power(10, 30); // 10^30
  12.   y := -Power(10, 30); // -(10^30)
  13.   z := 1;
  14.   r1 := (x + y) + z;
  15.   r2 := x + (y + z);
  16.   WriteLn(r1, ' ', r2); // 1 0
  17.   ReadLn;
  18. end.      


One should be utterly careful about the order of floating point operations.

What Every Computer Scientist Should Know About Floating-Point Arithmetic (https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html)

Title: Re: compilation and arithmetic operations with numbers
Post by: Seenkao on October 17, 2021, 08:42:55 pm
ASerge, это как раз более-менее правильное поведение. Я не могу сказать точно, потому что не разобрался пока с SIMD. Типы выражения приводятся к одному типу и вычисляются.
Вопрос, это весь код? Если да, то значит Delphi преобразовал все числа в единое число.
FPC также может неплохо справляться с оптимизацией, если мы укажем для него определенные точки, и он будет их использовать.

Yandex translate:
Serge, this is just more or less correct behavior. I can't say for sure, because I haven't figured out SIMD yet. Expression types are reduced to the same type and evaluated.
The question is, is this the whole code? If yes, then Delphi has converted all the numbers into a single number.
FPC can also do a good job with optimization if we specify certain points for it, and it will use them.

y.ivanov, это знать желательно при разработке, но я вёду речь не об этом.  :)
Yandex translate:
y.ivanov, it is desirable to know this when developing, but that's not what I'm talking about. :)
Title: Re: compilation and arithmetic operations with numbers
Post by: Paolo on October 18, 2021, 12:06:42 am
Code: Pascal  [Select][+][-]
  1. Besides, It's bad thing to rearrange FP arithmetic, will yield a different result.
  2.  
+1
Title: Re: compilation and arithmetic operations with numbers
Post by: alpine on October 18, 2021, 01:42:28 am
*snip*
y.ivanov, это знать желательно при разработке, но я вёду речь не об этом.  :)
Yandex translate:
y.ivanov, it is desirable to know this when developing, but that's not what I'm talking about. :)
Have you tried my snippet from reply #11? It is actually an example from the link.

The point is that the floating point arithmetic doesn't obey to the associative laws of algebra. Neither the distributive.

By my understanding, what you're talking about is to pre-calculate at compile time with what is known by the compiler. But that is wrong because:
Code: Pascal  [Select][+][-]
  1.   r0 := x + y + z;
  2.   r1 := (x + y) + z;
  3.   r2 := x + (y + z);

Title: Re: compilation and arithmetic operations with numbers
Post by: engkin on October 18, 2021, 02:36:36 am
By my understanding, what you're talking about is to pre-calculate at compile time with what is known by the compiler.

It is a basic optimization know as "Constant Folding"
Title: Re: compilation and arithmetic operations with numbers
Post by: Seenkao on October 18, 2021, 09:32:54 am
By my understanding, what you're talking about is to pre-calculate at compile time with what is known by the compiler.
Yes.

А продолжение - нет. Потому что всё остальное - это ваши домыслы.
Я не предлагал менять что-то в процессе вычисления. А предлагал указать компилятору дополнительные "точки", с помощью которых компилятор будет быстрее вычислять конечный результат.

То, о чём пишите вы, не относится к компиляции. Это чистое изменение кода. И в таких ситуация решать программисту, надо делать это или нет. Но ни как не компилятору!

И покажу вам код, что предлагаю я.

Yandex translate:
And the continuation is not. Because everything else is your speculation.
I didn't suggest changing anything in the calculation process. I suggested specifying additional "points" to the compiler, with which the compiler will calculate the final result faster.

What you are writing about does not apply to compilation. This is a pure code change. And in such situations, it's up to the programmer to decide whether to do it or not. But not the compiler!

And I'll show you the code that I offer.

The original code:
Code: Pascal  [Select][+][-]
  1. for Count := 0 to MAX_TEST do    // MAX_TEST = 1000000
  2.     begin
  3.       mS1 := nn + 66 + 35;
  4.       mS2 := mS1 - 54 - 22;
  5.       mS3 := ms1 / 22;
  6.     end;              

The final code:
Code: Pascal  [Select][+][-]
  1.     n2 := 35;
  2.     n5 := 66;
  3.     n6 := 54;
  4.     mS4 := 22;
  5.     for Count := 0 to MAX_TEST do
  6.     begin
  7.       mS1 := n2 + nn + n5;
  8.       mS2 := mS1 - n6 - mS4;
  9.       mS3 := ms1 / mS4;
  10.     end;          
В итоге ни чего не поменялось, но изменённый код будет работать немного быстрее. А в больших программах вероятнее всего программа может выдать намного большее ускорение конечной программы. Хотя бы по той причине, что на большинстве участков кода используются не оптимизированные данные. Даже при вызове процедур/функций.
Оптимизировать можно много всего, даже не изменяя конечного кода программы (у меня ускорение чуть меньше 8%).

Yandex translate:
As a result, nothing has changed, but the modified code will work a little faster. And in large programs, most likely the program can give a much greater acceleration of the final program. At least for the reason that most parts of the code use non-optimized data. Even when calling procedures/functions.
You can optimize a lot of things without even changing the final program code (I have a slightly less than 8% acceleration).

P.S. хотя большинству из вас это не нужно, вы даже заморачиваться подобным не будете. / although most of you don't need it, you won't even bother with it.
Title: Re: compilation and arithmetic operations with numbers
Post by: FPK on October 31, 2021, 05:40:40 pm
Code: Pascal  [Select][+][-]
  1. Besides, It's bad thing to rearrange FP arithmetic, will yield a different result.
  2.  
+1

I have added an optimization for this but it is only active in "fastmath" mode: https://gitlab.com/freepascal.org/fpc/source/-/commit/5a617cd1082e80008559a012673db2eecb6304bf (in fastmath mode the compiler does transformations which are mathematically valid but might break due to floating point number behaviour).
Title: Re: compilation and arithmetic operations with numbers
Post by: Seenkao on November 01, 2021, 02:09:03 am
FPK, thank you!
I have added an optimization for this but it is only active in "fastmath" mode: https://gitlab.com/freepascal.org/fpc/source/-/commit/5a617cd1082e80008559a012673db2eecb6304bf (in fastmath mode the compiler does transformations which are mathematically valid but might break due to floating point number behaviour).
"fastmath" не обязателен. Достаточно произвести проверку что сумма/разность чисел идущих друг за другом не теряют своего конечного значения (первое значение не поглощает значение второго числа).
Первое - это проверка по длине. Попадание в длину числа, чтоб не было потерь (при учёте, что числа сами не выходят за пределы и не теряют свой смысл. Но и тогда это можно делать. Это не зависит от компилятора).
Второе (уже не обязательное) проверка изменения конечного числа. Эта проверка не может быть первой, потому что тут больше фактическую роль играет длина каждого числа.

Точнее преобразование не должно идти когда первое число намного больше второго (если это полезная информация).

Google translate:
"fastmath" is optional. It is enough to check that the sum / difference of the numbers following each other do not lose their final value (the first value does not absorb the value of the second number).
The first is checking by length. Hitting the length of a number so that there is no loss (taking into account that the numbers themselves do not go beyond the limits and do not lose their meaning. But even then it can be done. It does not depend on the compiler).
The second (no longer required) check of the change in the final number. This check cannot be the first, because the length of each number plays a more actual role here.

More precisely, the conversion should not occur when the first number is much larger than the second (if this is useful information).

for example:
z := n + 50 + 4   ->  (50 + 4) does not lose its meaning
z := n + 3434563748272345 + 443    -> (3434563748272345 + 443) the meaning of the expression disappears

let be
z := n + num01 + num02
then (exaggerated)
Code: Pascal  [Select][+][-]
  1. num03 := num01 + num02;
  2. if num03 <> num01 then
  3.   if num03 - num01 = num01 then
  4.     the condition is satisfied, we transform.

если в формулах стоят целые числа, а рассчитываться будут числа с плавающей точкой, то мы можем эти действия произвести с целыми числами. Это не конечный код, а только предварительный. А с целыми числами мы можем получить более точный результат.

Всего хорошего!

google translate:
if the formulas contain integers, and floating point numbers will be calculated, then we can perform these actions with integers. This is not final code, only preliminary code. And with integers, we can get a more accurate result.

Good luck! ;)
TinyPortal © 2005-2018