【问题标题】:Double precision value computation errors on MediaTek processors联发科处理器上的双精度值计算错误
【发布时间】:2014-02-21 04:44:57
【问题描述】:

我发现我在市场上发布的一个应用程序在某些手机上产生了奇怪的结果。经调查发现,一个计算两个 GeoPoints 之间距离的函数存在问题 - 有时它返回完全错误的值。此问题仅在具有MediaTek MT6589 SoC(又名 MTK6589)的设备上重现。并且 AFAIK 所有此类设备都安装了 Android 4.2。

更新我还能够在带有MediaTek MT8125/8389 芯片的联想 S6000 平板电脑和带有 MT6589 并安装了 Android 4.1Fly IQ444 Quattro 上重现该错误。 p>

我创建了一个有助于重现错误的测试项目。它重复运行计算 1'000 或 100'000 次迭代。为了排除线程问题的可能性,计算是在 UI 线程上执行的(带有小暂停以保持 UI 响应)。在测试项目中,我只使用了原始距离公式的一部分:

private double calcX() {
    double t = 1.0;
    double X = 0.5 + t / 16384;
    return X;
}

您可以自行检查web2.0calc.comX 的值应约为:0.50006103515625
但是在带有 MT6589 芯片的设备上,经常会计算出错误的值:2.0

项目是available at Google CodeAPK 也可用)。测试类的源码如下:

public class MtkTestActivity extends Activity {

  static final double A = 0.5;
  static final double B = 1;
  static final double D = 16384;

  static final double COMPUTED_CONST = A + B / D;

  /*
   * Main calculation where bug occurs
   */
  public double calcX() {
    double t = B;
    double X = A + t / D;
    return X;
  }

  class TestRunnable implements Runnable {

    static final double EP = 0.00000000001;

    static final double EXPECTED_LOW = COMPUTED_CONST - EP;

    static final double EXPECTED_HIGH = COMPUTED_CONST + EP;

    public void run() {
      for (int i = 0; i < SMALL_ITERATION; i++) {
        double A = calcX();

        if (A < EXPECTED_LOW || A > EXPECTED_HIGH) {
          mFailedInCycle = true;
          mFails++;
          mEdit.getText().append("FAILED on " + mIteration + " iteration with: " + A + '\n');
        }
        mIteration++;
      }

      if (mIteration % 5000 == 0) {
        if (mFailedInCycle) {
          mFailedInCycle = false;
        } else {
          mEdit.getText().append("passed " + mIteration + " iterations\n");
        }
      }

      if (mIteration < mIterationsCount) {
        mHandler.postDelayed(new TestRunnable(), DELAY);
      } else {
        mEdit.getText().append("\nFinished test with " + mFails + " fails");
      }
    }

  }

  public void onTestClick(View v) {
    startTest(IT_10K);
  }

  public void onTestClick100(View v) {
    startTest(IT_100K);
  }

  private void startTest(int iterationsCount) {
    Editable text = mEdit.getText();
    text.clear();
    text.append("\nStarting " + iterationsCount + " iterations test...");
    text.append("\n\nExpected result " + COMPUTED_CONST + "\n\n");
    mIteration = 0;
    mFails = 0;
    mFailedInCycle = false;
    mIterationsCount = iterationsCount;
    mHandler.postDelayed(new TestRunnable(), 100);
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mHandler = new Handler(getMainLooper());
    mEdit = (EditText) findViewById(R.id.edtText1);
  }

  private static final int IT_10K = 1000;

  private static final int IT_100K = 100000;

  private static final int SMALL_ITERATION = 50;

  private static final int DELAY = 10;

  private int mIteration;

  private int mFails;

  private boolean mFailedInCycle;

  private Handler mHandler;

  private int mIterationsCount;

  private EditText mEdit;

}

要解决此问题,只需将 calcX() 方法中的所有 double 更改为 float 就足够了。

