Rust में 240 तत्वों वाले ऐरे पर लूप धीमा क्यों होता है? विश्लेषण

शुरुआती लोगों के लिए ऑनलाइन Python प्रशिक्षक

Python आसानी से सीखें बिना सिद्धांत के बोझ के। स्वचालित जांच के साथ व्यावहारिक कार्य हल करें और ब्राउज़र में सीधे कोड लिखें।

पाठ्यक्रम शुरू करें

240 तत्वों का रहस्य: आपका Rust कोड अचानक धीमा क्यों हो जाता है?

\n\n

एक स्थिति की कल्पना करें: आप Rust में उच्च-प्रदर्शन कोड लिख रहे हैं, सब कुछ तेज़ चल रहा है। आप 200 तत्वों वाले ऐरे पर एल्गोरिदम का परीक्षण करते हैं — बढ़िया। आप 40 और तत्व जोड़ते हैं (कुल 240) — और प्रदर्शन 2-3 गुना गिर जाता है। जादू जैसा लगता है? नहीं, यह भौतिकी है। अधिक सटीक रूप से — आधुनिक प्रोसेसर की आर्किटेक्चर।

\n\n

Stack Overflow पर प्रश्न "Why is there a large performance impact when looping over an array with 240 or more elements?" ने हज़ारों व्यूज़ प्राप्त किए और एक क्लासिक बन गया। 2025 में, नई माइक्रोआर्किटेक्चर और जटिल कैश पदानुक्रम के साथ, इस समस्या को समझना किसी भी Rust डेवलपर के लिए महत्वपूर्ण है।

\n\n

इस लेख में हम केवल उत्तर को दोबारा नहीं बताएंगे। हम दिखाएंगे कि यह Rust में कैसा दिखता है, समस्या का निदान कैसे करें और, सबसे महत्वपूर्ण, — 2025 की आधुनिक प्रथाओं का उपयोग करके इससे कैसे बचें।

\n\n

समस्या की जड़: कैश मिस (Cache Misses)

\n\n

प्रोसेसर सीधे RAM के साथ काम नहीं करता। यह कैश का उपयोग करता है — चिप पर अल्ट्रा-फास्ट मेमोरी। डेटा को cache lines (आमतौर पर 64 बाइट्स) नामक ब्लॉक में कैश में लोड किया जाता है।

\n\n

संख्या 240 का जादू L1 कैश के आकार (आमतौर पर डेटा के लिए 32 KB) और तत्व के आकार से संबंधित है। यदि ऐरे i32 (4 बाइट्स) है, तो 240 तत्व 960 बाइट्स लेते हैं। ऐसा लगता है कि यह L1 में फिट बैठता है। लेकिन समस्या यह है कि एल्गोरिदम एक साथ दो या तीन ऐरे के साथ काम कर सकता है, या कंपाइलर कोड को प्रभावी ढंग से वेक्टराइज़ नहीं कर पाता, जिससे कैश मिस (cache misses) होते हैं।

\n\n

C++ में मूल प्रश्न में, सीमा ठीक 240 तत्व थी क्योंकि यह प्रीफ़ेचिंग (prefetching) और L1/L2 कैश आकार की विशेषताओं से टकराती थी।

\n\n

Rust में यह कैसा दिखता है?

\n\n

आइए स्थिति का अनुकरण करें। हम ऐरे के तत्वों का योग करेंगे, लेकिन एक "स्टेप" के साथ जो कैश को तोड़ता है।

\n\n
use std::time::Instant;\n\nfn main() {\n    // 240 तत्वों का ऐरे\n    let size = 240;\n    let data: Vec<i32> = (0..size).collect();\n    \n    // कोल्ड स्टार्ट\n    let start = Instant::now();\n    let sum: i32 = data.iter().sum();\n    let duration = start.elapsed();\n    println!("240 तत्व (अनुक्रमिक): {:?}", duration);\n    \n    // अब "खराब" एक्सेस 64 (cache line आकार) के स्टेप के साथ करते हैं\n    let mut sum2 = 0i32;\n    let start = Instant::now();\n    for i in (0..size).step_by(64) {\n        sum2 += data[i];\n    }\n    let duration2 = start.elapsed();\n    println!("240 तत्व (स्टेप 64): {:?}", duration2);\n}\n
\n\n

आपको आश्चर्य होगा, लेकिन अंतर 10 गुना हो सकता है! पहला लूप अनुक्रमिक एक्सेस का उपयोग करता है — प्रोसेसर पूर्वानुमान लगाता है और पहले से cache lines लोड करता है। दूसरा — बड़े स्टेप के साथ रैंडम एक्सेस, जो कैश से बाहर निकाल देता है।

\n\n

ठीक 240 क्यों? गहरा कारण

\n\n

2025 में, प्रोसेसर में 32 KB से 64 KB तक का L1 डेटा कैश होता है। लेकिन समस्या केवल आकार की नहीं है। यह कैश की साहचर्यता (set-associative cache) के बारे में है।

\n\n

कैश को 64 अलमारियों (sets) वाली लाइब्रेरी के रूप में सोचें। प्रत्येक अलमारी में 8 किताबें (ways) हो सकती हैं। जब प्रोसेसर मेमोरी तक पहुँचता है, तो वह पते के आधार पर "अलमारी संख्या" की गणना करता है। यदि अलमारी में पहले से 8 किताबें हैं, तो एक को बाहर फेंकना होगा।

\n\n

उन ऐरे के लिए जिनका आकार दो की घात का गुणज है (जैसे, 256), सभी तत्व एक ही "अलमारी" पर पड़ सकते हैं, जिससे cache conflict misses होते हैं। 240, 256 माइनस 16 है, जो इस गुणजता को तोड़ता है और... विरोधाभासी रूप से प्रीफ़ेचिंग की विशेषताओं के कारण स्थिति को और खराब कर देता है।

\n\n

Rust में व्यावहारिक परीक्षण

\n\n
use std::time::Instant;\n\nfn bench_access(size: usize) {\n    let data: Vec<i32> = (0..size as i32).collect();\n    \n    // "खराब" पैटर्न: हर 16वें तत्व तक पहुँच\n    let mut sum = 0i32;\n    let start = Instant::now();\n    for i in (0..size).step_by(16)                

ब्लॉग

पुस्तक अनुशंसाएँ