首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Win32 32/64中Math.Sum的不同优化

Win32 32/64中Math.Sum的不同优化
EN

Stack Overflow用户
提问于 2019-07-22 08:18:08
回答 2查看 210关注 0票数 2

我有以下代码

代码语言:javascript
复制
const
  NumIterations = 10000000;
var
  i, j : Integer;
  x : array[1..100] of Double;
  Start : Cardinal;
  S : Double;
begin
  for i := Low(x) to High(x) do x[i] := i;

  Start := GetTickCount;
  for i := 1 to NumIterations do S := System.Math.Sum(x);
  ShowMessage('Math.Sum: ' + IntToStr(GetTickCount - Start));

  Start := GetTickCount;
  for i := 1 to NumIterations do begin
    S := 0;
    for j := Low(x) to High(x) do S := S + x[j];
  end;
  ShowMessage('Simple Sum: ' + IntToStr(GetTickCount - Start));
end;

当为Win32编译时,Math.Sum要比简单循环快得多,因为Math.Sum是用汇编程序编写的,并且使用四重循环展开。

但是在为Win64编译时,Math.Sum要比简单循环慢得多,因为在64位Math.Sum中使用Kahan求和。这是在求和过程中精度最小化误差累积的一个优化,但比简单的循环还要慢得多。

也就是说,在为Win32编译时,我得到了为了速度而优化的代码,在为Win64编译相同的代码时,我得到了为准确性而优化的代码。这并不是我天真地期望的那样。

Win32 32/64之间的差异有什么合理的原因吗?Double总是8字节,所以在Win32 32/64中的准确性应该是相同的。

在Delphi的当前版本中,Math.Sum是否仍然以相同的方式实现(汇编程序和循环在Win32中展开,Kahan在Win64中求和)?我使用Delphi-XE5。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2019-07-22 09:28:44

在Delphi的当前版本中,Math.Sum是否仍然以相同的方式实现(汇编程序和循环在Win32中展开,Kahan在Win64中求和)?我使用Delphi-XE5。

是(德尔菲10.3.2)。

Win32 32/64之间的差异有什么合理的原因吗?Double总是8字节,所以在Win32 32/64中的准确性应该是相同的。

32位DelphiforWin32使用旧的FPU,而64位编译器使用SSE指令.当64位编译器在XE2中引入时,许多旧的程序集例程没有移植到64位。相反,有些例程与其他现代编译器具有类似的功能。

您可以通过引入一个Kahan求和函数来稍微增强64位实现。

代码语言:javascript
复制
program TestKahanSum;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,Math,Diagnostics;

function KahanSum(const input : TArray<Double>): Double;
var
  sum,c,y,t : Double;
  i : Integer;         
begin
    sum := 0.0;                 
    c := 0.0;                      
    for i := Low(input) to High(input) do begin
      y := input[i] - c;  
      t := sum + y; 
      c := (t - sum) - y; 
      sum := t;                 
    end;
    Result := sum;
end;

var
  dArr : TArray<Double>;
  res : Double;
  i : Integer;
  sw : TStopWatch;
begin
  SetLength(dArr,100000000);
  for i := 0 to High(dArr) do dArr[i] := Pi;
  sw := TStopWatch.StartNew;
  res := Math.Sum(dArr);
  WriteLn('Math.Sum:',res,' [ms]:',sw.ElapsedMilliseconds);
  sw := TStopWatch.StartNew;
  res := KahanSum(dArr);
  WriteLn('KahanSum:',res,' [ms]:',sw.ElapsedMilliseconds);
  sw := TStopWatch.StartNew;
  res := 0;
  for i := 0 to High(dArr) do res := res + dArr[i];
  WriteLn('NaiveSum:',res,' [ms]:',sw.ElapsedMilliseconds);
  ReadLn;
end.

64位:

代码语言:javascript
复制
Math.Sum: 3.14159265358979E+0008 [ms]:492
KahanSum: 3.14159265358979E+0008 [ms]:359
NaiveSum: 3.14159265624272E+0008 [ms]:246

32位:

代码语言:javascript
复制
Math.Sum: 3.14159265358957E+0008 [ms]:67
KahanSum: 3.14159265358979E+0008 [ms]:958
NaiveSum: 3.14159265624272E+0008 [ms]:277

15位的Pi是3.14159265358979

在本例中,32位的数学程序集例程精确到13位,而64位的数学例程精确到15位数。

结论:

  • 64位实现速度较慢(与简单求和相比,是2的一倍),但比32位的数学例程更精确。
  • 引入一个增强的卡汉求和程序可以提高35%的性能。
票数 2
EN

Stack Overflow用户

发布于 2019-07-22 09:37:33

当切换编译目标时,具有相同的RTL函数的行为不一样,这是一个可怕的错误。它不应该改变行为。更糟糕的是,Win64/pascal ()在单或双上的行为不一样!和(单)是天真的求和,而和(双)是用卡汉.:(

最好使用普通的+运算符,或者创建自己的Kahan函数。

我可以确认Delphi10.3中的bug仍然存在。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57141737

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档