进一步调查
关闭 JIT(通过将 android:vmSafeMode="true" 添加到应用程序清单)也可以修复错误。

以前有人见过这个错误吗?也许这是一个已知问题?

p.s.:如果有人能够使用其他芯片在设备上重现此错误,或者可以使用任何 MediaTek 芯片和 Android >= 4.3 进行测试,我将不胜感激。

【问题讨论】:

  • code.google.com/p/android/issues/detail?id=63790 详细介绍了运行 Android 4.1.1 的 HTC One S 设备以及许多其他设备上的确切问题。在这个错误中,它最初看起来像是与 ProGuard 的一些不良交互,但进一步的分析已将其缩减为一些更小的 Dalvik 字节码集。根本原因尚未确定,唯一的解决方法是您发现关闭 JIT 的方法。
  • 是的,它似乎与我发布的内容非常相似。 Google 代码的链接包含一个您可以尝试的测试 apk,here 是更详细的描述和与 ProGuard 的创建者的讨论。
  • 我能够在装有 Android 4.2.1 的 MT6589 手机(Canvas HD)中重现此错误。
  • @ScottBarta 是的,它们看起来很相似,但是我无法使用 MT6589(使用 Dmitry 提供的 APK)在设备上重现 #63790。此外,这与 #63790 的不同之处在于它定期发生但并非一直发生。在过去的几天里,我对这个错误进行了更多的调查,我很快就会在我的回答中展示调查的结果。
  • 抱歉,收到您的电子邮件时,我正在参加会议。我刚刚在受我影响的手机上尝试了您的 apk。我测试了 100K 迭代,每次或多次退出而没有退出,并且在大约 20 次测试后没有看到任何与“0 失败”不同的东西。所以,这些问题似乎有所不同。

标签: android floating-point double jit


【解决方案1】:

这是一个 JIT 错误,从 2012 年底到 2013 年初在 JellyBean 源中活跃。简而言之,如果两个或多个高 32 位不同但低 32 位相同的双精度常量被在同一个基本块中使用时,JIT 会认为它们是相同的,并且会不恰当地优化其中一个。

我将缺陷引入:https://android-review.googlesource.com/#/c/47280/

并将其固定在:https://android-review.googlesource.com/#/c/57602/

该缺陷不应出现在任何最近的 Android 版本中。

