MediaTek处理器上的双精度值计算错误

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

更新我还能够重现联想S6000平板电脑上使用联发科MT8125 / 8389芯片的错误以及使用MT6589和安装了Android 4.1的 Fly IQ444 Quattro上的错误 。

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

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

您可以在web2.0calc.com上自行检查, X的值应约为: 0.50006103515625
然而,在具有MT6589芯片的设备上,通常会计算出错误的值: 2.0

项目可在Google Code上获得 (也可以使用APK )。 测试类的来源如下:

     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_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()方法calcX()所有double更改为float calcX()

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

    有没有人见过这个bug? 也许这是一个已知的问题?

    ps:如果有人能够在设备上用其他芯片重现这个bug,或者可以用任何MediaTek芯片和Android> = 4.3进行测试,我将非常感激。

    Related of "MediaTek处理器上的双精度值计算错误"

    这是一个JIT错误,它在2012年末到2013年初在JellyBean源中处于活动状态。简而言之,如果两个或更多的双精度常量在高32位中不同,但在低32位中相同则在JIT认为它们是相同的基本块,并且不恰当地优化其中一个。

    我介绍了这个缺陷: https : //android-review.googlesource.com/#/c/47280/

    并修复它: https : //android-review.googlesource.com/#/c/57602/

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

    有没有人见过这个bug? 也许这是一个已知的问题?

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

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

    对于(1)在本机代码中使用类似以下的内容:

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

    对于(2)使用公共存储大小,这是一个float

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

    不幸的是,在使用Android Java端口时,您将受到制造商的支配。 享受他们的调整,疏忽和实施错误。 至少它不会破坏你的虚拟机 。

    我花了最后一周调查这个问题,这是我发现的:

    • MT6589设备的用户之前已经注意到此错误(例如此处和此处 )
    • 常见的解决方法是禁用JIT(针对特定应用或整个系统)
    • 我能够在MT6589和MT8125 / 8389的几个设备上重现这个问题, 目前它还没有在提供的设备上再现,除了上面提到的更新部分。
    • 如果比我在问题中发布的要简单得多,那么复制bug的expression式只是:
      X = A + b / D
    • 计算之间的延迟是错误的一个关键部分:没有它偶尔出现错误,计算后的小睡眠会一直重现(一旦代码被JITed)
    • 我创建了一个脚本,它组装一个简单的jar文件并直接运行dalvikvm ,并将parameter passing给它。 这允许设置jit阈值并接收JIT生成的输出ARM代码
    • -Xjitdisableopt:1传递给Dalvik解决了问题(此参数禁用kLoadStoreElimination优化)。 也可以将dalvik.vm.extra-opts=-Xjitdisableopt:1build.prop文件中作为保留JIT的快速解决方法(需要root和reboot)
    • 虽然这个问题看起来类似于Scott Barta提到的bug #63790 ,但我认为它是不同的(也是提到的bug的作者Dmitry已经证实这个“Mediatek”bug不会在受#63790影响的手机上重现)
    • 更新:我在模拟器上推出了libdvm.so (来自带有MT6589芯片的Fly IQ4410),并在那里再现了bug。 但是如果我使用从Android 4.2源libdvm.so编译的libdvm.so ,那么这个bug就会消失。 看起来由受影响的设备附带的某些特定版本的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 

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

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

    我建议您可以尝试在更广泛的硬件基础上进行更多测试,以便更好地理解问题。 如果问题确实与硬件有关,我猜你最好的方法是find解决问题的方法并将其记录给其他开发人员。