配列を別の小さな配列の値に基づいて更新するコードがあります。 c
5バイト構造体へのアクセスは8バイトよりもはるかに遅い
for (var i = 0; i < result.Length; i++)
{
var c = cards[i];
result[i] -= one[c.C0] + one[c.C1];
}
はデッキからカードを表すバイトのペアである構造体です。
private void TestCards2(int testRepetitions, float[] result, float[] one, Cards[] cards)
{
for (var r = 0; r < testRepetitions; r++)
for (var i = 0; i < result.Length; i++)
{
var c = cards[i];
result[i] -= one[c.C0] + one[c.C1];
}
}
設定testRepetitions
= 25万、とのアレイを使用して: one
は、(デッキから52枚のカードの各々に対するエントリを持つ)52のアレイサイズ
私はこのコードをプロファイルするベンチマークを書いてあります256要素(result.Length = 256
)、私のマシンでは約8.5秒で実行されます。私は5枚のカード(5バイト)を保持するために、その構造体を変更すると、同じベンチマークは今〜13Sを取る
struct Cards
{
public byte C0;
public byte C1;
public Cards(byte c0, byte c1)
{
C0 = c0;
C1 = c1;
}
}
:ここ
はCards
構造体です。 なぜそれが起こりますか?計算は同じで、残っている3枚のカードは使用されておらず、すべての配列はL1キャッシュに収まるのに十分小さい。
さらに見知らぬ人は、カードを8バイト保持するように変更すると、ベンチマークが10秒かかります。
マイセットアップ:ここで何が起こっているか
Test With 2 Cards. Time = 8582 ms
Test With 5 Cards. Time = 12910 ms
Test With 8 Cards. Time = 10180 ms
:
VS 2015 Update 3.
.NET 4.6.2
Release Build x64
CPU: Haswell i7-5820K CPU @ 3.30GHz
ここで私が得た正確なタイミングがありますか?
ベンチマークコード:
class TestAdjustment
{
public void Test()
{
using (Process p = Process.GetCurrentProcess())
p.PriorityClass = ProcessPriorityClass.High;
var size = 256;
float[] one = ArrayUtils.CreateRandomFloatArray(size:52);
int[] card0 = ArrayUtils.RandomIntArray(size, minValue:0, maxValueInclusive:51);
int[] card1 = ArrayUtils.RandomIntArray(size, minValue: 0, maxValueInclusive: 51);
Cards[] cards = CreateCardsArray(card0, card1);
Cards5[] cards5 = CreateCards5Array(card0, card1);
Cards8[] cards8 = CreateCards8Array(card0, card1);
float[] result = ArrayUtils.CreateRandomFloatArray(size);
float[] resultClone = result.ToArray();
var testRepetitions = 25*1000*1000;
var sw = Stopwatch.StartNew();
TestCards2(testRepetitions, result, one, cards);
WriteLine($"Test With 2 Cards. Time = {sw.ElapsedMilliseconds} ms");
result = resultClone.ToArray(); //restore original array from the clone, so that next method works on the same data
sw.Restart();
TestCards5(testRepetitions, result, one, cards5);
WriteLine($"Test With 5 Cards. Time = {sw.ElapsedMilliseconds} ms");
result = resultClone.ToArray();
sw.Restart();
TestCards8(testRepetitions, result, one, cards8);
WriteLine($"Test With 8 Cards. Time = {sw.ElapsedMilliseconds} ms");
}
private void TestCards2(int testRepetitions, float[] result, float[] one, Cards[] cards)
{
for (var r = 0; r < testRepetitions; r++)
for (var i = 0; i < result.Length; i++)
{
var c = cards[i];
result[i] -= one[c.C0] + one[c.C1];
}
}
private void TestCards5(int testRepetitions, float[] result, float[] one, Cards5[] cards)
{
for (var r = 0; r < testRepetitions; r++)
for (var i = 0; i < result.Length; i++)
{
var c = cards[i];
result[i] -= one[c.C0] + one[c.C1];
}
}
private void TestCards8(int testRepetitions, float[] result, float[] one, Cards8[] cards)
{
for (var r = 0; r < testRepetitions; r++)
for (var i = 0; i < result.Length; i++)
{
var c = cards[i];
result[i] -= one[c.C0] + one[c.C1];
}
}
private Cards[] CreateCardsArray(int[] c0, int[] c1)
{
var result = new Cards[c0.Length];
for (var i = 0; i < result.Length; i++)
result[i] = new Cards((byte)c0[i], (byte)c1[i]);
return result;
}
private Cards5[] CreateCards5Array(int[] c0, int[] c1)
{
var result = new Cards5[c0.Length];
for (var i = 0; i < result.Length; i++)
result[i] = new Cards5((byte)c0[i], (byte)c1[i]);
return result;
}
private Cards8[] CreateCards8Array(int[] c0, int[] c1)
{
var result = new Cards8[c0.Length];
for (var i = 0; i < result.Length; i++)
result[i] = new Cards8((byte)c0[i], (byte)c1[i]);
return result;
}
}
struct Cards
{
public byte C0;
public byte C1;
public Cards(byte c0, byte c1)
{
C0 = c0;
C1 = c1;
}
}
struct Cards5
{
public byte C0, C1, C2, C3, C4;
public Cards5(byte c0, byte c1)
{
C0 = c0;
C1 = c1;
C2 = C3 = C4 = 0;
}
}
struct Cards8
{
public byte C0, C1, C2, C3, C4, C5, C6, C7;
public Cards8(byte c0, byte c1)
{
C0 = c0;
C1 = c1;
C2 = C3 = C4 = C5 = C6 = C7 = 0;
}
}
編集 私は億回の繰り返しで、もう一度ベンチマークを再実行してきました。
Test With 5 Cards. Time = 52245 ms
Test With 8 Cards. Time = 40531 ms
と逆の順序で:ここでの結果はある
Test With 8 Cards. Time = 41041 ms
Test With 5 Cards. Time = 52034 ms
は4 Proの表面上でそれを実行する(Skylakeマイクロアーキテクチャi7-6650Uターボはブーストへ〜3.4GHz以上):
Test With 8 Cards. Time = 47913 ms
Test With 5 Cards. Time = 55182 ms
違いはありますし、順序にも依存しません。
インテルVTuneを使用してプロファイリングを実行し、「5カード」バージョンの場合は0.3
、「8カード」の場合は0.27
と表示されます。
Edit2初期ランダム配列を作成するためのArrayUtilsクラスが追加されました。
public static class ArrayUtils
{
static Random rand = new Random(137);
public static float[] CreateRandomFloatArray(int size)
{
var result = new float[size];
for (int i = 0; i < size; i++)
result[i] = (float) rand.NextDouble();
return result;
}
public static int[] RandomIntArray(int size, int minValue, int maxValueInclusive)
{
var result = new int[size];
for (int i = 0; i < size; i++)
result[i] = rand.Next(minValue, maxValueInclusive + 1);
return result;
}
}
この問題は再現できません。 Test with 8 Cardsは最も速いですが、2枚のカードでテストするのが最も時間がかかります。私はこのことを説明する方法も知らないかもしれません:)おそらく、あなたのケースはこの行の浅いコピーに関係しています: 'var c = cards [i];'。 5または2バイトのプロパティを持つより8つのプロパティを持つ構造体をシャローコピーするには、より多くの時間がかかります。 –
@ Yeldar私のベンチマークでは、5バイトの構造体は8バイトより遅く、2バイトは最速です。 – Michal
ベンチマークなど*非常に*高速なコードはあまりにも難しいです。 2回と8回のテストの違いは、1回の割り当てにつきわずか0.25ナノ秒であり、クロックスピードの数倍ですらありません。テストを並べ替えるだけで、任意の結果が得られます。あなたが実際にテストしているのは、あなたのマシンが十分にプロセッサを冷却する能力です。ファンをオンにするのが少し遅いようですが、これは珍しいことではありません。より一貫性のある結果を望むなら、熱をあまり上げないでください。2500万がそれをより良くしません。そしてケースを開き、ダストバニーを吸う。 –