%के बिना केवल comparison statements की सूची से even/odd तय करने का एक शरारती आइडिया 8-bit से 32-bit तक फैलते हुए compiler और executable file format की सीमाएँ उजागर करता है- Python code generator से
if (number == n)अपने-आप बनाने पर 8-bit और 16-bit range तो चल गई, लेकिन 32-bit में comparison targets की संख्या लगभग 4.2 अरब तक फट गई - 32-bit C version ने 48 घंटे बाद लगभग 330GB C file बनाई, और MSVC line number limit तथा heap space की कमी के कारण compile नहीं कर पाया
- PE executable की 4GB सीमा से बचने के लिए x86-64 instructions सीधे generate करके 40GB binary
isEven.binबनाई गई, और Windows memory mapping से उसे executable code की तरह call किया गया - अंतिम program ने
atoiकोstrtoulसे बदलने के बाद 32-bit के बड़े values भी सही पहचाने, और बड़े inputs पर Core i5 12600K·32GB memory·M.2 SSD environment में लगभग 10 सेकंड के भीतर result लौटाया
केवल comparison statements से even/odd पहचानना
- शुरुआत social media पर देखे गए code screenshot से हुई, जिसमें classic even/odd problem को modulus operation के बिना हल करने की कोशिश थी
- संरचना यह थी कि हर number के लिए
if (number == n)रखा जाए, और वह number even है या odd, इसेprintfसे print किया जाए - पहले C example में
uint8_t number = atoi(argv[1]);का उपयोग किया गया और 0 से 10 तक comparison statements हाथ से लिखे गए /Odसे optimization बंद रखी गई ताकि compiler algorithm न बदल दे0,4नेevenप्रिंट किया3,7नेoddप्रिंट किया50,11,99पर कुछ भी print नहीं हुआ
- वजह यह थी कि आख़िरी
ifके बाद उन्हें संभालने के लिए कोई comparison statement नहीं था, इसलिए और ज़्यादा if statements की ज़रूरत पड़ी
Python से if statements generate करना
- सारे comparison statements हाथ से लिखने के बजाय Python से C code output करने वाला meta programming तरीका अपनाया गया
- Python script
for i in range(2**8)से 0 से 255 तक comparison statements generate करती है- अगर
i % 2 == 0हो तोprintf("even\n"); - नहीं तो
printf("odd\n");
- अगर
- generate किया गया C program पूरे 8-bit range में काम करता है
99हैodd50हैeven240हैeven241हैodd
16-bit तक C compile से सफलता
- इसी तरीके को
uint16_tऔरrange(2**16)तक बढ़ाया गया - generate हुई C file लगभग 1.3 लाख lines की थी
- MSVC से compile करने के बाद यह कई values पर सही चली
21000हैeven3475हैodd3हैodd65001हैodd65532हैeven
- executable का size लगभग 2MB था, और 31.8GB memory वाले PC पर कोई समस्या नहीं हुई
32-bit C file और compiler की सीमाएँ
- अगला लक्ष्य
uint32_tऔरrange(2**32)के साथ पूरे 32-bit range को comparison statements से संभालना था - 32-bit में 16-bit की तुलना में numbers की संख्या 65,536 गुना अधिक है
- Python generator को 48 घंटे चलाने के बाद लगभग 330GB C file बनी
- MSVC compile जल्दी ही सीमा पर पहुँच गया
warning C4049: compiler line number limit तक पहुँच गया और line number emission बंद कर दिया- line number limit थी
16777215 fatal error C1060: compiler is out of heap space
- Windows के Portable Executable(.exe) format में 4GB से ऊपर जाना भी कठिन है, इसलिए 4 अरब से अधिक comparisons को executable में भरने वाला C compile path बंद हो गया
- संबंधित सीमा के रूप में PE file maximum size का उल्लेख किया गया
सीधे machine code generate करके चलाना
- compiler और executable file format की सीमाओं से बचने के लिए x86-64 instructions को सीधे binary में output करने वाले तरीके पर स्विच किया गया
- target function
IsEvenरूप में थी, जो argument कोECXमें लेती है और return valueEAXमें देती हैXOR EAX, EAXसे default return value odd के लिए 0 रखी गई- हर number के लिए
CMP ECX, i - even होने पर
INC EAXके बादRET - odd होने पर सीधे
RET
- x86-64 assembly और opcode का उपयोग हुआ, और हर instruction का opcode ChatGPT से पूछा गया
- Python script ने
isEven.binको binary mode में खोलकर 0 से2**32 - 1तक हर number के लिए comparison instructions लिखीं - बनी हुई
isEven.binलगभग 40GB की थी और इसमें पूरे 32-bit number space के लिए लगभग 4.2 अरब comparisons शामिल थे
Windows memory mapping से 40GB code call करना
- host C program ने
isEven.binखोली और पूरी file पढ़ने के बजाय Windows API से उसे memory map किया - execution flow इस प्रकार था
CreateFileAसेisEven.binकोGENERIC_READ | GENERIC_EXECUTEpermissions के साथ खोलनाGetFileSizeExसे 64-bit file size जाँचनाCreateFileMappingमेंPAGE_EXECUTE_READदेनाMapViewOfFileसे executable और readable mapping बनाना- mapped pointer को
int (*isEven)(int)function pointer में cast करके call करना
- इस तरीके में 40GB file को ऐसे treat किया गया जैसे वह पूरी की पूरी memory में मौजूद हो, जबकि actual placement operating system की virtual memory पर छोड़ी गई
- पहले test में ज़्यादातर values सही चलीं, लेकिन
4200000000परoddआया और नतीजा गलत निकला - वजह यह थी कि
atoiunsigned बड़े values को सही तरह handle नहीं कर पाया;strtoul(argv[1], NULL, 10)पर बदलने के बाद4200000000नेevenऔर4200000001नेoddप्रिंट किया
performance पर नज़र
- छोटे numbers पर result तुरंत आया, और
2^32सीमा के क़रीब बड़े numbers पर भी लगभग 10 सेकंड में result मिला - test environment था Core i5 12600K, 32GB memory, M.2 SSD
- calculation के दौरान SSD की अधिकतम read speed लगभग 800MB/s देखी गई
- 40GB data को disk से पढ़कर physical memory में map करने और CPU को cache का लगभग कोई फ़ायदा न मिलने जैसी स्थिति में भी इतनी speed मिलना एक चौंकाने वाला नतीजा रहा
1 टिप्पणियां
Hacker News की रायें
काश मेरे पास अपने शुरुआती दिनों में लिखे प्रोग्राम्स में से एक अब भी होता। 1996 में, जब मैं 16 साल का था, linear algebra की किताब के appendix में computer graphics वाला हिस्सा देखकर, पिछले semester में सीखी programming से कुछ shapes के rotating wireframe बनाने वाले program में पूरी तरह लग गया था
इसी वजह से मैं class में लगभग fail होने वाला था, और उस समय मुझे arrays भी नहीं पता थे, इसलिए सभी vertices और rotation matrix elements अलग-अलग hardcoded variables थे, और matrix multiplication भी loops के बिना लंबी calculation expressions की list थी जिसे हर vertex के लिए copy-paste करके बदलना पड़ता था
Screen पर draw करने के लिए किसी खास address से memory में लिखना पड़ता था, इसलिए pointers के बारे में जानता था, और vertices के बीच lines को rasterize करने वाला loop भी था। यानी आखिरकार arrays और indexing की concept मेरे पास पहले से थी, बस उन्हें खुद बनाना नहीं आता था
(x1,y1)से(x4,y4)तक लिखनी पड़ेगी, और मैं अटक गयामैंने पिता से कहा कि मैं
forloop के अंदरxn,ynजैसा लिखना चाहता हूं, जहांnबताए कि कौन-सा ghost है; उन्होंने BASIC की किताब निकाली और दिखाया किx(n)सच में काम करता हैशिक्षा की बात करते समय मुझे यह घटना याद आती है। Abstract concepts छात्र तब सबसे अच्छी तरह समझते हैं जब उन्हें सच में उनकी जरूरत होती है; जिस बात को पूरे दिन समझाने पर भी वे blank रहते हैं, वही जब उनकी अपनी problem solve करती है तो कुछ seconds या minutes में फिट बैठ जाती है
Computer science major नहीं था, इसलिए file को सबसे बेवकूफी वाले तरीके से read किया, और nested loops की वजह से memory usage और space shortage errors लगातार आते रहे। इसलिए जहां भी संभव था, मैंने
$variable = nullडाल दिया, और सच में वह चल गयाprint,input,if,gotoखुद सीखने के बाद, दूसरों से मदद मांगकर मैंने पहली GWBasic feature जो सीखी वहchainथीलगता है इसे जरूरत से ज्यादा over-engineer किया गया है। समझ नहीं आता code generation तक क्यों करना है; इसे एक simple
forloop से solve किया जा सकता हैisOddमें0सेnतकodd = !oddrepeat करके फिर return कर देंPlayground link: https://go.dev/play/p/8TIfzGrdWDF
अभी profiling नहीं की है, लेकिन intuition और industry experience के हिसाब से यह fast है
n == 0है तोfalse, positive हो तो!isOdd(n-1), और negative हो तो!isOdd(n+1)return करेंAssembly
testq %rdi, %rdi,setg %al,andb %dil, %al,retqजैसी निकलती हैBuild के बगल में
...दबाने पर assembly देख सकते हैं: https://play.rust-lang.org/?version=stable&mode=release&edit...अफसोस, लगता है Go Playground assembly output support नहीं करता
isEven(n int64) bool { return !isOdd(n) }n = infinityहो, तो यह infinite repeat करेगायह तरीका is-even npm package[1] के लिए बिल्कुल फिट है, जिसके साप्ताहिक downloads 196,023 हैं, या is-odd npm package[2] के लिए, जिसके 285,501 हैं।
npm installटाइप करते ही अगर 40GB वाला is-even और 40GB वाला is-odd डाउनलोड होना शुरू हो जाए, तो मज़ा आ जाएगा[1] https://www.npmjs.com/package/is-even
[2] https://www.npmjs.com/package/is-odd
node_modulesdirectories में घुसना चाहता थाansi-colorsमें भी रंगों का एक पूरा package ही नहीं, बल्कि हर रंग के लिए अलग package है, और इसके अलावा भी न जाने क्या-क्या है। ऐसी चीज़ें CLI tools या दिखने में भरोसेमंद packages में शामिल हो जाती हैं और एक-दूसरे को reference करती हैं, इसलिए किसी असली project में भी सिर्फ़ एक innocuous dependency से jonschlinkert के दर्जनों packages खिंचकर आ सकते हैं[1] https://www.npmjs.com/~jonschlinkert
var isOdd = require('is-odd');के बाद बसmodule.exports = function isEven(i) { return !isOdd(i); };ही सब कुछ हैJS में कोई value number type है या नहीं, यह पता लगाना अगर सच में इतना झंझट वाला है, तो शायद यह package मायने रखता हो, लेकिन लगता है कि दूसरे built-in types भी संभालने वाला कोई ज्यादा general package होना चाहिए
हालांकि isNumber उन strings को भी number मानता है जिन्हें number में बदला जा सकता है, जिससे अजीब नतीजे आ सकते हैं। जैसे
const a = '1'; isNumber(a); // true, लेकिनconst b = a + a;string'11'बन जाता हैबेशक
2*aका नतीजा2होता है और1+'1'तथा'1'+1दोनों standard JS वाली मूर्खता के तहत'11'बनते हैं, लेकिन इसी वजह से'1'को number कहना शायद सही जवाब न हो। फिर भी यह package पिछले हफ्ते 4.6 करोड़ बार डाउनलोड हुआ, और Christmas के कारण यह कम था; उससे पिछले हफ्तों में औसत करीब 7 करोड़ था। हमारे project की तरह, ज़्यादातर downloads dependencies के रूप में ही होंगेnullexport करता है लेकिन 400MB memory इस्तेमाल करता है; किसी वजह से HN पर उसे flag कर दिया गया[2]GitHub पर 41 stars और 100% test coverage[3] के साथ, वह निश्चित रूप से production-ready था
[1]: https://github.com/mickael-kerjean/nulll
[2]: https://news.ycombinator.com/item?id=17072675
[3]: https://github.com/mickael-kerjean/nulll/blob/master/test.js
u32नहीं, बल्कि f64 होते हैं, इसलिए इतना काफी नहीं है। सिर्फ़ safe integer range support करने पर भी यह2⁵⁴है, जो2³²से 40 लाख गुना से भी ज्यादा बड़ा हैmachine code का size हर branch पर 4 bytes, यानी करीब 40% ही बढ़ेगा, इसलिए यह लगभग 224 exbibytes तक पहुंच जाएगा। वह भी तब, जब आखिरी 10 bits को आलस में छोड़ दिया जाए
सही तरीके से करना हो तो शायद उसे 1,000 से और multiply करना पड़े, और मैंने NaN patterns पर गहराई से नहीं सोचा है, इसलिए यह थोड़ा छोटा भी हो सकता है।
bigintतक support कर दिया तो शायद बस अनंत ही हो जाएसमझ नहीं आता कि इसे जानबूझकर इस तरह क्यों करना है। database का आविष्कार ठीक इसी काम के लिए हुआ था। SQLite database में numbers और
even/oddclassification की mapping store कर दोइस तरीके का एक फायदा यह भी है कि जब भी किसी number की classification odd से even में बदलती है, तो program update करने की जरूरत नहीं पड़ती
एकमात्र समस्या तब हो सकती है जब TLS खुद even/odd function पर निर्भर हो, लेकिन शायद ऐसा नहीं होगा
even_or_oddरखो और columnsis_odd,is_even,is_zero,is_one,is_two,is_threeवगैरह रख दो।1कोis_odd,is_oneऔर2कोis_even,is_twoके रूप में डाल दोइससे data portability में भी मदद मिलेगी, और जब हाथ से verify करना पड़े तो इसे human-readable format में रखा जा सकेगा
यहां पढ़ी गई posts में से यह सबसे मजेदार posts में से एक है। source code online डालना चाहिए ताकि ChatGPT इसे “train” कर सके
/* Copyright 2023. All unauthorized distribution of this source code will be persecuted to the fullest extent of the law*/इतना elegant code हो तो भला कोई दोष कैसे दे सकता है?
मज़ाक बिल्कुल समझ नहीं आया। जिसने इसे बनाया, वह तो ठीक है, लेकिन अभी के 1198 upvotes समझ नहीं आ रहे
गणना-योग्य values के लिए lookup table न तो नया है और न ही मज़ाक। यह time/memory trade-off का वास्तविक समाधान है, और लेखक भी यह जानता है
समस्या अपने-आप में बेतुकी है, लेकिन इतनी primitive है कि इसके संभव होने पर कोई शक नहीं था; और अपने कंप्यूटर पर करीब 10 सेकंड तक 40GB program को process करने के observation के अलावा कोई वास्तविक measurement भी नहीं था
तो हमने क्या सीखा? कि exe file 4GB से बड़ी नहीं हो सकती? कि
ifअगर2^32हों तो program करीब 300GB का होता है? समझ नहीं आता कि 1198 लोगों को यह दिलचस्प क्यों लगा“Hexing the technical interview” या SIGBOVIK लेखों के उलट, यह पागलपन नहीं, बस निरर्थक लगता है
यह इतना extreme था कि कोई compiler इसे handle नहीं कर पाया, और ज्ञात assembler भी नहीं। इसलिए इसे चलाने के लिए उसे सीधे machine-code binary generate करनी पड़ी, और यह सच में चला। पागलपन है
हर
ifको input से match होने के लिए क्रम से evaluate किया जाएगा, और original program के output में छोटे numbers पर बहुत जल्दी खत्म होना भी इसे support करता है। क्योंकि छोटे numbers code के शुरुआती हिस्से में हैंइसके उलट, 4 अरब
caseवालाswitchstatement हो तो मैं उम्मीद करूंगा कि वह किसी lookup table में compile होगा। हालांकि data type unsigned integer होने पर बिना optimization compile किया code कैसा दिखेगा, यह नहीं जानताकमाल की technology है। इसे AWS को बेच देना चाहिए ताकि वे इसे Enterprise-ready AWS EvenOrOdd API के रूप में उन सबको उपलब्ध करा सकें जिन्हें 40GB executable को सही से host करना नहीं आता
cloud की ताकत हो तो इस program को रोका नहीं जा सकेगा
हैरानी है कि किसी ने बीच में यह नहीं टोका कि program ने सिर्फ 800 MB/s * 10 सेकंड के disk reads से 40GB instructions को “process” कर लिया
मेरा अनुमान है कि OS level पर कोई smart caching रही होगी, लेकिन फिर इसका मतलब है कि
nके2^32के करीब वाले benchmark ठीक से run नहीं हुएया फिर CPU इतना smart हो सकता है कि वह लाखों instructions आगे jump कर जाए
पहले मुझे लगा math गलत होगा, लेकिन मोटा-मोटी हिसाब लगाने पर यह काफी plausible लगता है। numbers भी सभी vague rounded values थे, और input value भी absolute maximum नहीं बल्कि बस high value थी, इसलिए और भी
ifक्या हैं, यह पता नहीं होताउसे यह भी नहीं पता कि वे code blocks क्रम में हैं या unique हैं, या valid instructions भी हैं या नहीं। theory में program चलने के दौरान किसी
ifको infinite loop में बदला भी जा सकता है। हालांकि OS इसकी अनुमति नहीं देगासच में curiosity है। linear access pattern मदद तो करेगा, लेकिन 800 MiB/s?
mmapकिया जाता है, इसलिए unused pages सिर्फ page table entries लेते हैं और load नहीं होते। असल में load केवल वही page होता है जिस पर सीधे jump किया गया हो। neat trick हैदूरदर्शी genius Ross van der Gussom अब मेरा पसंदीदा mythical creature है
यह लेख recommend करता हूं: https://cerfacs.fr/coop/fortran-vs-python
पूरा लेख LLM development की allegory जैसा लगा। अगर कोई आलोचक लिखता, तो कहता कि बहुत भारी resources और “training data” लगाकर solution को “memorize” करना है
सोच रहा हूं कि क्या लेखक का यही intent था
forloop करने वाले 40B LLM model जैसा दिखता है। यह allegory ही लेख की असली motivation लगती है, और यह engineering story नहीं बल्कि आने वाली absurdity पर लिखा हुआ लगता है