11

私自身と私の時代の別の開発者は、最近、仕事中のCore2Duoマシンから新しいCore2Quad9505に移行しました。どちらもWindowsXPSP332ビットとJDK1.6.0_18を実行しています。

そうすると、System.nanoTime()から戻ってくるばかげた値のように見えるため、いくつかのタイミング/統計/メトリック集約コードの自動化された単体テストがすぐに失敗し始めました。

私のマシンでこの動作を確実に示すテストコードは次のとおりです。

import static org.junit.Assert.assertThat;

import org.hamcrest.Matchers;
import org.junit.Test;

public class NanoTest {

  @Test
  public void testNanoTime() throws InterruptedException {
    final long sleepMillis = 5000;

    long nanosBefore = System.nanoTime();
    long millisBefore = System.currentTimeMillis();

    Thread.sleep(sleepMillis);

    long nanosTaken = System.nanoTime() - nanosBefore;
    long millisTaken = System.currentTimeMillis() - millisBefore;

    System.out.println("nanosTaken="+nanosTaken);
    System.out.println("millisTaken="+millisTaken);

    // Check it slept within 10% of requested time
    assertThat((double)millisTaken, Matchers.closeTo(sleepMillis, sleepMillis * 0.1));
    assertThat((double)nanosTaken, Matchers.closeTo(sleepMillis * 1000000, sleepMillis * 1000000 * 0.1));
  }

}

典型的な出力:

millisTaken=5001
nanosTaken=2243785148

100倍実行すると、実際の睡眠時間の33%から60%のナノ結果が得られます。通常は約40%です。

Windowsのタイマーの精度の弱点を理解し、System.nanoTime()のような関連スレッドをスレッド間で一貫して読んだことがありますか?ただし、私の理解では、System.nanoTime()は、使用している目的を正確に目的としています。経過時間の測定。currentTimeMillis()よりも正確です。

なぜそれがそのようなクレイジーな結果を返すのか誰かが知っていますか?これはハードウェアアーキテクチャの問題である可能性がありますか(変更された唯一の主要なものはこのマシンのCPU /マザーボードです)?現在のハードウェアでのWindowsHALに問題がありますか?JDKの問題?nanoTime()を放棄する必要がありますか?どこかにバグを記録する必要がありますか、それともさらに調査する方法についての提案がありますか?

UPDATE 19/07 03:15 UTC:以下のfinnwのテストケースを試した後、さらにグーグルを実行し、 bugid:6440250などのエントリに出くわしました。また、金曜日の終わりにpingがネガティブに戻ってきていることに気付いた他の奇妙な行動を思い出しました。そこで、boot.iniに/ usepmtimerを追加すると、すべてのテストが期待どおりに動作し、pingも正常になりました。

しかし、なぜこれがまだ問題であったのかについて少し混乱しています。私の読書から、TSCとPMTの問題はWindowsXPSP3でほぼ解決されたと思いました。私のマシンが元々SP2であり、元々SP3としてインストールされていたのではなく、SP3にパッチが適用されていたことが原因でしょうか?また、 MSKB896256のようなパッチをインストールする必要があるかどうかも疑問に思います。たぶん私はこれを企業のデスクトップビルドチームに取り上げるべきでしょうか?

4

4 に答える 4

7

C:\boot.ini文字列の末尾に/usepmtimerを追加することで、この問題は解決されました (マルチコア システムでの nanoTime() の適合性についていくつかの疑惑が浮上しています!) 。Windows で TSC ではなく電源管理タイマーを使用するように強制します。XP SP3 を使用しているのに、なぜこれを行う必要があったのかは未解決の問題ですが、これがデフォルトであることは理解していましたが、おそらく私のマシンが SP3 にパッチされた方法が原因だったのでしょう。

于 2010-07-23T06:36:58.820 に答える
2

私のシステム (Windows 7 64 ビット、Core i7 980X):

nanosTaken=4999902563
millisTaken=5001

System.nanoTime() は OS 固有の呼び出しを使用するため、Windows とプロセッサの組み合わせにバグがあると思われます。

于 2010-07-18T10:15:10.117 に答える
1

おそらく、この別のスタック オーバーフローの質問に対する回答を読みたいと思うでしょう: System.nanoTime() は完全に役に立たないのですか? .

要約すると、nanoTime は、複数のコア CPU の存在によって影響を受ける可能性があるオペレーティング システムのタイマーに依存しているように見えます。そのため、nanoTime は、OS と CPU の特定の組み合わせではあまり役に立たない可能性があり、複数のターゲット プラットフォームで実行する予定の移植可能な Java コードで使用する場合は注意が必要です。この件に関してウェブ上には多くの不満があるようですが、意味のある代替案についてはあまりコンセンサスが得られていません.

于 2010-07-18T09:10:52.567 に答える
1

これがバグなのか、コア間の通常のタイマーの違いなのかを判断するのは困難です。

試すことができる実験は、ネイティブ呼び出しを使用して、特定のコアでスレッドを強制的に実行することです。

また、電源管理の影響を排除するには、次の代わりにループでスピンしてみてくださいsleep()

import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.W32API;

public class AffinityTest {

    private static void testNanoTime(boolean sameCore, boolean spin)
    throws InterruptedException {
        W32API.HANDLE hThread = kernel.GetCurrentThread();
        final long sleepMillis = 5000;

        kernel.SetThreadAffinityMask(hThread, new NativeLong(1L));
        Thread.yield();
        long nanosBefore = System.nanoTime();
        long millisBefore = System.currentTimeMillis();

        kernel.SetThreadAffinityMask(hThread, new NativeLong(sameCore? 1L: 2L));
        if (spin) {
            Thread.yield();
            while (System.currentTimeMillis() - millisBefore < sleepMillis)
                ;
        } else {
            Thread.sleep(sleepMillis);
        }

        long nanosTaken = System.nanoTime() - nanosBefore;
        long millisTaken = System.currentTimeMillis() - millisBefore;

        System.out.println("nanosTaken="+nanosTaken);
        System.out.println("millisTaken="+millisTaken);
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Sleeping, different cores");
        testNanoTime(false, false);
        System.out.println("\nSleeping, same core");
        testNanoTime(true, false);
        System.out.println("\nSpinning, different cores");
        testNanoTime(false, true);
        System.out.println("\nSpinning, same core");
        testNanoTime(true, true);
    }

    private static final Kernel32Ex kernel =
        (Kernel32Ex) Native.loadLibrary(Kernel32Ex.class);

}

interface Kernel32Ex extends Kernel32 {
    NativeLong SetThreadAffinityMask(HANDLE hThread, NativeLong dwAffinityMask);
}

コアの選択に応じて非常に異なる結果が得られる場合 (たとえば、同じコアで 5000 ミリ秒、別のコアでは 2200 ミリ秒)、問題はコア間の自然なタイマー変動であることを示唆しています。

スリープとスピンで非常に異なる結果が得られる場合は、電源管理によってクロックが遅くなったことが原因である可能性が高くなります。

4 つの結果のいずれ5000 ミリ秒に近いものがない場合は、バグである可能性があります。

于 2010-07-18T11:54:19.940 に答える