【讨论】:

    【解决方案2】:

    以前有人见过这个错误吗?也许这是一个已知问题?

    这些有时会出现在几个 Android 邮件列表中。

    我相信您所看到的是 (1) 不同 CPU 及其对浮点值的处理,以及 (2) 导致不同舍入和截断的存储大小差异的影响。

    对于 (1),在本机代码中使用如下内容:

    • _controlfp(_PC_24, _MCW_PC);
    • _controlfp(_RC_NEAR, _MCW_RC);

    对于 (2),使用通用存储大小,即 float

    在原生世界中有时还会出现另一个相关问题:将浮点数传递给函数,但函数处的值始终为 0.0f(而不是用于调用函数的非 0 值)。您可以使用-mfloat-abi=softfp 清除它。见Hard-float and JNI

    不幸的是,您在使用他们的 Android Java 端口时受制造商的摆布。享受他们的调整、疏忽和实施错误。至少不是corrupting your VM

    【讨论】:

      【解决方案3】:

      我在上周调查了这个问题,以下是我的发现:

      • MT6589 设备的用户(例如herehere)之前已注意到此错误
      • 常见的解决方法是禁用 JIT(针对特定应用或整个系统)
      • 我能够在使用 MT6589 和 MT8125/8389 的几台设备上重现此问题,目前尚未在采用上述以外芯片的设备上重现此问题请参阅下面的更新部分李>
      • 重现错误的表达式比我在问题中发布的简单得多,只是:
        X = A + b / D
      • 计算之间的延迟是错误的关键部分:没有它,错误会偶尔出现,在计算后稍作休眠,它总是会重现(一旦代码被 JITed)
      • 我创建了一个脚本,它组装一个简单的 jar 文件并直接运行dalvikvm,并将参数传递给它。这允许设置 jit 阈值并接收由 JIT 生成的输出 ARM 代码
      • -Xjitdisableopt:1 传递给Dalvik 可修复问题(此参数禁用kLoadStoreElimination 优化)。也可以将dalvik.vm.extra-opts=-Xjitdisableopt:1 添加到build.prop 文件中,作为一种保留JIT 的快速解决方法(需要root 和重新启动)
      • 虽然这个问题看起来与 Scott Barta 提到的错误 #63790 相似,但我认为它是不同的(提到的错误 Dmitry 的作者也已确认此“Mediatek”错误不会在受 # 影响的手机上重现63790)
      • 更新:我已将libdvm.so(来自带有 MT6589 芯片的 Fly IQ4410)放在模拟器上,并在那里重现了错误。但是,如果我使用从 Android 4.2 源代码编译的libdvm.so,则该错误就会消失。受影响设备附带的某些特定版本的 libdvm 库生成的 JIT 编译代码似乎存在问题
      • 更新: 使用与上述相同的技术成功重现了三星 Ace 2 手机(NovaThor U8500,Android 4.1.2)上的错误 - 取自 Fly IQ444(MT6589,Android 4.1.2)的 libdvm.so

      我已提交错误报告#65750

      以下是用于重现错误的测试的源代码和 JIT 程序集输出:

      public class Calc {
      
        static final double A = 0.5;
        static final double B = 1;
        static final double D = 16384;
      
        public double calcX() {
          double t = B;
          double X = A + t / D;
          return X;
        }    
      }
      

      Dalvik 正常运行的 JIT 输出:

      D/dalvikvm: Dumping LIR insns
      D/dalvikvm: installed code is at 0x45deb000
      D/dalvikvm: total size is 124 bytes
      D/dalvikvm: 0x45deb000 (0000): data    0xc278(49784)
      D/dalvikvm: 0x45deb002 (0002): data    0x457a(17786)
      D/dalvikvm: 0x45deb004 (0004): data    0x0044(68)
      D/dalvikvm: 0x45deb006 (0006): ldr     r0, [r15pc, -#8]
      D/dalvikvm: 0x45deb00a (000a): ldr     r1, [r0, #0]
      D/dalvikvm: 0x45deb00c (000c): adds    r1, r1, #1
      D/dalvikvm: 0x45deb00e (000e): str     r1, [r0, #0]
      D/dalvikvm: -------- entry offset: 0x0000
      D/dalvikvm: L0x4579e28c:
      D/dalvikvm: -------- dalvik offset: 0x0000 @ const-wide/high16 v0, (#16368), (#0)
      D/dalvikvm: 0x45deb010 (0010): vldr    d8, [r15, #96]
      D/dalvikvm: -------- dalvik offset: 0x0002 @ const-wide/high16 v2, (#16352), (#0)
      D/dalvikvm: 0x45deb014 (0014): vmov.f64  d9, d8
      D/dalvikvm: -------- dalvik offset: 0x0004 @ const-wide/high16 v4, (#16592), (#0)
      D/dalvikvm: 0x45deb018 (0018): vmov.f64  d10, d9
      D/dalvikvm: -------- dalvik offset: 0x0006 @ div-double/2addr v0, v4, (#0)
      D/dalvikvm: 0x45deb01c (001c): vdivd   d8, d8, d10
      D/dalvikvm: -------- dalvik offset: 0x0007 @ add-double/2addr v0, v2, (#0)
      D/dalvikvm: 0x45deb020 (0020): vadd    d8, d8, d9
      D/dalvikvm: -------- dalvik offset: 0x0008 @ return-wide v0, (#0), (#0)
      D/dalvikvm: 0x45deb024 (0024): vmov.f64  d11, d8
      D/dalvikvm: 0x45deb028 (0028): vstr    d11, [r6, #16]
      D/dalvikvm: 0x45deb02c (002c): vstr    d8, [r5, #0]
      D/dalvikvm: 0x45deb030 (0030): vstr    d10, [r5, #16]
      D/dalvikvm: 0x45deb034 (0034): vstr    d9, [r5, #8]
      D/dalvikvm: 0x45deb038 (0038): blx_1   0x45dea028
      D/dalvikvm: 0x45deb03a (003a): blx_2   see above
      D/dalvikvm: 0x45deb03c (003c): b       0x45deb040 (L0x4579f068)
      D/dalvikvm: 0x45deb03e (003e): undefined
      D/dalvikvm: L0x4579f068:
      D/dalvikvm: -------- reconstruct dalvik PC : 0x457b83f4 @ +0x0008
      D/dalvikvm: 0x45deb040 (0040): ldr     r0, [r15pc, #28]
      D/dalvikvm: Exception_Handling:
      D/dalvikvm: 0x45deb044 (0044): ldr     r1, [r6, #108]
      D/dalvikvm: 0x45deb046 (0046): blx     r1
      D/dalvikvm: -------- end of chaining cells (0x0048)
      D/dalvikvm: 0x45deb060 (0060): .word (0x457b83f4)
      D/dalvikvm: 0x45deb064 (0064): .word (0)
      D/dalvikvm: 0x45deb068 (0068): .word (0x40d00000)
      D/dalvikvm: 0x45deb06c (006c): .word (0)
      D/dalvikvm: 0x45deb070 (0070): .word (0x3fe00000)
      D/dalvikvm: 0x45deb074 (0074): .word (0)
      D/dalvikvm: 0x45deb078 (0078): .word (0x3ff00000)
      D/dalvikvm: End LCalc;calcX, 6 Dalvik instructions.
      

      最有趣的部分是:

      vldr    d8, [r15, #96]   ;  d8 := 1.0
      vmov.f64  d9, d8         ;  d9 := d8
      vmov.f64  d10, d9        ;  d10 := d9   // now d8, d9 and d10 contains 1.0 !!!
      vdivd   d8, d8, d10      ;  d8 := d8 / d10 = 1.0
      vadd    d8, d8, d9       ;  d8 := d8 + d9 = 2.0
      vmov.f64  d11, d8
      

      JIT 生成的代码看起来完全错误。而不是三个,只有一个常数被读取为 1.0,因此我们收到X = 1.0 + 1.0 / 1.0 的计算结果,这并不奇怪,其值为 2.0

      这是 Dalvik 运行的 JIT 输出,禁用了 kLoadStoreElimination 优化(修复了错误):

      D/dalvikvm: Dumping LIR insns
      D/dalvikvm: installed code is at 0x45d64000
      D/dalvikvm: total size is 124 bytes
      D/dalvikvm: 0x45d64000 (0000): data    0x5260(21088)
      D/dalvikvm: 0x45d64002 (0002): data    0x4572(17778)
      D/dalvikvm: 0x45d64004 (0004): data    0x0044(68)
      D/dalvikvm: 0x45d64006 (0006): ldr     r0, [r15pc, -#8]
      D/dalvikvm: 0x45d6400a (000a): ldr     r1, [r0, #0]
      D/dalvikvm: 0x45d6400c (000c): adds    r1, r1, #1
      D/dalvikvm: 0x45d6400e (000e): str     r1, [r0, #0]
      D/dalvikvm: -------- entry offset: 0x0000
      D/dalvikvm: L0x45717274:
      D/dalvikvm: -------- dalvik offset: 0x0000 @ const-wide/high16 v0, (#16368), (#0)
      D/dalvikvm: 0x45d64010 (0010): vldr    d8, [r15, #96]
      D/dalvikvm: -------- dalvik offset: 0x0002 @ const-wide/high16 v2, (#16352), (#0)
      D/dalvikvm: 0x45d64014 (0014): vldr    d10, [r15, #76]
      D/dalvikvm: 0x45d64018 (0018): vldr    d9, [r15, #80]
      D/dalvikvm: 0x45d6401c (001c): vstr    d9, [r5, #8]
      D/dalvikvm: -------- dalvik offset: 0x0004 @ const-wide/high16 v4, (#16592), (#0)
      D/dalvikvm: 0x45d64020 (0020): vstr    d10, [r5, #16]
      D/dalvikvm: -------- dalvik offset: 0x0006 @ div-double/2addr v0, v4, (#0)
      D/dalvikvm: 0x45d64024 (0024): vdivd   d8, d8, d10
      D/dalvikvm: -------- dalvik offset: 0x0007 @ add-double/2addr v0, v2, (#0)
      D/dalvikvm: 0x45d64028 (0028): vadd    d8, d8, d9
      D/dalvikvm: 0x45d6402c (002c): vstr    d8, [r5, #0]
      D/dalvikvm: -------- dalvik offset: 0x0008 @ return-wide v0, (#0), (#0)
      D/dalvikvm: 0x45d64030 (0030): vmov.f64  d11, d8
      D/dalvikvm: 0x45d64034 (0034): vstr    d11, [r6, #16]
      D/dalvikvm: 0x45d64038 (0038): blx_1   0x45d63028
      D/dalvikvm: 0x45d6403a (003a): blx_2   see above
      D/dalvikvm: 0x45d6403c (003c): b       0x45d64040 (L0x45718050)
      D/dalvikvm: 0x45d6403e (003e): undefined
      D/dalvikvm: L0x45718050:
      D/dalvikvm: -------- reconstruct dalvik PC : 0x457313f4 @ +0x0008
      D/dalvikvm: 0x45d64040 (0040): ldr     r0, [r15pc, #28]
      D/dalvikvm: Exception_Handling:
      D/dalvikvm: 0x45d64044 (0044): ldr     r1, [r6, #108]
      D/dalvikvm: 0x45d64046 (0046): blx     r1
      D/dalvikvm: -------- end of chaining cells (0x0048)
      D/dalvikvm: 0x45d64060 (0060): .word (0x457313f4)
      D/dalvikvm: 0x45d64064 (0064): .word (0)
      D/dalvikvm: 0x45d64068 (0068): .word (0x40d00000)
      D/dalvikvm: 0x45d6406c (006c): .word (0)
      D/dalvikvm: 0x45d64070 (0070): .word (0x3fe00000)
      D/dalvikvm: 0x45d64074 (0074): .word (0)
      D/dalvikvm: 0x45d64078 (0078): .word (0x3ff00000)
      D/dalvikvm: End LCalc;calcX, 6 Dalvik instructions
      

      按预期加载所有三个常量并执行正确的评估。

      【讨论】:

        【解决方案4】:

        您面临的问题可能与处理器硬件有关。 计算历史上有一些臭名昭著的例子:
        1994 年,部分 Intel Pentium 处理器确实出现错误,产生浮点计算错误(FDIV bug)。这只是小数点后的第 4 位。英特尔最终制定了一个替换程序,将有缺陷的 CPU 换成好的 CPU。 DEC VAX 11/785(1984 年推出)在其(可选)浮点协处理器中存在设计缺陷。由于硬件中的竞争条件,有时浮点协处理器在某些机器上返回任意值而不是所需的结果。 Digital Equipment Corporation 制定了一项计划,以更换所有客户的(5 个大型印刷电路板)协处理器,并签订了硬件维护合同。

        我建议您可以尝试在更广泛的硬件基础上进行更多测试,以更好地了解问题。如果问题真的与硬件有关,我猜您最好的方法是找到解决问题的方法并为其他开发人员记录它。

        【讨论】:

          猜你喜欢
          • 2013-07-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-06
          • 2011-02-26
          • 1970-01-01
          • 1970-01-01
          • 2010-09-16
          相关资源
          最近更新 更多