कल मुझे क्रिस्टोफ़ नाहर द्वारा ".NET स्ट्रक्चर परफॉर्मेंस" शीर्षक से एक लेख मिला, जिसने कई भाषाओं (C ++, C #, Java, जावास्क्रिप्ट) को एक विधि के लिए बेंचमार्क किया, जिसमें दो बिंदु संरचनाएं ( double
टुपल्स) शामिल हैं।
जैसा कि यह निकला, C ++ संस्करण निष्पादित करने के लिए लगभग 1000ms लेता है (1e9 पुनरावृत्तियों), जबकि C # एक ही मशीन पर ~ 3000ms के तहत नहीं मिल सकता है (और x64 में और भी खराब प्रदर्शन करता है)।
इसे स्वयं जांचने के लिए, मैंने C # कोड लिया (और केवल उस विधि को कॉल करने के लिए थोड़ा सरल किया गया, जहाँ मान को मानकर पास किया गया है), और इसे i7-3610QM मशीन (सिंगल कोर के लिए 3.1Ghz बूस्ट), 8GB RAM, Win8 पर चलाया। 1, .NET 4.5.2 का उपयोग करते हुए, मेरे ओएस 64-बिट के बाद से 32-बिट (x86 WoW64) का निर्माण करें। यह सरलीकृत संस्करण है:
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);
}
}
साथ Point
बस के रूप में परिभाषित:
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; } }
}
इसे चलाने से लेख में इसके समान परिणाम मिलते हैं:
Result: x=1000000001 y=1000000001, Time elapsed: 3159 ms
पहला अजीब अवलोकन
चूंकि विधि को इनलाइन किया जाना चाहिए, मैंने सोचा कि अगर मैं पूरी तरह से संरचना को हटा देता हूं और पूरी चीज को एक साथ सम्मिलित करता हूं तो कोड कैसे प्रदर्शन करेगा?
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);
}
}
और व्यावहारिक रूप से एक ही परिणाम मिला (वास्तव में कई रिट्रीट के बाद 1% धीमा), जिसका अर्थ है कि JIT-ter एक अच्छा काम कर रहा है जो सभी फ़ंक्शन कॉल का अनुकूलन कर रहा है:
Result: x=1000000001 y=1000000001, Time elapsed: 3200 ms
इसका यह भी अर्थ है कि बेंचमार्क किसी भी struct
प्रदर्शन को मापने के लिए नहीं लगता है और वास्तव में केवल मूल double
अंकगणित को मापने के लिए लगता है (बाकी सब कुछ दूर अनुकूलित होने के बाद)।
अजीब सामान
अब आया अजीब हिस्सा। यदि मैं केवल लूप के बाहर एक और स्टॉपवॉच जोड़ता हूं (हां, मैंने इसे कई रिट्रीट के बाद इस पागल कदम तक सीमित कर दिया है), कोड तीन बार तेजी से चलता है :
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
क्या बकवास है! और ऐसा नहीं Stopwatch
है कि मुझे गलत परिणाम मिल रहे हैं क्योंकि मैं स्पष्ट रूप से देख सकता हूं कि यह एक सेकंड के बाद समाप्त होता है।
क्या कोई मुझे बता सकता है कि यहां क्या हो रहा है?
(अपडेट करें)
यहाँ एक ही कार्यक्रम में दो विधियाँ दी गई हैं, जिससे पता चलता है कि इसका कारण JITting नहीं है:
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();
}
}
आउटपुट:
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
यहाँ एक पास्टबिन है। आपको इसे .NET 4.x पर 32-बिट रिलीज़ के रूप में चलाने की आवश्यकता है (यह सुनिश्चित करने के लिए कोड में कुछ चेक हैं)।
(अपडेट 4)
@ हंस के जवाब पर @ usr की टिप्पणियों के बाद, मैंने दोनों तरीकों के लिए अनुकूलित डिसएफ़ीड की जाँच की, और वे अलग-अलग हैं:
ऐसा प्रतीत होता है कि अंतर दोहरे क्षेत्र संरेखण के बजाय पहले मामले में संकलक अभिनय के कारण हो सकता है?
इसके अलावा, अगर मैं दो चर (कुल 8 बाइट की ऑफसेट) जोड़ देता हूं, तो मुझे अभी भी वही गति मिलती है - और अब ऐसा नहीं लगता कि यह हंस पैसेंट द्वारा फील्ड अलाइनमेंट उल्लेख से संबंधित है:
// 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);
}
double
चरों के साथ एक परीक्षण किया , कोई struct
एस नहीं , इसलिए मैंने स्ट्रक्चर लेआउट / विधि कॉल अक्षमताओं को खारिज कर दिया है।