- 1989 में Rob Pike के प्रोग्रामिंग के 5 नियमों पर लेख
- नियम 1: यह मानकर मत चलो कि प्रोग्राम अपना ज़्यादातर समय कहाँ बिताएगा; bottleneck अप्रत्याशित जगहों पर आ सकते हैं। जब तक bottleneck सिद्ध न हो जाए, speed hack से बचो।
- नियम 2: speed के लिए tuning करने से पहले हमेशा मापो। केवल तभी optimize करो जब कोड का कोई हिस्सा बाकी हिस्सों पर महत्वपूर्ण प्रभाव डालता हो।
- नियम 3: जब n छोटा हो, तो जटिल algorithm धीमे होते हैं। अधिकांश मामलों में यही सच है। जटिल algorithm का उपयोग केवल तब करो जब n अक्सर बड़ा हो, और तब भी पहले नियम 2 लागू करो।
- नियम 4: सरल algorithm और data structure बेहतर होते हैं। वे जटिल चीज़ों की तुलना में bug के प्रति कम संवेदनशील होते हैं और implement करना आसान होता है।
- नियम 5: सही data structure प्रोग्रामिंग में निर्णायक है। अगर data अच्छी तरह व्यवस्थित है, तो algorithm स्वतः स्पष्ट हो जाएगा।
- Pike के नियम 1 और 2, Tony Hoare के इस कथन को दर्शाते हैं: "premature optimization सभी बुराइयों की जड़ है।"
- Ken Thompson ने Pike के नियम 3 और 4 को इस तरह दोबारा कहा: "जब संदेह हो, तो brute force का उपयोग करो।"
- नियम 3 और 4, KISS (Keep It Simple, Stupid) design philosophy को लागू करते हैं।
- नियम 5, Fred Brooks की 'The Mythical Man-Month' में कही बात से मेल खाता है, जिसे अक्सर संक्षेप में यूँ कहा जाता है: "smart object का उपयोग करने वाला stupid code लिखो।"
1 टिप्पणियां
Hacker News की टिप्पणियां
“डेटा ही राज करता है” — इससे पूरी तरह सहमत हूं
इसलिए LeetCode interviews मुझे हमेशा अजीब लगे। आम तौर पर फोकस algorithms पर होता है, जबकि असल में कई बार शुरुआत से ही इस तरह approach नहीं करना चाहिए और data structures ज़्यादा केंद्र में होने चाहिए
हां, अगर algorithms की बिल्कुल समझ न हो तो edge cases या किसी खास वजह से किसी specific algorithm पर निर्भर होने की ज़रूरत कब है, यह समझ नहीं आ सकता। फिर भी algorithms अपेक्षाकृत कम समय में सिखाए जा सकते हैं, जबकि कौन-सा data structure इस्तेमाल करना चाहिए, इसकी समझ बनाना लोगों को ज़्यादा मुश्किल लगता है
उस पल माहौल बदलकर “अच्छा, सच में एक senior engineer आया है” जैसा हो जाता है, technical problem पर ज़्यादा खुले तरीके से बात होती है और “coding आती है या नहीं” साबित करने वाला रवैया भी कम हो जाता है
उल्टा, अच्छे बदलाव लाने, milestones हासिल करने और team collaboration में जिन teams के साथ सबसे ज़्यादा मुश्किल हुई, वे वे थीं जहां data structures और code architecture को ठीक से पकड़ने वाला कोई नहीं था। लगता है बहुत लोग इस सोच के आदी हो गए हैं कि framework सब कर देगा, और अगर न हुआ तो किसी ज़्यादा smart व्यक्ति का बनाया plugin या middleware इसे solve कर देगा
जो engineer data structures से बचता है, वह अपने ही पैर पर कुल्हाड़ी मारता है, और सबसे उपयोगी tools में से एक छोड़ देता है; इसकी सीमाएं रोज़मर्रा में दिखने लगती हैं
उदाहरण के लिए code का बड़ा हिस्सा weighted directed acyclic graph (DAG) में longest path खोजने के लिए इस्तेमाल हो सकता है, लेकिन core बात यह समझना था कि problem को weighted DAG के रूप में represent किया जा सकता है। अगर यह नहीं दिखता, तो problem फिर भी solve हो सकती है, लेकिन solution कहीं ज़्यादा धीमा और जटिल होगा
Interviewer पहले से यह नहीं बताएगा कि priority queue, adjacency matrix, trie वगैरह इस्तेमाल करना है। अगर आप अटकें तो hint मिल सकता है, लेकिन बहुत ज़्यादा hand-holding को strong hiring signal मानना मुश्किल है
“Fancy algorithms तब धीमे होते हैं जब n छोटा हो, और n आम तौर पर छोटा ही होता है” — इस बात के संदर्भ में, हाल के project में जो महसूस हुआ वह यह है कि बड़ा n आपकी सोच से कहीं ज़्यादा बड़ा हो सकता है
“एक लाख operations करने होंगे, इसलिए optimize करना ही पड़ेगा” सोचना आसान है, लेकिन computers तेज़ हैं और एक लाख multiplications जैसी चीज़ आम तौर पर इतनी जल्दी हो जाती है कि उस पर गहराई से सोचने की ज़रूरत नहीं पड़ सकती
मतलब यह नहीं कि बिल्कुल सोचना ही नहीं चाहिए, लेकिन modern hardware कितना पागलपन की हद तक तेज़ है, यह अक्सर चौंकाता है
मैंने गलती से quadratic time बन गए code की वजह से production outage होते देखा है, और भले ही 99% users हमेशा छोटे n का इस्तेमाल करें, कुछ users अक्सर बड़े n से टकराकर बहुत slow app experience कर सकते हैं
ज़्यादातर मामलों में, common case में थोड़ा slow और implementation थोड़ी complex होने पर भी मैं quadratic time से बेहतर algorithm चुनना चाहूंगा। आम slow paths optimize हो जाते हैं, लेकिन rare slow paths को developer खुद hit नहीं करता, इसलिए वे छूट जाते हैं या production में फटते हैं
बेशक अगर algorithm बहुत complex हो तो simple quadratic time implementation चुना जा सकता है, लेकिन default मैं संभव हो तो quadratic से कम रखना चाहूंगा। इस बारे में लिखा मेरा लेख भी है: https://kevincox.ca/2023/05/09/less-than-quadratic/
इसलिए 40 साल पहले, जब CPU memory से इतना तेज़ नहीं था और consumer hardware पर branch prediction failures की इतनी चिंता नहीं की जाती थी, तब यह बात ज़्यादा सही बैठती होगी
हर interview में hiring manager यह चाहता है, लेकिन ऐसा होता है कि production के घाव अभी न झेल चुके LeetCode beginner decision लेने से इनकार कर देते हैं
https://www.frankmcsherry.org/assets/COST.pdf
पिछले 20 सालों में computer performance में सुधार को देखते हुए, उस threshold को 100,000 तक बढ़ाना काफ़ी उचित लगता है
“premature optimization is the root of all evil” वाली मशहूर कहावत असल में Tony Hoare की नहीं, बल्कि Donald Knuth की कही हुई है, और इसे अक्सर संदर्भ के बिना optimization के खिलाफ सामान्य बयान की तरह इस्तेमाल किया जाता है
पूरा वाक्य है: “हमें छोटी-छोटी efficiency, यानी कहें तो 97% मामलों को भूल जाना चाहिए। premature optimization is the root of all evil. लेकिन अहम 3% में मौके नहीं चूकने चाहिए”
असल बात यह है कि optimization वहीं करें जहाँ उसका असर हो
मुमकिन है Tony ने पहले कहा हो और Knuth ने उसे तराशकर प्रकाशित किया हो। जरूरी संदर्भ देने वाला लंबा quote साथ रखना हमेशा अच्छा है
उस समय programming आज से काफी अलग थी। तब “premature optimization” का मतलब “बस scalable popular library इस्तेमाल कर लो” नहीं था, बल्कि “ऐसा समझ से बाहर bit-manipulation algorithm इस्तेमाल करो जो सिर्फ इसी hardware पर चले” के ज्यादा करीब था
“premature optimization is the root of all evil” में यह अर्थ पहले से शामिल है; कहावत “optimization is the root of all evil” नहीं है
company के data structures/algorithms interviews में मैंने गिनती से बाहर frontend developers को bubble sort को सबसे अच्छा विकल्प कहते देखा है। मौके पर derivation करवाने की जरूरत नहीं, बस कुछ तरीके जानना और समस्या के हिसाब से अच्छा विकल्प बता देना काफी है
अगर “premature optimization मत करो” को इतनी चरम सीमा तक जीते हैं कि efficient methods तक नहीं जानते, तो यह कैसे पता चलेगा कि important हिस्सा कहाँ है
“data structures ही core हैं” वाली बात databases में दोगुनी महत्वपूर्ण है
जो लोग DB को बेवकूफ bit storage या object definitions का 1:1 reflection भर समझकर इस्तेमाल करते हैं, वे अक्सर तब हैरान होते हैं जब DB इसे personal लेता है और performance खराब कर देता है
अगर मुझे ORM-generated DB schema फिर से देखना पड़ा, तो वह reunion बहुत जल्दी होगा
समस्या यह है कि कुछ, या कई, developers SQL नहीं जानते, और ORM इस्तेमाल करने के लिए जरूरी DB knowledge भी नहीं रखते
ORM काफी leaky abstraction है, जिसके नीचे क्या है यह जानना जरूरी है। यह समझ लें तो ज्यादातर ORM से भी ठीक-ठाक schema बनाया जा सकता है
data structures को अच्छी तरह organize करने और design बदलने पर भी वैसा बनाए रखने के लिए data और code को organization level पर अलग करना होगा
DB schema design, use cases और उनके बीच की mapping को बाकी implementation से अलग करना चाहिए, और इसी group को integrity checks आदि भी लिखने चाहिए। अगर organization structure data और code को अलग नहीं करता, तो code और data को अलग करना मुश्किल है
मेरा एक extra rule यह है कि छोटी-छोटी performance waste जमा होकर, भले ही अलग-अलग मामूली लगें, आखिरकार program को धीमा बना देती हैं
अगर complexity, readability, maintainability और implementation cost पर असर नहीं पड़ता, तो performance को यूँ ही छोड़ नहीं देना चाहिए। बाकी शर्तें लगभग समान हों तो दो विकल्पों में धीमे वाले को चुनना ठीक नहीं है
और अगर मान लें कि n छोटा है, तो लगभग कुछ भी चल जाता है। लेकिन अगर ऐसा code लिखते हैं जो n के 100 से कम होने पर ठीक चलता है और 10000 से ऊपर टूट जाता है, जैसे O(n²), तो साफ limit लगानी चाहिए। छोटा n मानने वाली धारणा टूटे तो AWS bill bomb या अटके हुए program से बेहतर है कि जोर से error दे
इन guidelines में से काफी आखिरकार over-engineering रोकने की strategy पर आकर टिकती हैं
मेरे अनुभव में premature optimization सबसे महंगे traps में से एक है। संभावित समस्या को बहुत जल्दी bypass करने पर वह assumption validate नहीं होती, और अगली team को अनावश्यक complexity सुलझाने के लिए महंगा solution बनाना पड़ता है
सीखा हुआ approach यह है: optimization estimates पर निर्भर करती है, और शुरुआती estimates अक्सर गलत होते हैं
यह भी समझ आया कि लोगों को जरूरत से ज्यादा complex code बनाने से रोकने के लिए ego management और psychology की समझ काफी महत्वपूर्ण है
overproduction को आम तौर पर सबसे खराब waste माना जाता है, क्योंकि यह न सिर्फ ऐसी चीज बनाता है जिसकी जरूरत नहीं, बल्कि वह effort भी खा जाता है जो वास्तव में जरूरी चीज पर लग सकता था। over-engineering भी ऐसी ही है
उदाहरण के लिए, सिर्फ 100 users होने पर भी यह सोचकर microservices architecture से शुरू करना कि कभी 1 million users हो गए तो monolith को redesign करना मुश्किल होगा
इसलिए पहले यह address करना चाहिए कि समय के साथ code कम flexible क्यों हो जाता है
कुल मिलाकर अच्छे नियम हैं, लेकिन असल में नियम 1 वैसा का वैसा लागू नहीं होता
शुरुआत करते समय यह परिकल्पना चाहिए कि bottleneck क्या बनेगा। ऐसा हमेशा संभव नहीं होता कि XYZ implement करने के बाद माप लें कि क्या slow है और उसे ठीक कर दें। X, Y, Z आपस में जुड़े होते हैं, इसलिए Y को तेज़ बनाने के लिए कभी-कभी X और Z को किसी खास तरीके से बनाना पड़ता है, और कभी-कभी पहले से पता होता है कि Y bottleneck बनेगा
बाद में मापकर यह जान भी जाएँ कि क्या slow है, तब भी तेज़ बनाने के approach पर दांव लगाना पड़ता है। दांव जितना ज़्यादा informed हो, उतना अच्छा
अच्छे programmer मापते हैं, लेकिन वे यह भी predict कर सकते हैं कि क्या slow होगा, किसमें bugs ज़्यादा होंगे और क्या ज़्यादा memory लेगा, इसलिए उन्हें कम iterations करनी पड़ती हैं। performance behavior predict नहीं किया जा सकता—इसे नियम की तरह कहना, अच्छे programmers के जमा किए अनुभव और कौशल को नज़रअंदाज़ करना है
क्योंकि नियम 1 का पालन करने की प्रक्रिया ही bottlenecks का अंदाज़ा लगाने वाली अच्छी intuition के लिए ज़रूरी अनुभव और empirical background पाने का सबसे अच्छा तरीका है
अगर speed prediction गलत निकला, तो project की पूरी उम्र तक बेवजह complex code ढोना पड़ता है
लोग algorithm की speed का अंदाज़ा अक्सर गलत लगाते हैं। अगर computer अपना 99% समय DB server से n लाने में लगाता है, तो O(n) और O(n²) अक्सर real time में एक जैसे दिख सकते हैं
C में लिखा algorithm भी equivalent Python code से slow हो सकता है, क्योंकि bytecode compiler ने कोई smart काम किया हो सकता है
मैंने legacy code को fast बनाने का काम काफ़ी किया है; आम तौर पर यह सोच से कहीं आसान होता है, और ऐसे कारणों से slow होता है जो original author को obvious नहीं थे। असल में, codebase इतना complex हो चुका होता है कि मूल लेखक उससे reasoning नहीं कर पाता, इसलिए वह slow रहता है। मेरे पास “बहुत slow” का एक concrete example होता है, इसलिए उसे run करके slow points observe करते हुए debug करना आसान होता है
अगर आप बहुत सारे physical objects वाला video game बना रहे हैं, और अनुभव से पक्का जानते हैं कि collision detection बड़ा issue होगा, तो game और system को उसी के इर्द-गिर्द design करना speed hacking नहीं है
अगर काम ऐसा है जहाँ performance बड़ा concern बनने वाला है, तो मापना स्वाभाविक है। यह जाँचने के लिए नहीं कि वह concern है या नहीं, बल्कि यह देखने के लिए कि आप उस concern को कितनी अच्छी तरह handle कर रहे हैं
अगर नई requirements के लिए नया system बना रहे हैं, तो कई बार बस शुरू कर देना ठीक लगता है। बनाइए, test और measure कीजिए, फेंक दीजिए या refactor कीजिए, और repeat कीजिए
Rust को उदाहरण लें तो यह draft language और OCaml में बने compiler से शुरू हुआ और फिर iterate हुआ। भले ही पता था कि किसी दिन OCaml से self-hosting पर जा सकते हैं, मुझे नहीं पता कि उससे बड़ा फर्क पड़ता
अगर users ही नहीं हैं, तो घंटों लगने वाला function भी उस function की तुलना में अभी भी काफी fast है जिसे optimize करने पर milliseconds लगते हैं। मुझे नहीं पता कि किसी ने यह साबित किया है कि वह ऐसे predictions सही-सही कर सकता है
नियम 5 के विरोध में, simple data पर complex algorithms बड़ा performance gain दे सकते हैं, obstacles हटाते हैं और उल्टा चीज़ों को simplify भी कर सकते हैं
उदाहरण के लिए BinaryTree object के बजाय sorted array में binary search करें, तो merge करना concat के बाद sort भर रह जाता है और simple हो जाता है; pointers नहीं होते, इसलिए serialization आसान हो जाता है, और कुछ मामलों में serialization की ज़रूरत ही नहीं रहती। array disk या memory में, या mmap के ज़रिये दोनों में हो सकता है; RAM से बड़े data को भी handle कर सकता है; और file या mapping की ओर इशारा करके सीधे run करने वाला cold start भी संभव है। इसमें cache-oblivious गुण भी हैं
Huffman coding भी उदाहरण है। University में आम तौर पर tree-based algorithm और O(n log n) complexity के रूप में सीखा होगा, लेकिन मुझे नहीं पता था कि in-place array-based तरीके से Huffman tree को linear time में construct करने का तरीका भी है
बेशक 99% समय backend microservices बनाते हुए standard collection data structures इस्तेमाल होते हैं। लेकिन अगर काम पर big data का काम कर रहा हूँ, तो उस समय trend में चल रहे MapReduce परिवार को adopt करने के बजाय local बड़े disks वाली single machine पर process करना कहीं ज़्यादा पसंद करूँगा
Rob Pike शायद पहले code की profiling करने को कहते, और फिर देखते कि fancy code या alternative data structure सचमुच तेज़ है या नहीं
यह लेख मैंने 10 साल से भी पहले पहली बार cat-v पर पढ़ा था, और design व complexity को approach करने और उसके बारे में सोचने के तरीके पर इसका अमिट असर पड़ा
http://doc.cat-v.org/bell_labs/pikestyle
मूल नियम “data structures ही core हैं” से यह घटकर “smart objects इस्तेमाल करने वाला dumb code लिखो” कैसे बन जाता है, समझ नहीं आता
“smart objects” phrase बहुत खराब लगा, और मूल नियम लंबा होने पर भी कहीं बेहतर है