Dün , Christoph Nahr tarafından yazılan ".NET Struct Performance" başlıklı ve iki nokta yapısı ( double
tuples) ekleyen bir yöntem için çeşitli dilleri (C ++, C #, Java, JavaScript) karşılaştıran bir makale buldum .
Anlaşıldığı üzere, C ++ sürümünün çalıştırılması yaklaşık 1000 ms sürerken (1e9 yineleme), C # aynı makinede ~ 3000 ms'nin altına inemez (ve x64'te daha da kötü performans gösterir).
Kendim test etmek için, C # kodunu aldım (ve sadece parametrelerin değere göre geçtiği yöntemi çağırmak için biraz basitleştirdim) ve bir i7-3610QM makinesinde (tek çekirdek için 3.1Ghz artış), 8GB RAM, Win8 üzerinde çalıştırdım. 1, .NET 4.5.2 kullanarak, 32-bit RELEASE (işletim sistemim 64-bit olduğundan x86 WoW64) oluşturun. Bu basitleştirilmiş versiyondur:
public static class CSharpTest
{
private const int ITERATIONS = 1000000000;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Point AddByVal(Point a, Point b)
{
return new Point(a.X + b.Y, a.Y + b.X);
}
public static void Main()
{
Point a = new Point(1, 1), b = new Point(1, 1);
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
a = AddByVal(a, b);
sw.Stop();
Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms",
a.X, a.Y, sw.ElapsedMilliseconds);
}
}
İle Point
basitçe olarak tanımlanan:
public struct Point
{
private readonly double _x, _y;
public Point(double x, double y) { _x = x; _y = y; }
public double X { get { return _x; } }
public double Y { get { return _y; } }
}
Çalıştırmak, makaledeki sonuçlara benzer sonuçlar verir:
Result: x=1000000001 y=1000000001, Time elapsed: 3159 ms
İlk garip gözlem
Yöntemin satır içi olması gerektiğinden, yapıları tamamen kaldırırsam ve her şeyi bir araya getirsem kodun nasıl performans göstereceğini merak ettim:
public static class CSharpTest
{
private const int ITERATIONS = 1000000000;
public static void Main()
{
// not using structs at all here
double ax = 1, ay = 1, bx = 1, by = 1;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
{
ax = ax + by;
ay = ay + bx;
}
sw.Stop();
Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms",
ax, ay, sw.ElapsedMilliseconds);
}
}
Ve pratik olarak aynı sonucu elde etti (aslında birkaç denemeden sonra% 1 daha yavaş), bu da JIT-ter'in tüm işlev çağrılarını optimize ederek iyi bir iş çıkardığı anlamına geliyor:
Result: x=1000000001 y=1000000001, Time elapsed: 3200 ms
Bu aynı zamanda, karşılaştırmanın herhangi bir struct
performansı ölçmediği ve aslında yalnızca temel double
aritmetiği ölçtüğü anlamına gelir (her şey optimize edildikten sonra).
Garip şeyler
Şimdi garip kısım geliyor. Döngünün dışında yalnızca başka bir kronometre eklersem (evet, birkaç denemeden sonra bu çılgın adımı daralttım), kod üç kat daha hızlı çalışır :
public static void Main()
{
var outerSw = Stopwatch.StartNew(); // <-- added
{
Point a = new Point(1, 1), b = new Point(1, 1);
var sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
a = AddByVal(a, b);
sw.Stop();
Console.WriteLine("Result: x={0} y={1}, Time elapsed: {2} ms",
a.X, a.Y, sw.ElapsedMilliseconds);
}
outerSw.Stop(); // <-- added
}
Result: x=1000000001 y=1000000001, Time elapsed: 961 ms
Saçma! Ve bu Stopwatch
bana yanlış sonuçlar vermek gibi değil çünkü bir saniye sonra bittiğini açıkça görebiliyorum.
Biri bana burada neler olabileceğini söyleyebilir mi?
(Güncelleme)
İşte aynı programda, sebebin JITting olmadığını gösteren iki yöntem:
public static class CSharpTest
{
private const int ITERATIONS = 1000000000;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Point AddByVal(Point a, Point b)
{
return new Point(a.X + b.Y, a.Y + b.X);
}
public static void Main()
{
Test1();
Test2();
Console.WriteLine();
Test1();
Test2();
}
private static void Test1()
{
Point a = new Point(1, 1), b = new Point(1, 1);
var sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
a = AddByVal(a, b);
sw.Stop();
Console.WriteLine("Test1: x={0} y={1}, Time elapsed: {2} ms",
a.X, a.Y, sw.ElapsedMilliseconds);
}
private static void Test2()
{
var swOuter = Stopwatch.StartNew();
Point a = new Point(1, 1), b = new Point(1, 1);
var sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
a = AddByVal(a, b);
sw.Stop();
Console.WriteLine("Test2: x={0} y={1}, Time elapsed: {2} ms",
a.X, a.Y, sw.ElapsedMilliseconds);
swOuter.Stop();
}
}
Çıktı:
Test1: x=1000000001 y=1000000001, Time elapsed: 3242 ms
Test2: x=1000000001 y=1000000001, Time elapsed: 974 ms
Test1: x=1000000001 y=1000000001, Time elapsed: 3251 ms
Test2: x=1000000001 y=1000000001, Time elapsed: 972 ms
İşte bir pastebin. NET 4.x üzerinde 32 bitlik bir sürüm olarak çalıştırmanız gerekir (bunu sağlamak için kodda birkaç denetim vardır).
(Güncelleme 4)
@ Usr'nin @Hans'ın cevabıyla ilgili yorumlarını takiben, her iki yöntem için de optimize edilmiş demontajı kontrol ettim ve oldukça farklılar:
Görünüşe göre bu fark, derleyicinin çift alan hizalamasından ziyade ilk durumda komik davranmasından kaynaklanıyor olabilir.
Ayrıca, iki değişken eklersem (toplam ofset 8 bayt), yine de aynı hız artışını elde ederim - ve artık Hans Passant'ın alan hizalama sözüyle ilgili görünmüyor:
// this is still fast?
private static void Test3()
{
var magical_speed_booster_1 = "whatever";
var magical_speed_booster_2 = "whatever";
{
Point a = new Point(1, 1), b = new Point(1, 1);
var sw = Stopwatch.StartNew();
for (int i = 0; i < ITERATIONS; i++)
a = AddByVal(a, b);
sw.Stop();
Console.WriteLine("Test2: x={0} y={1}, Time elapsed: {2} ms",
a.X, a.Y, sw.ElapsedMilliseconds);
}
GC.KeepAlive(magical_speed_booster_1);
GC.KeepAlive(magical_speed_booster_2);
}