シナリオ
複数のスレッド間で連続した異なるリソースの書き換えが発生する、マルチコア CPU 上でのシナリオ。
発生メカニズム
CPU は通常、変数の読み取りを高速化するためにキャッシュを使用しますが、MESI プロトコルにより、異なる CPU 上のスレッドが対応するキャッシュを変更すると、他の CPU 上の対応するキャッシュ行が無効になります(存在する場合)。そのため、別の CPU がそのキャッシュ行の変数を変更すると(同じ変数でなくても)、メモリから読み取る必要があります。これにより、元の CPU の対応するキャッシュ行が無効になり、これが繰り返されるため、変数の変更読み取りはすべてメモリを経由する必要があり、キャッシュを効果的に利用することができず、速度が低下します。
コードの実装
- フェイクフィリングが発生する場合
public final class FalseSharing
implements Runnable
{
public final static int NUM_THREADS = 2; // 変更
public final static long ITERATIONS = 500 * 1000 * 1000;
private final int arrayIndex;
private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
static
{
for (int i = 0; i < longs.length; i++)
{
longs[i] = new VolatileLong();
}
}
public FalseSharing(final int arrayIndex)
{
this.arrayIndex = arrayIndex;
}
public static void main(final String[] args) throws Exception
{
final long start = System.nanoTime();
runTest();
System.out.println("duration = " + (System.nanoTime() - start));
}
private static void runTest() throws InterruptedException
{
Thread[] threads = new Thread[NUM_THREADS];
for (int i = 0; i < threads.length; i++)
{
threads[i] = new Thread(new FalseSharing(i));
}
for (Thread t : threads)
{
t.start();
}
for (Thread t : threads)
{
t.join();
}
}
public void run()
{
long i = ITERATIONS + 1;
while (0 != --i)
{
longs[arrayIndex].value = i;
}
}
public final static class VolatileLong
{
public volatile long value = 0;
}
}
- フェイクフィリングが発生しない場合
public final class FalseSharing
implements Runnable
{
public final static int NUM_THREADS = 2; // 変更
public final static long ITERATIONS = 500 * 1000 * 1000;
private final int arrayIndex;
private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
static
{
for (int i = 0; i < longs.length; i++)
{
longs[i] = new VolatileLong();
}
}
public FalseSharing(final int arrayIndex)
{
this.arrayIndex = arrayIndex;
}
public static void main(final String[] args) throws Exception
{
final long start = System.nanoTime();
runTest();
System.out.println("duration = " + (System.nanoTime() - start));
}
private static void runTest() throws InterruptedException
{
Thread[] threads = new Thread[NUM_THREADS];
for (int i = 0; i < threads.length; i++)
{
threads[i] = new Thread(new FalseSharing(i));
}
for (Thread t : threads)
{
t.start();
}
for (Thread t : threads)
{
t.join();
}
}
public void run()
{
long i = ITERATIONS + 1;
while (0 != --i)
{
longs[arrayIndex].value = i;
}
}
@sun.misc.Contended
public final static class VolatileLong
{
public volatile long value = 0;
}
}
ここには、JVM パラメータ - XX:-RestrictContended を追加する必要があります。
実験結果
- フェイクフィリングが発生する場合
- フェイクフィリングが発生しない場合