क्या I/O अब bottleneck नहीं रहा?
(stoppels.ch)- हाल की चर्चाओं में उठे I/O प्रदर्शन और CPU प्रोसेसिंग स्पीड के असंतुलन को प्रयोग से सत्यापित करते हुए दिखाया गया कि वास्तव में अब भी CPU ही मुख्य सीमा है
- sequential read speed cold cache में 1.6GB/s और warm cache में 12.8GB/s तक पहुँचती है, लेकिन single-threaded word frequency गणना केवल 278MB/s के स्तर पर रहती है
- कोड की branch संरचना vectorization में बाधा डालती है, और सिर्फ साधारण lowercase conversion optimization से भी यह केवल लगभग 330MB/s तक ही सुधरती है
wc -wकमांड भी केवल 245MB/s तक पहुँचती है, जिससे पुष्टि होती है कि डिस्क नहीं बल्कि CPU गणना और branch processing bottleneck हैं- AVX2 आधारित manual vectorization से इसे 1.45GB/s तक बढ़ाया गया, लेकिन यह अब भी sequential read speed का लगभग 11% ही है, जो साबित करता है कि bottleneck I/O नहीं बल्कि CPU है
I/O स्पीड और CPU प्रदर्शन की तुलना
- Ben Hoyt के दावे के अनुसार, यह जाँचा गया कि हाल की sequential read speed में वृद्धि क्या CPU स्पीड की ठहराव स्थिति से आगे निकल चुकी है
- उसी तरीके से मापने पर cold cache में 1.6GB/s और warm cache में 12.8GB/s दर्ज हुए
- लेकिन single thread पर word frequency गणना चलाने पर यह केवल 278MB/s रही
- यानी cache warm होने पर भी यह disk read speed की लगभग 1/5 ही है
C आधारित word frequency गणना प्रयोग
- GCC 12 से
optimized.cको-O3 -march=nativeविकल्पों के साथ compile करने के बाद 425MB input file पर चलाया गया- परिणाम: 1.525 सेकंड लगे, प्रोसेसिंग स्पीड 278MB/s
- कोड के भीतर multiple branches और early exit ने compiler की vectorization optimization को बाधित किया
- lowercase conversion logic को loop के बाहर ले जाने के बाद यह 330MB/s तक सुधरा
- Clang का उपयोग करने पर vectorization बेहतर हुई
साधारण word count (wc -w) तुलना
- frequency गणना की जगह केवल शब्दों की संख्या गिनने वाली
wc -wकमांड चलाई गई- परिणाम: 245.2MB/s, जो अपेक्षा से धीमी थी
wc' ','\n','\t'जैसे विभिन्न whitespace characters और locale characters को संभालती है- इसलिए यह केवल साधारण spaces को विभाजक मानने वाले कोड की तुलना में अधिक गणना करती है
AVX2 आधारित vectorization का प्रयास
- आधुनिक CPU फीचर्स का उपयोग करते हुए AVX2 instruction set के साथ vectorization लागू की गई
- 256-bit registers का उपयोग, डेटा को 32-byte alignment के साथ
- whitespace character comparison के लिए
VPCMPEQBनिर्देश का उपयोग
- bit mask (PMOVMSKB) और Find First Set(ffs) निर्देशों से word boundaries का पता लगाया गया
- प्रेरणा Cosmopolitan libc के
strlenimplementation से ली गई
- प्रेरणा Cosmopolitan libc के
प्रदर्शन परिणाम और निष्कर्ष
- manual vectorized code (
wc-avx2) ने 1.45GB/s प्रोसेसिंग स्पीड हासिल कीwc -wके समान परिणाम (82,113,300 शब्द) सत्यापित किए गए
- cold cache स्थिति में भी अब भी user mode computation time ही प्रमुख रहा
- इससे पुष्टि हुई कि disk I/O की तुलना में CPU गणना bottleneck है
- कुल मिलाकर disk speed पर्याप्त तेज़ है, लेकिन branch processing और hash calculation जैसे CPU operations अब भी सीमित करने वाले कारक बने हुए हैं
- कोड और प्रयोग के परिणाम GitHub (
haampie/wc-avx2) पर सार्वजनिक हैं
1 टिप्पणियां
Hacker News की राय
मेरा मानना है कि आधुनिक CPU की performance limit इस बात से तय होती है कि एक single core कितना data संभाल सकता है, यानी
memcpy()की speedज़्यादातर x86 cores लगभग 6GB/s तक पहुँचते हैं, जबकि Apple M series करीब 20GB/s के स्तर पर है
विज्ञापनों में दिखने वाले ‘200GB/s’ जैसे आँकड़े पूरे cores के aggregate bandwidth होते हैं; single core अब भी लगभग 6GB/s के आसपास ही रहता है
इसलिए आप चाहे कितना भी perfect parser लिख लें, इस सीमा को पार नहीं कर सकते
लेकिन zero-copy format इस्तेमाल करने पर CPU बेकार data को skip कर सकता है, इसलिए सिद्धांततः 6GB/s से ‘ज़्यादा’ हासिल किया जा सकता है
मेरे द्वारा विकसित किया जा रहा Lite³ format इसी सिद्धांत का उपयोग करता है और simdjson से अधिकतम 120 गुना तेज़ performance दिखाता है
उदाहरण के लिए, Zen 1 single core पर 25GB/s दिखाता है(संदर्भ लिंक)
मेरे लिखे microbenchmark के अनुसार Zen 2, AVX के बिना 17GB/s और non-temporal AVX के साथ 35GB/s तक पहुँचता है
Apple M3 Max पर non-temporal NEON के साथ 125GB/s तक मापा गया
इसलिए x86 के लिए 6GB/s और Apple के लिए 20GB/s वाले आँकड़े वास्तविकता से काफ़ी कम लगते हैं
क्योंकि iGPU unified memory तक पहुँच सकता है
इसलिए बड़े memory copy, parallel parsing, compression/decompression जैसे कामों में iGPU को blitter की तरह इस्तेमाल करना तकनीकी रूप से फ़ायदेमंद है
हालाँकि zero-copy format में जिस ‘skip’ की बात है, वह cache line unit पर होता है
लगता है कि मूल लेखक ने
timecommand के output की ग़लत व्याख्या की हैsystemtime वह CPU time है जो kernel ने process की ओर से इस्तेमाल कियाउदाहरण में
real0.395s,user0.196s,sys0.117s हो तो CPU ने कुल 313ms ही काम किया, और बाकी 82ms idle state में थायानी यह disk subsystem से तेज़ तो चला, लेकिन अंतर बहुत बड़ा नहीं था
साथ ही I/O path CPU-bound है — भले ही disk और code अनंत तेज़ हों, kernel I/O code चलाने में फिर भी 117ms लगते हैं
मैं इस पोस्ट का लेखक हूँ। इसका एक follow-up भी है: I/O is no longer the bottleneck, part 2
प्रतिभागियों द्वारा इस्तेमाल की गई अलग-अलग optimization techniques पर यह analysis post दिलचस्प है
समस्या की complexity या whitespace classification की संख्या के आधार पर approaches अलग थीं
performance bottleneck हमेशा “CPU या I/O” जैसे किसी एक factor से तय नहीं होता, बल्कि असली workload में सबसे पहले saturate होने वाले resource से तय होता है
वह CPU, memory bandwidth, cache, disk, network, lock, latency — कुछ भी हो सकता है
इसलिए measure करें, profiling से साबित करें, और बदलाव के बाद फिर से measure करें
समस्या CPU या I/O नहीं, बल्कि latency और throughput के बीच संतुलन है
ज़्यादातर software latency को नज़रअंदाज़ करते हैं, इसलिए धीमे होते हैं
data को memory में linear तरीके से रखना, या batch processing और parallelization लागू करना, इन्हें काफ़ी तेज़ बना सकता है
एक ऐसी architecture की कल्पना करें जो सिर्फ CPU ↔ cache ↔ non-volatile storage संरचना से बनी हो
अगर
mmap()की performance characteristicsmalloc()जैसी हों, तो program memory को file name से निर्दिष्ट करके persistence OS पर छोड़ी जा सकती हैअब भी बहुत से software design hard disk era की सीमाओं में बँधे हुए हैं
fsync()अब भी धीमा हैअसली persistence के लिए, rotational disk हो या न हो, अलग approach की ज़रूरत है
वास्तव में ज़्यादातर memory requests
mmap()के ज़रिए ही पूरी होती हैंलेकिन kernel के लिए access pattern का अनुमान लगाना मुश्किल होता है, इसलिए यह
read/writeसे धीमा हो सकता हैcloud environment में performance कभी-कभी pricing lever भी बन जाती है
hardware performance ने हैरान कर देने वाली प्रगति की है, लेकिन कुछ software, ख़ासकर Windows या messenger apps, उल्टे और धीमे लगते हैं
developer remote workstation के रूप में यह अक्षम है
Telegram या FB Messenger तेज़ हैं, लेकिन Teams या Skype नहीं
कुछ LCD में 500ms तक की latency होती है
जब NVMe SSD पहली बार आए थे, तब मज़ाक में कहा जाता था, “अब तो हमारे पास 2TB RAM है”
लेकिन आजकल GPU servers में सचमुच 2TB RAM लगी होती है — यह कमाल की engineering है
अब लगता है तब खरीद लेना चाहिए था
high-concurrency environment में OLAP database को optimize करने के अपने अनुभव के आधार पर कहूँ तो bottleneck ज़्यादातर memory speed थी
I/O bottleneck मूल रूप से sequential read नहीं, बल्कि seek time से जुड़ा विचार था
मैं पोस्ट का मुख्य तर्क समझता हूँ, लेकिन इस बिंदु को उठाना चाहता हूँ
sequential read speed को code से सुधारा नहीं जा सकता था, इसलिए non-sequential access optimization ही मुख्य बात थी