🏎️ शाळेच्या पद्धतीने performance engineering शिका

एक program किंवा service वेगवान कशी करायची, हे शाळेच्या क्रीडा दिनाच्या रूपात शिकवले आहे: अंतिम रेषेवरचे स्टॉपवॉच, धावण्याचा ट्रॅक, रिलेमधील बॅटन-बदल, पाण्याच्या टेबलावरची रांग आणि रिलेतील सर्वात हळू धावपटू. प्रत्येक धडा म्हणजे शाळेतील एक गोष्ट, सोबत काढलेली आकृती आणि एक lab — आणि क्रीडा दिन repo मध्येच आहे: pure Python मधील deterministic models (perf/sim.py, शून्य dependencies), ज्यात खरे cProfile call counts, खरे tracemalloc, खरे SQLite query plans आणि simulated खर्च आहेत, त्यामुळे प्रत्येक run तेच आकडे छापतो.

⏱️ p50 · p95 · p99🏁 प्रामाणिक benchmarks📋 cProfile📈 O(n²) vs O(n log n)🎒 tracemalloc🗂️ hit ratio🔁 N+1 queries🧵 GIL🚰 Little · Amdahl🗃️ EXPLAIN📱 LCP · INP · CLS🏆 CI मधील budgets

⏱️ भाग 1 — मोजमाप (1–4)

  • स्टॉपवॉच ⏱️
  • warm-up आणि अनेक फेऱ्या 🏁
  • प्रशिक्षिकेचा clipboard 📋
  • जोडीने तपासा की आधी क्रमाने लावा 📈

🔧 भाग 2 — नेहमीचे उपाय (5–8)

  • एका वेळी एक अडथळा 🎒
  • scoreboard लक्षात ठेवतो 🗂️
  • मैदानावर कमी फेऱ्या 🔁
  • एक बॅटन, अनेक धावपटू 🧵

🏟️ भाग 3 — संपूर्ण system (9–12)

  • पाण्याच्या टेबलावरची रांग 🚰
  • index कार्डांची पेटी 🗃️
  • फोनवरचे page 📱
  • पात्रता वेळा 🏆
# the 60-second wow — one program, measured and made fast:
git clone https://github.com/BaluRaut/learn-performance-school.git && cd learn-performance-school
python3 perf/demo.py               # 12 lessons: stopwatches, hot spots, N+1, the GIL, queues, query plans, budgets
python3 perf/test_perf.py          # 12 checks across the lessons

तुम्हाला काय दिसायला हवे (छाटलेले — यश असे दिसते):

═══ latency ═══
── 1000 requests timed by the finish-line stopwatches (ms per request = latency)
   mean 51.3 · p50 40 · p95 59 · p99 260 · max 1364
   37 of 1000 took 100 ms or more · 7 took 800 ms or more — the tail
   the mean (51.3) describes nobody: most finish near 40, a few take far longer
── throughput = finishers per second: one lane at 40 ms a lap → 25/s · four lanes → 100/s · each lap is still 40 ms
   a page that waits for   1 call(s): 1.0% of page loads meet at least one call slower than its p99
   a page that waits for  10 call(s): 9.6% of page loads meet at least one call slower than its p99
   a page that waits for 100 call(s): 63.4% of page loads meet at least one call slower than its p99
   report percentiles, not the mean — and remember the p99 of each call becomes common in a big fan-out

═══ bench ═══
── Katrina times version A 20 times (simulated stopwatch): first three runs [42.1, 31.7, 21.6] ms — cold, warming up
   one run says 42.1 ms · the mean of all 20 says 15.5 ms · after 3 warm-up runs, the median of 17 says 12.4 ms
   spread of the 17: min 11.2 · p25 11.7 · median 12.4 · p75 12.7 · max 19.9 (one spike: another program woke up)
── A vs B1 (a small tweak): medians 12.4 vs 12.0 ms · middle halves 11.7–12.7 vs 11.3–12.3 → no clear difference: the change is smaller than the noise
── A vs B2 (a real fix): medians 12.4 vs 8.1 ms · middle halves 11.7–12.7 vs 7.8–8.4 → B is faster (1.53x)
   warm up, repeat, report the median and the spread; claim a win only when it is bigger than the noise

═══ profile ═══
── Dipika profiles the results program with cProfile: 300 runners, 2000 laps (we read CALL COUNTS, not times)
   find_runner 2000× · best_of 4× · results_slow 1×
   find_runner walks the runner list every time: 297,098 comparisons for 2000 lookups — the hot spot
── fix: build a dict by bib once →   best_of 4× · results_fast 1×
   find_runner comparisons now 0 · same results: True · best 100 m per house (ms) {'blue': 11000, 'green': 11006, 'red': 11006, 'yellow': 11005}
   measure first: the slow part is rarely where you guessed, and one hot function is usually most of it

═══ complexity ═══
── any duplicate bib numbers? three ways, counting comparisons (no duplicates, so each does its full work)
       n   every pair O(n²)   sort + neighbours O(n log n)   a set O(n)
     250             31,125                          1,921          250
     500            124,750                          4,355          500
    1000            499,500                          9,724        1,000
    2000          1,999,000                         21,436        2,000
   double n: every pair does 4× the work · sorting a little over 2× · the set exactly 2×
   big-O is how the work GROWS; at n = 2000 the pairwise way already does 93× the comparisons of sorting

═══ memory ═══
── the sum of squares of 0..99,999: a list holds every result, a generator makes one at a time
   same answer: True (333,328,333,350,000)
   results held at once: list 100,000 · generator 1
   tracemalloc peak: the list needs over 100× the generator's memory: True
   stream data you only walk through once; keep a list when you need it again, in any order, or its length

═══ caching ═══
── fib(25) = 75025: plain recursion does the work 242,785 times · memoized (lru_cache) 26 times (23 cache hits)
── parents look up results: 2000 requests over 500 runners, a few asked for far more often · a hit costs 1 ms, a miss 50 ms
   LRU cache of  10 → hit ratio 15.4% · average 42.5 ms per request
   LRU cache of  50 → hit ratio 33.0% · average 33.9 ms per request
   LRU cache of 100 → hit ratio 44.9% · average 28.0 ms per request
   LRU cache of 250 → hit ratio 67.7% · average 16.8 ms per request
   a cache is only as good as its hit ratio — and every cached answer can be stale until it is refreshed

═══ io ═══
── best lap for each of 50 runners, from SQLite (sqlite3's trace callback counts the queries)
   N+1: one query for the runners, then one per runner → 51 queries
   batched with IN (...)                            → 2 queries
   one JOIN + GROUP BY                              → 1 query · same answer all three ways: True
   at 1 ms round trip to a database server: 51 ms vs 2 ms vs 1 ms of waiting (here SQLite is in-process)
── writing 10,000 result lines (210,000 bytes) unbuffered     → 10,000 raw writes
── writing 10,000 result lines (210,000 bytes) buffered (8 KB) → 26 raw writes
   every trip costs a fixed toll: carry many things per trip

═══ concurrency ═══
── 8 tasks · I/O-bound: 5 ms of Python + 100 ms waiting for a reply · CPU-bound: 100 ms of Python (a teaching model)
                                             I/O-bound   CPU-bound
   one after another                           840 ms      800 ms
   8 threads, one baton (the GIL)              140 ms      800 ms
   asyncio, 1 thread, 8 tasks                  140 ms      800 ms
   4 processes on 4 cores (+50 ms start)       260 ms      250 ms
   8 threads, free-threaded build, 4 cores     110 ms      200 ms
   waiting needs no baton: threads and asyncio overlap the waits · Python work needs the baton: processes, or a free-threaded build
   (the GIL is released while waiting on I/O and inside many C extensions; free-threaded CPython exists since 3.13)

═══ queueing ═══
── one water table, 10 ms to serve a runner · M/M/1 approximation: time at the table = 10 ms ÷ (1 − utilisation)
   utilisation 50% → formula     20 ms · simulated (20,000 runners)     20 ms
   utilisation 80% → formula     50 ms · simulated (20,000 runners)     49 ms
   utilisation 90% → formula    100 ms · simulated (20,000 runners)    104 ms
   utilisation 95% → formula    200 ms · simulated (20,000 runners)    186 ms
   utilisation 99% → formula   1000 ms · simulated (20,000 runners)    519 ms
   near 100% busy, the wait explodes (and a short simulation has not caught up with it yet)
── Little's law L = λW: 80 runners/s × 50 ms = 4 runners at the table on average
── Amdahl's law: 90% of the relay can be shared out, 10% is one runner's leg that nobody can help with
   2 runners → 1.82× · 4 runners → 3.08× · 8 runners → 4.71× · 16 runners → 6.40× · endless runners → 10.00×
   keep utilisation well below 100% for low latency · speed up the serial part, it caps everything

═══ database ═══
── 10,000 laps, 500 runners · EXPLAIN QUERY PLAN for: SELECT lap_ms FROM laps WHERE runner_id = ?
   no index            → SCAN laps   (reads all 10,000 rows)
   index on runner_id  → SEARCH laps USING INDEX idx_laps_runner (runner_id=?)   (jumps to runner 42's 20 rows)
   index on (runner_id, lap_ms) → SEARCH laps USING COVERING INDEX idx_laps_runner_ms (runner_id=?)
   covering: every column the query needs is in the index, so the table itself is never read
   runners WHERE name = 'runner042'         → SEARCH runners USING COVERING INDEX idx_runners_name (name=?)
   runners WHERE lower(name) = 'runner042'  → SCAN runners
   runners WHERE name LIKE '%042'           → SCAN runners
   a function on the column or a leading % hides the index; read the plan before and after every change

═══ web ═══
── the sports-day results page on a phone: 150 ms round trip, 500 KB/s (a teaching model of one page load)
   as built: 30 KB HTML, 80 KB CSS, 900 KB JS, 1200 KB hero image   LCP  5020 ms (poor) ·   2210 KB
   + compress text (gzip/Brotli, text to about 25%)                 LCP  3505 ms (needs improvement) · 1452.5 KB
   + defer the script (no longer blocks the first paint)            LCP  3055 ms (needs improvement) · 1452.5 KB
   + resize the hero and serve AVIF (1200 → 200 KB)                 LCP  1055 ms (good) ·  452.5 KB
   repeat visit: files cached, the HTML revalidated (304)           LCP   450 ms (good) ·      0 KB
   (hashed files: Cache-Control: max-age=31536000, immutable · the HTML: Cache-Control: no-cache)
── CLS: an image with no width/height pushes the text down a quarter of the screen → 0.1875 (needs improvement) · with width/height set → 0.0 (good)
── INP: a tap runs 300 ms of script in one piece → 316 ms (needs improvement) · in 50 ms chunks that yield → 66 ms (good)
   Core Web Vitals (75th percentile of real visits): LCP ≤ 2.5 s · INP ≤ 200 ms (replaced FID in March 2024) · CLS ≤ 0.1

═══ budgets ═══
── a load test (simulated, open model): one server, 10 ms per request, capacity about 100 requests/s
    50 req/s → p50    14 ms · p95    60 ms · p99    98 ms
    70 req/s → p50    25 ms · p95   109 ms · p99   160 ms
    90 req/s → p50    64 ms · p95   280 ms · p99   411 ms
   110 req/s → p50  1716 ms · p95  3122 ms · p99  3187 ms
   the knee: past about 80% busy, p95 climbs fast · above capacity the queue only grows
── budget check, before the fixes: SQL queries per page 51 / 5 ❌ · LCP ms (simulated) 5020 / 2500 ❌ · page weight KB 2210 / 500 ❌ · p95 ms at 70 req/s 109 / 100 ❌ → FAIL (the CI job exits 1)
── budget check, after the fixes : SQL queries per page 1 / 5 ✅ · LCP ms (simulated) 1055 / 2500 ✅ · page weight KB 452.5 / 500 ✅ · p95 ms at 70 req/s 23 / 100 ✅ → PASS
   (after: the profiled fix is assumed to cut the service time from 10 ms to 5 ms — an input of the model)
   budget counts (queries, bytes, calls) in CI — they do not flake like timings; time things on a quiet, fixed machine
── the whole picture: measure → profile → fix the algorithm → memory, caching, I/O, concurrency → queues → queries → the page → budgets

✅ done — every runner timed, the relay is faster
🎒 धडा 01 आधी: तुम्हाला फक्त Python 3 लागेल — बाकी काही नाही. हे lab म्हणजे deterministic शिकवणी models चा संच आहे: मोजमाप खरे आहे (cProfile call counts, tracemalloc, SQLite चा EXPLAIN QUERY PLAN, SQL queries ची मोजलेली नोंद), खर्च आणि वेळा simulated आहेत. जिथे धडा खरी घड्याळाची वेळ दाखवतो, तिथे तो सांगतो तुमचे आकडे वेगळे असतील. ही शाळा कुठे बसते: ही शाळा एक program किंवा service वेगवान करते; Scaling शाळा machines जोडते (CDNs, autoscaling, replicas, queues); Observability शाळा हे सगळे production मध्ये मोजते.

🗺️ संपूर्ण चित्र — एक आकृती, पूर्ण कोर्स

संपूर्ण कोर्स एका कॅनव्हासवर. 4K आवृत्तीसाठी क्लिक करा.

The big picture: measure (latency and percentiles, benchmarking, profiling, complexity), common fixes (memory, caching, I/O and N+1, concurrency and the GIL) and the system (queueing, database query plans, web performance, load tests and budgets)

⏱️ भाग 1 — मोजमाप (धडे 1–4)

एक git branch = एक कल्पना; branch 04 मध्ये धडे 01–04 आहेत. perf/ चा प्रत्येक run सारखाच असतो — खर्च simulated आणि seeded आहेत, आणि lab घड्याळाच्या वेळा नव्हे तर counts वाचते.

1

⏱️ Latency आणि percentiles

Latency विरुद्ध throughput, आणि mean हळू धावपटूंना का लपवतो — p50, p95, p99 आणि tail.lesson-01-latency-percentilesधडा वाचा →आकृती पहा ↗
2

🏁 प्रामाणिक benchmarking

warm-up फेऱ्या, अनेक heats, median आणि पसारा — आणि खरा फायदा आवाजापासून (noise) कसा ओळखायचा.lesson-02-benchmarkingधडा वाचा →आकृती पहा ↗
3

📋 Profiling

प्रशिक्षिकेचा clipboard: काहीही बदलण्याआधी cProfile call counts दाखवतात की वेळ कुठे जातो.lesson-03-profilingधडा वाचा →आकृती पहा ↗
4

📈 व्यवहारातील complexity

bib क्रमांक जोडी-जोडीने तपासणे विरुद्ध आधी sort करणे — O(n²) विरुद्ध O(n log n), मोजून.lesson-04-complexityधडा वाचा →आकृती पहा ↗

🔧 भाग 2 — नेहमीचे उपाय (धडे 5–8)

पुन्हा पुन्हा लागणारे उपाय: कमी धरून ठेवा, दोनदा गणना करू नका, कमी फेऱ्या करा, वाट पाहणे एकमेकांवर चढवा (overlap).

5

🎒 Memory

साहित्याची खोली: सगळे अडथळे एकदम की एका वेळी एक — lists, generators आणि tracemalloc.lesson-05-memoryधडा वाचा →आकृती पहा ↗
6

🗂️ Caching आणि memoization

scoreboard लक्षात ठेवतो: memoization, LRU cache, hit ratio — आणि शिळी उत्तरे.lesson-06-cachingधडा वाचा →आकृती पहा ↗
7

🔁 I/O, N+1 आणि batching

रिलेमधील प्रत्येक बॅटन-बदलाला एक फेरी लागते: N+1 queries, IN आणि JOIN वापरून batching, buffered writes.lesson-07-io-batchingधडा वाचा →आकृती पहा ↗
8

🧵 Concurrency आणि GIL

एक बॅटन, अनेक धावपटू: CPU-bound विरुद्ध I/O-bound, threads, asyncio, processes आणि GIL.lesson-08-concurrencyधडा वाचा →आकृती पहा ↗

🏟️ भाग 3 — संपूर्ण system (धडे 9–12)

तुमच्या code भोवतीची service: queues, database, browser, आणि load tests व budgets वापरून ती वेगवान ठेवणे.

9

🚰 Queueing, Little आणि Amdahl

पाण्याच्या टेबलावरची रांग: utilisation विरुद्ध प्रतीक्षा, Little चा नियम, आणि Amdahl चा सर्वात हळू धावपटू.lesson-09-queueingधडा वाचा →आकृती पहा ↗
10

🗃️ Database query performance

प्रत्येक निकालपत्रक शोधा की index वापरा: EXPLAIN QUERY PLAN, scans, indexes आणि covering indexes.lesson-10-database-queriesधडा वाचा →आकृती पहा ↗
11

📱 Web performance

पालकांच्या फोनवरचे निकालाचे page: LCP, INP, CLS, payload, compression आणि caching headers.lesson-11-web-performanceधडा वाचा →आकृती पहा ↗
12

🏆 Load tests, budgets आणि संपूर्ण चित्र

गर्दीसोबतची रंगीत तालीम, आणि CI मधील पात्रता वेळा — load tests, budgets आणि संपूर्ण नकाशा.lesson-12-load-testing-budgetsधडा वाचा →आकृती पहा ↗
🗣️ मोठ्याने समजावून सांगा — धडा 12 नंतर: (1) mean latency हा वाईट सारांश का आहे? (2) benchmark चे पहिले runs का टाकून द्यायचे? (3) cProfile चा ncalls तुम्हाला असे काय सांगतो जे स्टॉपवॉच सांगत नाही? (4) data दुप्पट झाल्यावर O(n²) कामाचे काय होते? (5) generator केव्हा चुकीचा पर्याय असतो? (6) cache मदत करेल की नाही हे काय ठरवते? (7) N+1 query म्हणजे काय आणि ती कशी दुरुस्त करायची? (8) standard CPython मध्ये threads शुद्ध-Python CPU काम वेगवान का करत नाहीत? (9) 100% utilisation जवळ प्रतीक्षा का फुगते? (10) covering index काय वाचवतो? (11) LCP, INP आणि CLS प्रत्येकी काय मोजतात? (12) CI budgets साठी timings पेक्षा counts का चांगले?
🎓 याच शाळेतून: Scaling · Observability · Database · DSA — तेच उपमांचे विश्व, तीच branch-दर-branch पद्धत.

📐 धड्यांच्या आकृत्या — क्रमांक अनुसरा

प्रत्येक धडा एका क्रमांकित बॉक्स-आणि-बाण आकृतीत — स्वतंत्र पानावरही.

1 ⏱️ Latency आणि percentiles

Latency विरुद्ध throughput, आणि mean हळू धावपटूंना का लपवतो — p50, p95, p99 आणि tail.

🧒 सोप्या शब्दांत

आज शाळेचा क्रीडा दिवस आहे. 1000 धावपटू प्रत्येकी एक फेरी धावतात, आणि कतरिना सगळ्यांची वेळ घेते. बहुतेक जण सुमारे 40 मध्ये पूर्ण करतात. काही अडखळतात, आणि मोजक्या जणांचा बूट निघतो, त्यांना 800 पेक्षा जास्त लागतात. सरासरी 51 येते, पण कोणीच 51 मध्ये धावले नाही! म्हणून कतरिना वेळा रांगेत लावते आणि मधली (40) आणि हळू टोकाची (260) वाचते.

📖 नवे शब्दlatency — एका request ला लागणारा वेळ, जसे एका धावपटूची फेरीची वेळthroughput — दर second किती requests पूर्ण होतात, जसे दर second पूर्ण करणारे धावपटूpercentile — p95 म्हणजे 100 पैकी 95 धावपटू ज्या वेळेत किंवा त्याआधी पोहोचले ती वेळtail — रांगेचे हळू टोक, जसे 100 पेक्षा जास्त वेळ घेणारे धावपटू
1⏱️ 1000 धावपटू, प्रत्येकी एक फेरी — कतरिनाने नोंदवलेल्या अंतिम रेषेवरच्या वेळा, सर्वात वेगवान ते सर्वात हळू क्रमाने17208599Katrinaप्रत्येक धावपटू = एक requestएका फेरीचा वेळ = latencyअंतिम रेषा20406010020050010002000ms (log)tailmean 51.3 ms — असा धावपटू कोणीच नाहीp50 40p95 59p99 260max 136402505007509501000रांगेतील स्थान (सर्वात वेगवान → सर्वात हळू)1000 पैकी 37 ना ≥ 100 ms लागले · 7 ना ≥ 800 ms2🏃‍♀️ throughput = दर सेकंदाला पूर्ण करणारेएक lane · फेरीला 40 ms25 / sचार lanes · तरीही फेरीला 40 ms100 / sजास्त lanes → दर सेकंदाला जास्त धावपटू पूर्ण करतात; कोणतीही फेरी वेगवान होत नाही3🧩 अनेक calls ची वाट पाहणाऱ्या page ला tail भेटतोच1 call1.0%10 calls9.6%100 calls63.4%किमान एक call त्याच्या p99 पेक्षा हळू असलेल्या page loads चा वाटा1 − 0.99ᵏ: 100 calls वर, "100 पैकी 1" हीच नेहमीची गोष्ट होते
⏪ आधी

Teams dashboard वर एकच सरासरी response time दाखवत, आणि काही users एक second पेक्षा जास्त थांबले तरी ते ठीक दिसत असे.

💡 काय

Latency म्हणजे एका धावपटूला लागणारा वेळ; throughput म्हणजे दर second किती पूर्ण करतात. Percentiles नेहमीचा वेळ आणि tail दाखवतात.

⚙️ कसे

कतरिना 1000 धावपटूंची वेळ घेते: mean 51.3 ms, पण p50 40, p95 59, p99 260 आणि max 1364 ms. 37 जणांना 100 ms किंवा जास्त लागले.

🎯 का

Mean 51.3 कोणाचेच वर्णन करत नाही, आणि 100 calls ची वाट पाहणाऱ्या page ला 63.4% loads मध्ये p99 पेक्षा हळू call भेटतो.

🚀 पुढे

पुढच्या धड्यात बदलाची प्रामाणिक वेळ घेतली जाते: warm-up फेऱ्या, अनेक heats, median आणि spread, आणि मगच विजयाचा दावा.

🧪 इथे करून पाहा — कतरिनाची स्टॉपवॉच — किती धावपटू अडखळतात किंवा बूट हरवतात, आणि एक page किती calls ची वाट पाहतो ते बदला
10004111100

पूर्ण धडा 01 वाचा →

2 🏁 प्रामाणिक benchmarking

warm-up फेऱ्या, अनेक heats, median आणि पसारा — आणि खरा फायदा आवाजापासून (noise) कसा ओळखायचा.

🧒 सोप्या शब्दांत

नवे बूट ऐश्वर्याला जलद करतात का हे दीपिकाला जाणून घ्यायचे आहे. कतरिना तिची वेळ एकदाच घेते, सकाळी थंडीत: 42 seconds. नंतर: 12. बूटांमुळे नाही, फक्त शरीर गरम झाल्यामुळे! म्हणून त्या आधी warm-up फेऱ्या घेतात, मग अनेक heats, आणि मधली वेळ पाहतात. नव्या वेळा स्पष्टपणे कमी असतील तरच खरा विजय.

📖 नवे शब्दbenchmark — versions ची तुलना करण्यासाठी code ची वेळ काळजीपूर्वक, अनेक वेळा मोजणेwarm-up — सुरुवातीच्या हळू runs, जेव्हा caches अजून थंड असतातmedian — सगळ्या वेळा रांगेत लावल्यावर मधली वेळ, जसे 12.4 msnoise — runs मधले छोटे यादृच्छिक बदल, जे खरा फरक नसतात
1🏁 कतरिना version A ची वेळ 20 वेळा मोजते (simulated स्टॉपवॉच) — आधी थंड फेऱ्या, एक spike, मग स्थिर010203040ms42.1#131.7#221.6#312.1#412.9#512.8#612.4#711.7#819.9#912.9#1012.2#1111.6#1212.6#1312.6#1411.2#1512.2#1611.4#1712.6#1812.7#1911.6#20सर्व 20 चा mean: 15.5 ms ✗17 warm runs चा median: 12.4 ms ✓warm-up — मोजले नाहीदुसरा program जागा झालाKatrinaएक run म्हणतो42.1 ms —थंड, हेसत्य नाही2⚖️ दोन एकट्या runs नव्हे, तर मधले अर्धे भाग (p25–p75) तुलना कराAB1 छोटा बदल7891011121314151617181920msboxes एकमेकांवर येतात → स्पष्ट फरक नाहीmedian 12.4 vs 12.0AB2 खरा उपाय7891011121314151617181920msB वेगवान आहे (1.53x)medians 12.4 vs 8.1 ms · मधले अर्धे भाग 11.7–12.7 vs 7.8–8.4box = 17 warm runs चा मधला अर्धा भाग · काळी रेषा = median · ठिपके = प्रत्येक run
⏪ आधी

लोक code ची वेळ एकदाच घेत, 42.1 ms मग 12 ms पाहून नवीन version तिप्पट जलद म्हणत, खरे तर ते फक्त warm झाले होते.

💡 काय

प्रामाणिक benchmark warm-up runs सोडून देतो, अनेक heats घेतो, आणि median सोबत मधला अर्धा भाग (p25–p75) सांगतो.

⚙️ कसे

कतरिना A 20 वेळा चालवते: पहिले runs 42.1, 31.7, 21.6 ms. Mean 15.5 ms म्हणतो; 17 warm runs चा median 12.4 ms म्हणतो.

🎯 का

B1 (12.0 विरुद्ध 12.4 ms) A शी overlap होतो, म्हणून तो noise आहे. B2 (8.1 ms, 7.8–8.4) overlap होत नाही: खरा 1.53x विजय.

🚀 पुढे

पुढचा धडा विचारतो वेळ कुठे जातो: दीपिकाचा clipboard, cProfile, कोणते function किती वेळा चालते ते मोजतो.

🧪 इथे करून पाहा — A विरुद्ध B — किती warm-up runs टाकता, किती heats, आणि laptop किती गोंगाट करतो
873208

पूर्ण धडा 02 वाचा →

3 📋 Profiling

प्रशिक्षिकेचा clipboard: काहीही बदलण्याआधी cProfile call counts दाखवतात की वेळ कुठे जातो.

🧒 सोप्या शब्दांत

Results छापायला खूप वेळ लागतो, आणि प्रत्येकाचा वेगळा अंदाज आहे. प्रशिक्षक दीपिका अंदाज करत नाही. ती clipboard घेते आणि प्रत्येक कामावर खूण करते. मोठ्या यादीत धावपटू शोधण्याला 2000 खुणा मिळाल्या, आणि प्रत्येक शोध 300 नावे एकेक करून पाहतो. म्हणून ती bib क्रमांकानुसार लावलेली card box बनवते, आणि हळू काम संपते.

📖 नवे शब्दprofiler — कोणती functions चालतात आणि किती वेळा ते नोंदवणारे toolhot spot — जिथे बहुतेक काम होते ती एक जागा, जसे find_runnerncalls — cProfile ने मोजलेली calls ची संख्या, जसे find_runner 2000 वेळा
1📋 दीपिकाचा clipboard: cProfile ncallsresults_slow — 300 धावपटू, 2000 फेऱ्याfind_runner2,000×best_of4×results_slow1×Dipikacounts, वेळा नव्हे2🔎 प्रत्येक lookup 300 धावपटूंची यादी एकेक करून तपासतोफेरी 1 bib 49 ची आहे — find_runner सुरुवातीपासून सुरू करतो:145209094275271141285654…49#151या एका फेरीसाठी 151 तुलना — आणि फेऱ्या 2000 आहेतfind_runner ने केलेल्या तुलना297,098 (≈ प्रति lookup 149)उपायानंतर0printer ठीक होता — LOOKUP हाच hot spot होता3🗂️ उपाय: bib नुसार dict एकदाच बनवा — प्रत्येक lookup एकाच झटक्यात1…49…300by_bib = {bib: runner}एकदाच बनवले, 300 कार्डे1 झटकाresults_fastbest_of4×results_fast1×find_runner — गेलाप्रत्येक house चा सर्वोत्तम 100 m (ms) — दोन्ही प्रकारे सारखाचनिळा11,000हिरवा11,006red11,006पिवळा11,005निकाल तेच: Trueआधी मोजा: मंद भाग क्वचितच तुम्ही अंदाज केलेल्या ठिकाणी असतो,आणि बहुधा एकच hot function त्यातला बहुतेक वेळ खाते
⏪ आधी

Results हळू का छापले जातात याचा सगळे अंदाज करत: जुना printer, कठीण बेरजा. कोणी मोजले नाही, म्हणून उपाय चुकत.

💡 काय

Profiler प्रत्येक function call नोंदवतो. cProfile चे ncalls एकही ओळ बदलण्याआधी hot spot दाखवतात.

⚙️ कसे

दीपिका 300 धावपटू आणि 2000 laps profile करते: find_runner 2000 वेळा चालते आणि 297,098 comparisons करते.

🎯 का

एकदाच बनवलेली bib नुसार dict प्रत्येक lookup एका झटक्यात करते: comparisons 0 होतात आणि results तेच राहतात.

🚀 पुढे

पुढचा धडा काम कसे वाढते ते मोजतो: bibs जोडीने तपासणे, sort करून, किंवा set वापरून, n दुप्पट होताना.

🧪 इथे करून पाहा — दीपिकाचा clipboard — धावपटूंची यादी आणि laps वाढवा, मग fix चालू करा
300200013

संपूर्ण धडा 03 वाचा →

4 📈 प्रत्यक्षातील Complexity

bib क्रमांक जोडी-जोडीने तपासणे विरुद्ध आधी sort करणे — O(n²) विरुद्ध O(n log n), मोजून.

🧒 सोप्या शब्दांत

प्रत्येक धावपटूला bib क्रमांक मिळतो, आणि कोणतेही दोन सारखे नसावेत. ऐश्वर्या तपासते. 2000 धावपटूंच्या प्रत्येक जोडीची तुलना केली तर जवळजवळ 20 लाख checks लागतात. आधी त्यांना क्रमाने लावले, म्हणजे सारखे क्रमांक शेजारी उभे राहतात, तर सुमारे 21,000. प्रत्येक क्रमांकासाठी एक चौकट असलेल्या tick sheet ला फक्त 2000. उत्तर एकच, काम खूप वेगळे.

📖 नवे शब्दbig-O — data वाढल्यावर काम कसे वाढते ते, जसे O(n²)O(n²) — data दुप्पट, काम चौपट, जसे प्रत्येक जोडी तपासणेO(n log n) — दुप्पट data साठी दुप्पटीपेक्षा थोडे जास्त काम, जसे sorting
1📈 दुहेरी bib शोधण्यासाठी लागणाऱ्या तपासण्या (log scale)1001k10k100k1Mn = 250n = 500n = 1000n = 20001,999,000प्रत्येक जोडी · O(n²)21,436sort + शेजारी · O(n log n)2,000खूण-पत्रक (set) · O(n)दुप्पट केल्यावर ×4×2 पेक्षा थोडे जास्त×22🔢 मोजलेल्या तपासण्याnप्रत्येक जोडीsortset25031,1251,921250500124,7504,3555001,000499,5009,7241,0002,0001,999,00021,4362,000n दुप्पट करा: प्रत्येक जोडी पद्धत 4× काम करते,sorting 2× पेक्षा थोडे जास्त, set बरोबर 2×n = 2000: जोड्या sorting पेक्षा 93× तपासण्या करतातbig-O म्हणजे काम कसे वाढते —एका run ला किती वेळ लागतो ते नव्हे3🏷️ 6 bibs मध्ये दुहेरी आहे का, हे ऐश्वर्या तीन प्रकारे तपासू शकतेप्रत्येक जोडी: 6 साठी 15 तपासण्या4271938825n(n−1)/2 → O(n²)sort करा, मग शेजारी पाहा3719254288sort साठी सुमारे n log₂ n + n−1 → O(n log n)खूण-पत्रक: प्रत्येकाकडे एकदाच पाहणे4271938825n वेळा पाहणे → सरासरी O(n)Aishwaryaप्रत्येक पद्धतीत उत्तर तेच — पण कामाचे प्रमाण खूप वेगळे
⏪ आधी

250 test rows वर जलद असलेला code 2000 खऱ्या rows ला भेटला, आणि त्याचे काम कसे वाढते हे कोणी विचारले नव्हते.

💡 काय

Big-O सांगतो n सोबत काम कसे वाढते: प्रत्येक जोडी O(n²), sort करून पाहणे O(n log n), set म्हणजे O(n).

⚙️ कसे

ऐश्वर्या 2000 bibs तपासते: प्रत्येक जोडीला 1,999,000 checks, sorting ला 21,436, आणि set ला 2,000 लागतात.

🎯 का

n दुप्पट केल्यास जोड्यांचे काम 4×, sorting थोडे जास्त 2×, set बरोबर 2× होते. 2000 वर जोड्या sorting च्या 93× काम करतात.

🚀 पुढे

पुढचा धडा memory पाहतो: list मध्ये 100,000 hurdles एकदम बाहेर आणणे, की generator ने एका वेळी एक.

🧪 इथे करून पाहा — दुहेरी bib आहे का? तीन प्रकारे तपासण्या मोजा — मग n दुप्पट करा
2000

संपूर्ण धडा 04 वाचा →

5 🎒 Memory

साहित्याची खोली: सगळे अडथळे एकदम की एका वेळी एक — lists, generators आणि tracemalloc.

🧒 सोप्या शब्दांत

Hurdles शर्यतीला साहित्याच्या खोलीतून 100,000 hurdles लागतात. एक पद्धत: सगळे आधी बाहेर आणायचे, आणि मैदान भरून जाते. दुसरी पद्धत: ऐश्वर्या एक hurdle आणते, धावपटू ते उडी मारून पार करते, आणि ती पुढचे आणते. शर्यत आणि उत्तर तेच, पण मैदानावर एका वेळी एकच असते. ते मोजायचे किंवा पुन्हा पाहायचे असतील तर पूर्ण ढीग लागतो.

📖 नवे शब्दlist — सगळे items एकाच वेळी ठेवणारी पेटी, जसे 100,000 resultsgenerator — एका वेळी एक item बनवतो आणि वापरल्यावर विसरतोtracemalloc — program ने किती memory वापरली ते मोजणारे Python चे tool
1📋 list: आधी सगळे अडथळे बाहेर आणाकोठीएकाच वेळी धरलेले: 100,000मैदान भरले आहे —इतर कशालाहीजागा नाहीsum([i * i for i in range(n)])2🎒 generator: एका वेळी एक अडथळाकोठीऐश्वर्या पुढचा आणतेउडी मारली, मग परत कोठीतएकाच वेळी धरलेले: 1sum(i * i for i in range(n))3📉 100,000 वर्गांची बेरीज चालू असताना memory (आकार)memoryवेळ →list: 100,000 वर्ग तयार करतेमग त्यांची बेरीज करतेtracemalloc peakgenerator: एका वेळी एक वर्गtracemalloc peak: list ला generator पेक्षा 100× पेक्षा जास्त memory लागते: Trueदोन्ही प्रकारे उत्तर तेच: 333,328,333,350,0004🔁 list ठेवा जेव्हा तुम्हाला हवे असेल…↺ते पुन्हावापरलेला generator काहीच देत नाही⇄कोणताही क्रमindexing, sorting, उलटा क्रम#त्याची लांबीlen() ला प्रत्येक item लागतोएकदाच फिरायचे → stream करा
⏪ आधी

Programs प्रत्येक result ची पूर्ण list बनवत, मग प्रत्येक एकदाच वापरत, आणि data वाढल्यावर memory संपत असे.

💡 काय

List सगळे items एकाच वेळी ठेवते; generator एका वेळी एक item बनवतो. tracemalloc सर्वात जास्त memory मोजते.

⚙️ कसे

0..99,999 च्या वर्गांची बेरीज: list एकाच वेळी 100,000 results ठेवते, generator फक्त 1. दोन्हीचे उत्तर एकच येते.

🎯 का

tracemalloc दाखवते की त्याच 333,328,333,350,000 साठी list ला generator च्या 100× पेक्षा जास्त memory लागते.

🚀 पुढे

पुढचा धडा काम दोनदा करणे थांबवतो: memoization आणि LRU scoreboard, आणि मदत होते का हे ठरवणारा hit ratio.

🧪 इथे करून पाहा — list की generator? — n बदला आणि निकाल कशासाठी हवेत ते सांगा
100

संपूर्ण धडा 05 वाचा →

6 🗂️ Caching आणि memoization

scoreboard लक्षात ठेवतो: memoization, LRU cache, hit ratio — आणि शिळी उत्तरे.

🧒 सोप्या शब्दांत

पालक results office ला धावपटूंच्या वेळा विचारत राहतात. प्रत्येक वेळी ऐश्वर्या मागच्या खोलीत जाते आणि मोठे पुस्तक उघडते: 50 seconds. म्हणून ती दाराजवळ एक छोटा scoreboard लावते. उत्तर board वर असेल तर 1 second लागतो. बहुतेक पालक त्याच अंतिम फेरीतल्या धावपटूंबद्दल विचारतात, म्हणून छोटा board खूप मदत करतो. पण दुरुस्त केलेली वेळ board वर जुनीच राहू शकते.

📖 नवे शब्दcache — आधी काढलेल्या उत्तरांचा छोटा, जलद साठाhit ratio — cache उत्तर देतो त्या प्रश्नांचा वाटा, जसे 44.9%LRU — भरल्यावर, सर्वात जास्त काळ कोणी न विचारलेले उत्तर विसरणेstale — आता खरे नसलेले जुने उत्तर
1🌳 साध्या पद्धतीने fib(5): 15 calls — लाल = आधीच काढलेले102131024102135तेच छोटे प्रश्न, पुन्हा पुन्हा विचारलेलेlru_cache सह534231201hit6 calls, 3 hits2🔢 fib(25) = 75,025साधे recursion हे काम242,785 वेळा करतेmemoized (lru_cache)26 वेळा (23 cache hits)9,337× कमी काम — 26 ची पट्टीया scale वर रेषेपेक्षाही पातळ आहे@lru_cache(maxsize=None)def fib(k): ...फक्त pure function साठीच सुरक्षित3📋 दाराजवळचा scoreboardमागची खोली (मोठी वही)धावपटू 007 — वारंवार विचारला जातोधावपटू 001 — वारंवार विचारला जातोधावपटू 013 — वारंवार विचारला जातो… 100 साठी जागाAishwaryamiss: 50 mshit: 1 msभरला? सर्वात जास्त काळ न वापरलेले नाव पुसा (LRU)दुरुस्त केलेली वेळ refresh होईपर्यंत board वर जुनीच (stale) राहते4📊 500 धावपटूंवर 2000 requests — मोठा board025507515.4%42.5LRU 1033.0%33.9LRU 5044.9%28.0LRU 10067.7%16.8LRU 250hit ratio %प्रति request सरासरी ms (hit 1 · miss 50)
⏪ आधी

पालक तेच थोडे प्रश्न पुन्हा पुन्हा विचारत असले तरी प्रत्येक request सगळे काम पुन्हा करत असे.

💡 काय

Cache आधी काढलेली उत्तरे ठेवतो. Memoization एका function ला cache करते; LRU cache सर्वात जास्त काळ न वापरलेले विसरतो.

⚙️ कसे

fib(25) साधारण पद्धतीने 242,785 वेळा काम करते, lru_cache ने 26 वेळा. 100 चा LRU 44.9% hit करतो: सरासरी 28.0 ms.

🎯 का

Hit ला 1 ms आणि miss ला 50 लागतात, म्हणून hit ratio सगळे ठरवतो; 250 चा board 67.7% आणि 16.8 ms देतो.

🚀 पुढे

पुढचा धडा मैदान ओलांडण्याच्या फेऱ्या मोजतो: N+1 queries, IN आणि JOIN ने batching, आणि buffered writes.

🧪 इथे करून पाहा — दाराजवळचा scoreboard — त्याचा आकार, miss ची किंमत, प्रश्न किती एकतर्फी आहेत
100150325

संपूर्ण धडा 06 वाचा →

7 🔁 I/O, N+1 आणि batching

रिलेमधील प्रत्येक बॅटन-बदलाला एक फेरी लागते: N+1 queries, IN आणि JOIN वापरून batching, buffered writes.

🧒 सोप्या शब्दांत

दीपिकाला मैदानापलीकडच्या office मधून प्रत्येक धावपटूचा best lap हवा आहे. हळू पद्धत: यादीसाठी एक फेरी, मग प्रत्येक धावपटूसाठी एक फेरी. म्हणजे 51 फेऱ्या! चांगली: सगळ्या नावांची यादी घेऊन एक फेरी, 2 फेऱ्या. सर्वोत्तम: प्रत्येक धावपटू आणि त्याचा best lap एकदम मागणे, 1 फेरी. प्रत्येक फेरीला चालायलाच वेळ लागतो, म्हणून एका फेरीत अनेक गोष्टी न्या.

📖 नवे शब्दN+1 queries — यादीसाठी एक query, मग प्रत्येक item साठी आणखी एक, जसे 51batching — अनेक items एकाच फेरीत पाठवणे, जसे IN (...)buffering — पोस्ट करण्याआधी पाकीट भरणे, जसे 10,000 नाही तर 26 writes
1🏃‍♀️ दीपिका मैदानापलीकडच्या office मधून 50 धावपटूंपैकी प्रत्येकाचा सर्वोत्तम lap आणतेDipikaoffice (database)N+1: आधी यादी, मग प्रत्येक धावपटूसाठी एक फेरी51 queries51 msbatched: आधी यादी, मग सगळे 50 एकाच फेरीत2 queries2 msएक JOIN + GROUP BY1 query1 msप्रति फेरी 1 ms थांबणेतिन्ही प्रकारे उत्तर तेच: True · इथे SQLite in-process आहे, म्हणून फेऱ्या फुकट; network वर प्रत्येकीला एक round trip लागतोN+1: SELECT MIN(lap_ms) FROM laps WHERE runner_id = ? — 50 वेळाfix: … WHERE runner_id IN (?, ?, …) किंवा … JOIN laps … GROUP BY r.id2✉️ 10,000 निकाल-ओळी लिहिणे (210,000 bytes): प्रत्येक कार्ड पोस्ट करा, की आधी पाकिटे भराunbuffered — प्रत्येक ओळ हा स्वतंत्र raw writeप्रत्येक छोटे कार्ड = 50 writes10,000 raw writesbuffered (8 KB) — पाकीट भरल्यावरच पोस्ट होते26 raw writes210,000 bytes ÷ प्रति पाकीट 8,192 → 26 पाकिटे (शेवटचे अर्धवट भरलेले)POSTप्रत्येक फेरीला ठरलेला टोल पडतो —एका फेरीत अनेक गोष्टी न्या
⏪ आधी

ORM loop एक ओळ दिसत असे पण प्रत्येक row साठी एक query चालवत असे; 3 test rows ला दिसत नसे, खऱ्या data ला हळू.

💡 काय

Database किंवा disk च्या प्रत्येक फेरीला ठराविक toll लागतो. Batching एका फेरीत अनेक गोष्टी नेते: IN, JOIN, write buffer.

⚙️ कसे

50 धावपटूंचे best laps: N+1 51 queries चालवते, IN (...) 2, एक JOIN 1. 10,000 ओळी: 10,000 raw writes, किंवा buffered 26.

🎯 का

प्रत्येक round trip ला 1 ms असेल तर 51 ms विरुद्ध 2 ms विरुद्ध 1 ms थांबणे, आणि N+1 प्रत्येक नव्या धावपटूसोबत वाढते.

🚀 पुढे

पुढचा धडा थांबणे एकमेकांवर आणतो: threads, asyncio आणि processes, आणि GIL, track वरचा एकच baton.

🧪 इथे करून पाहा — मैदानापलीकडच्या फेऱ्या — धावपटू, round trip आणि पाकिटाचा आकार
50110000

संपूर्ण धडा 07 वाचा →

8 🧵 Concurrency आणि GIL

एक बॅटन, अनेक धावपटू: CPU-bound विरुद्ध I/O-bound, threads, asyncio, processes आणि GIL.

🧒 सोप्या शब्दांत

Relay संघात 8 धावपटू आणि एकच baton आहे, आणि फक्त baton हातात असलेलीच धावू शकते. पाण्याच्या टेबलाच्या कामात प्रत्येक जण थोडे धावते, मग पाण्याची वाट पाहते, आणि वाट पाहायला baton लागत नाही. म्हणून आठही जणी एकत्र थांबतात: सुमारे 140, 840 नाही. Sprints ला पूर्ण वेळ baton लागतो, म्हणून ते 800 घेतात. जलद sprints साठी जास्त tracks वापरा, प्रत्येकाचा स्वतःचा baton.

📖 नवे शब्दGIL — Python चा एकच baton: एका वेळी एकच thread Python चालवतोI/O-bound — बहुतेक वेळ उत्तराची वाट पाहणे, जसे पाण्याचे टेबलCPU-bound — बहुतेक वेळ गणना करणे, जसे baton लागणारी sprintprocess — स्वतःची memory आणि स्वतःचा GIL असलेला वेगळा program
1🥢 8 धावपटू, एकच बॅटन: I/O-bound tasks (5 ms Python, मग उत्तरासाठी 100 ms थांबणे)task 1task 2task 3task 4task 5task 6task 7task 8020406080100120140msPython काम वाटून, 40 msGIL खाली 8 threads — बॅटन 1 ms च्या पाळ्यांनी फिरते140 ms ला पूर्ण840 ms नव्हेGILPython चालवत आहे (बॅटन हातात)उत्तराची वाट (I/O) — बॅटनची गरज नाहीतयार, बॅटनची वाट पाहतthreads आणि asyncio वाट पाहण्याचे वेळ एकमेकांवर आणतात — thread I/O ची वाट पाहत असताना GIL सोडला जातो2🏟️ तेच 8 tasks, पाच प्रकारे (शिकवण्यासाठीचे model)I/O-bound (8 × 5 ms Python + 100 ms प्रतीक्षा)CPU-bound (8 × 100 ms Python)एकामागून एक1 बॅटन840 ms800 ms8 threads, एकच बॅटन (GIL)1 बॅटन140 ms800 msasyncio, 1 thread, 8 tasks1 बॅटन140 ms800 ms4 cores वर 4 processes (+50 ms सुरुवात)4 बॅटन260 ms250 ms8 threads, free-threaded, 4 cores4 बॅटन110 ms200 msप्रतीक्षेला बॅटन लागत नाही: threads आणि asyncio प्रतीक्षा एकमेकांवर चढवतातPython कामाला बॅटन लागतो: processes (प्रत्येकाचा स्वतःचा GIL), किंवा free-threaded build (3.13+)
⏪ आधी

Programs एका वेळी एकच task करत आणि network कडून प्रत्येक उत्तराची वाट पाहत रिकामे बसत.

💡 काय

GIL म्हणजे एकच baton: एका वेळी एकच thread Python चालवतो. I/O ची वाट पाहायला baton लागत नाही; processes स्वतःचा आणतात.

⚙️ कसे

5 ms Python आणि 100 ms थांबणे असे 8 tasks: एकामागून एक 840 ms, threads किंवा asyncio ने 140 ms, free-threaded 110 ms.

🎯 का

100 ms Python चे 8 tasks GIL खाली threads ने 800 ms घेतात, पण 4 processes वर 250 ms आणि free-threaded 200 ms.

🚀 पुढे

पुढचा धडा पाण्याच्या टेबलाजवळ उभा राहतो: 100% busy जवळ थांबणे का वाढते, Little's law आणि Amdahl चा सर्वात हळू टप्पा.

🧪 इथे करून पाहा — एक बॅटन, अनेक धावपटू — Python काम आणि प्रतीक्षा मिसळा, मग 8 tasks चालवण्याची पद्धत निवडा
851004

पूर्ण धडा 08 वाचा →

9 🚰 रांगा, Little आणि Amdahl

पाण्याच्या टेबलावरची रांग: utilisation विरुद्ध प्रतीक्षा, Little चा नियम, आणि Amdahl चा सर्वात हळू धावपटू.

🧒 सोप्या शब्दांत

पाण्याचे एकच टेबल आहे, आणि ऐश्वर्या 10 seconds मध्ये cup भरते. धावपटू अधूनमधून आले तर प्रत्येकाला सुमारे 20 लागतात. टेबल 90% वेळ busy असेल तर सुमारे 100. 99% ला सुमारे 1000! धावपटू घोळक्याने येतात, आणि busy टेबल कधीच मागे पडलेले भरून काढत नाही. आणि relay मध्ये फक्त एकच धावपटू धावू शकेल असा एक टप्पा पूर्ण संघाला मर्यादा घालतो.

📖 नवे शब्दutilisation — server किती वेळ busy असतो ते, जसे 90%Little's law — आतले लोक = येण्याचा दर × प्रत्येकाचा थांबण्याचा वेळ, जसे 80 × 0.05 = 4Amdahl's law — कोणीही वाटून घेऊ शकत नाही असा भाग speed-up ला मर्यादा घालतो, जसे 10×
1🚰 एक पाण्याचे टेबल, एका कपाला 10 msऐश्वर्या पाणी देतेरांगधावपटू कधीही येतात —कधी कधी तिघे एकदमटेबलावरचा वेळ = 10 ms ÷ (1 − busy)2📈 टेबल किती busy आहे विरुद्ध टेबलावरचा वेळ (M/M/1)025050075010000%25%50%75%100%msutilisation (busy)sim 519busyसूत्रsimulated50%20 ms20 ms80%50 ms49 ms90%100 ms104 ms95%200 ms186 ms99%1000 ms519 msसूत्र 10 ÷ (1 − ρ)simulated, 20,000 धावपटू80% नंतर:the knee (वळण)3🧮 Little चा नियम: L = λ × Wटेबलावर 4 धावपटूλ = दर सेकंदाला 80 धावपटू येतातW = प्रत्येक जण 50 ms (0.05 s) थांबतोL = 80 × 0.05 = सरासरी 4 तिथेकोणत्याही स्थिर system साठी हे खरे आहे:चालू requests = दर × प्रत्येकाचा वेळउदा. 4 busy workers, किंवा 4 उघडी connections4🏃‍♀️ Amdahl: रिलेचा 90% भाग वाटता येतो, 10% एकाच धावपटूचा टप्पा आहे1 धावपटू1× (एकटा)2 धावपटू1.82× वेगवान4 धावपटू3.08× वेगवान8 धावपटू4.71× वेगवान16 धावपटू6.40× वेगवानअमर्याद10.00× वेगवान — हीच मर्यादाserial टप्पा (10%) — कोणीही मदत करू शकत नाहीवाटता येणारा भाग (90%) धावपटूंमध्ये विभागलेलापट्टीची लांबी = लागलेला वेळ
⏪ आधी

पैसे वाचवण्यासाठी servers 95% busy चालत, आणि traffic थोडे वाढल्यावर latency का उडी मारते हे कोणी सांगू शकत नसे.

💡 काय

Queueing theory busy असणे आणि थांबणे जोडते: वेळ = service ÷ (1 − utilisation). Little: L = λW. Amdahl speed-up ला मर्यादा घालतो.

⚙️ कसे

ऐश्वर्या 10 ms मध्ये cup भरते: 50% busy असताना धावपटूला 20 ms, 90% ला 100 ms, 99% ला 1000 ms टेबलाजवळ लागतात.

🎯 का

80 धावपटू/s × 50 ms = टेबलाजवळ 4. 10% काम serial असेल तर अगणित धावपटू असले तरी जास्तीत जास्त 10.00× speed-up.

🚀 पुढे

पुढचा धडा results office उघडतो: SQLite मध्ये EXPLAIN QUERY PLAN, scan, index आणि covering index.

🧪 इथे करून पाहा — पाण्याचे टेबल — utilisation 100% कडे ढकला, मग रिले वाटून द्या
9010908

पूर्ण धडा 09 वाचा →

10 🗃️ Database query performance

प्रत्येक निकालपत्रक शोधा की index वापरा: EXPLAIN QUERY PLAN, scans, indexes आणि covering indexes.

🧒 सोप्या शब्दांत

Results office मध्ये एका ढिगात 10,000 lap sheets आहेत. एक पालक runner 42 चे laps विचारतात. मदतीशिवाय ऐश्वर्या 20 शोधण्यासाठी प्रत्येक sheet वाचते. हा scan आहे. कतरिनाच्या runner क्रमांकानुसार लावलेल्या card box ने ती थेट 42 वर जाते आणि फक्त 20 sheets आणते. प्रत्येक card वर lap ची वेळ लिहिली असेल तर तिला sheets ची गरजच पडत नाही.

📖 नवे शब्दindex — योग्य rows कडे बोट दाखवणारी क्रमाने लावलेली card boxscan — table ची प्रत्येक row वाचणे, जसे सगळ्या 10,000 sheetscovering index — query ला लागणारा प्रत्येक column आधीच असलेला indexquery plan — query साठी database चा आराखडा, EXPLAIN दाखवतो
1🐢 index नाही — एक SCAN2🗂️ runner_id वर index3🃏 covering indexऐश्वर्या प्रत्येक पान वाचते20 शोधण्यासाठी सगळ्या 10,000 rowsEXPLAIN QUERY PLANSCAN lapstable वरपासून खालपर्यंत वाचले जाते4041424344runner_id नुसार कार्डे20 rows42 वर थेट जा, मग त्याच्या rows आणा10,000 नाही, फक्त 20 rows वाचतेEXPLAIN QUERY PLANSEARCH laps USING INDEX idx_laps_runner(runner_id=?)42lap 40 ms42lap 42 ms42lap 42 mslap वेळ कार्डावरच आहेtable कधीच वाचले जात नाहीquery ला लागणारा प्रत्येक column index मध्येच आहेEXPLAIN QUERY PLANSEARCH laps USING COVERING INDEXidx_laps_runner_ms (runner_id=?)4🙈 runners(name) वरचा index — आणि तो लपवणारी दोन WHERE clausesSELECT id FROM runners WHERE name = 'runner042'SEARCH runners USING COVERING INDEX idx_runners_name (name=?)SELECT id FROM runners WHERE lower(name) = 'runner042'SCAN runnersSELECT id FROM runners WHERE name LIKE '%042'SCAN runnerslower(name) ला प्रत्येक नाव lower-case करावे लागते, आणि सुरुवातीच्या % ला ठरलेली सुरुवात नसते — क्रमाने लावलेली कार्डे मदत करू शकत नाहीतउपाय: lower(name) वर expression index → SEARCH … USING COVERING INDEX · प्रत्येक बदलाआधी आणि नंतर plan वाचा
⏪ आधी

Queries लहान table वर ठीक चालत, मग production मध्ये 10,000 पैकी प्रत्येक row वाचत, आणि कोणी plan पाहिला नाही.

💡 काय

EXPLAIN QUERY PLAN database काय करेल ते दाखवतो: SCAN प्रत्येक row वाचतो, SEARCH USING INDEX योग्य rows वर उडी मारतो.

⚙️ कसे

Runner 42 चे laps: index नसताना सगळ्या 10,000 rows चा SCAN; runner_id वरचा index 20 वर उडी घेतो; (runner_id, lap_ms) covering आहे.

🎯 का

Covering index table कधीच वाचत नाही. पण lower(name) किंवा LIKE '%042' index लपवतो आणि परत SCAN runners होतो.

🚀 पुढे

पुढचा धडा पालकांच्या phone वर results page उघडतो: LCP, INP, CLS, compression आणि caching headers.

🧪 इथे करून पाहा — EXPLAIN QUERY PLAN — indexes जोडा, query बदला, आणि plan वाचा

पूर्ण धडा 10 वाचा →

11 📱 Web performance

पालकांच्या फोनवरचे निकालाचे page: LCP, INP, CLS, payload, compression आणि caching headers.

🧒 सोप्या शब्दांत

कतरिनाची आई parking मध्ये phone वर results page उघडते. Page जड आहे, आणि मोठ्या photo साठी तिला 5 seconds थांबावे लागते. ती एक button दाबते, आणि क्षणभर काहीच होत नाही. मग photo आल्यावर मजकूर खाली उडी मारतो. ऐश्वर्या files लहान करते, script ला थांबायला सांगते, लहान photo वापरते आणि त्याची जागा राखून ठेवते. आता page सुमारे 1 second मध्ये दिसते.

📖 नवे शब्दLCP — page वरची सर्वात मोठी गोष्ट दिसायला लागणारा वेळ, जसे 5020 msINP — tap नंतर screen बदलायला लागणारा वेळ, जसे 316 msCLS — load होताना page किती उडी मारते ते, जसे 0.1875compression — पाठवण्याआधी text files लहान करणे
1📱 कतरिनाची आई निकालाचे पान उघडते: 150 ms round trip, 500 KB/s (शिकवण्यासाठीचे मॉडेल)010002000300040005000msचांगले ≤ 2.5 sवाईट > 4 sजसे बांधलेCSS + hero + JSLCP 5020 ms · वाईट2210 KB+ text compress कराCSS + hero + JSLCP 3505 ms · सुधारणा हवी1452.5 KB+ script defer कराCSS + heroLCP 3055 ms · सुधारणा हवी1452.5 KB+ लहान AVIF heroCSS + heroLCP 1055 ms · चांगले452.5 KBपुन्हा भेटLCP 450 ms · चांगले0 KBbytesconnect: TCP + TLS, 2 round tripsHTML: 1 round trip + त्याचे bytesपहिला paint कशाची वाट पाहतोhashed files: Cache-Control: max-age=31536000, immutable · HTML: Cache-Control: no-cache2📰 CLS — फोटो आल्यावर मजकूर उडी मारतोफोटो येतोमजकूर खाली ढकललापाव स्क्रीनCLS 0.1875 · सुधारणा हवीजागा राखून ठेवलीwidth + heightकाहीच हलत नाहीCLS 0.0 · चांगलेस्क्रीनचा 0.75 भाग हलला × उंचीच्या 0.25 ने = 0.18753👆 INP — एक tap, मग पुढचा paintएक 300 ms tasktap316 ms ला paintINP 316 ms · सुधारणा हवीyield करणारे 50 ms चे तुकडेtap66 ms ला paintINP 66 ms · चांगलेCore Web Vitals (p75): LCP ≤ 2.5 s · INP ≤ 200 ms · CLS ≤ 0.1
⏪ आधी

Pages office च्या जलद Wi-Fi वर तपासले जात, आणि कमकुवत phone signal वर पालकांना होणारा 5 second चा उशीर कोणी पाहिला नाही.

💡 काय

p75 वर Core Web Vitals: LCP (मुख्य content दिसणे) ≤ 2.5 s, INP (tap ला उत्तर) ≤ 200 ms, CLS (layout उड्या) ≤ 0.1.

⚙️ कसे

जसे बनवले तसे LCP 5020 ms आणि 2210 KB. Text compress: 3505 ms. Script defer: 3055 ms. 200 KB चा AVIF hero: 1055 ms.

🎯 का

Width आणि height दिल्याने CLS 0.1875 वरून 0.0; 50 ms chunks ने INP 316 वरून 66 ms; पुन्हा भेट 450 ms घेते.

🚀 पुढे

शेवटचा धडा गर्दीसह सराव करतो: load test knee शोधतो, आणि CI मधले budgets प्रत्येक उपाय जागी ठेवतात.

🧪 इथे करून पाहा — फोनवर निकालाचे पान — network, चार उपाय, एक layout shift आणि एक लांब tap
1505007525300

पूर्ण धडा 11 वाचा →

12 🏆 Load tests, budgets आणि संपूर्ण चित्र

गर्दीसोबतची रंगीत तालीम, आणि CI मधील पात्रता वेळा — load tests, budgets आणि संपूर्ण नकाशा.

🧒 सोप्या शब्दांत

क्रीडा दिवसाआधी शाळा सराव घेते आणि पाण्याच्या टेबलाकडे अधिकाधिक धावपटू पाठवते. दर second 50 ला सगळे ठीक. 90 ला रांग लांब होते. 110 ला ती वाढतच जाते. मग दीपिका भिंतीवर पात्रता वेळा लिहिते: जास्तीत जास्त 5 फेऱ्या, 2.5 seconds पेक्षा कमी, जास्तीत जास्त 500 KB. प्रत्येक बदलावर एक मदतनीस त्या तपासते.

📖 नवे शब्दload test — कधी हळू होते हे पाहण्यासाठी अधिकाधिक requests पाठवणेknee — जिथे थांबणे वेगाने वाढू लागते तो बिंदू, सुमारे 80% busyperformance budget — 5 queries किंवा 500 KB सारखी मर्यादा जी CI तपासते
1🏟️ सराव: एकाच पाण्याच्या टेबलावर दर सेकंदाला अधिक धावपटू103010030010003000ms (log)क्षमतेपेक्षा जास्त50 req/s70 req/s90 req/s110 req/sp50p95p9960109280वळणबिंदू (knee): ~80% व्यस्ततेच्या पुढे2📋 load-test तक्ता (simulated)req/sp50p95p9950146098702510916090642804111101,7163,1223,187एक server, प्रत्येक request ला 10 ms —क्षमता सुमारे 100 requests/sopen model: नवे धावपटू येतच राहतातउत्तरांचे काहीही होवोवळणबिंदूच्या (knee) बऱ्याच खाली राहा3📝 दीपिकाच्या पात्रता वेळा — प्रत्येक बदलावर CI job तपासते असे budgetbudgetसुधारणांपूर्वीसुधारणांनंतरप्रत्येक page वरील SQL queries≤ 5511LCP ms (simulated)≤ 2,5005,0201,055page चे वजन KB≤ 5002,210452.570 req/s वर p95 ms≤ 10010923FAIL — CI job exit 1 करतेPASSनंतर: service time10 → 5 ms (model input)CI मध्ये budget मोजण्या (queries, bytes, calls) — त्या timings सारख्या flaky होत नाहीत4🗺️ संपूर्ण चित्र⏱️मोजा🏁bench📋profile📈algorithm🎒memory🗂️cache🔁I/O🧵concurrency🚰queues🗃️queries📱page🏆budgets
⏪ आधी

मोठ्या launch आधी performance हाताने तपासला जाई, मग एकेका लहान बदलाने हळूहळू हरवत असे.

💡 काय

Load test वाढते traffic पाठवून knee शोधतो; performance budget म्हणजे CI job प्रत्येक बदलावर तपासणारी मर्यादा.

⚙️ कसे

एक 10 ms server: p95 50 req/s ला 60 ms, 70 ला 109, 90 ला 280, आणि क्षमतेपेक्षा जास्त 110 ला 3122 ms.

🎯 का

उपायांआधी 51 queries, LCP 5020 ms, 2210 KB आणि p95 109 ms budget मध्ये fail; नंतर 1, 1055, 452.5 आणि 23 pass.

🚀 पुढे

पुढे कुठे: Scaling school machines वाढवते, आणि Observability school हे सगळे production मध्ये मोजते.

🧪 इथे करून पाहा — सराव आणि पात्रता वेळा — service time, load, budgets, सुधारणा
107052500500100

संपूर्ण धडा 12 वाचा →

धडा 01 सुरू करा → 🗓️ अभ्यास योजना (4 आठवडे) 📐 सर्व 12 धड्यांच्या आकृत्या 🧪 प्रश्नमंजुषा (16 प्रश्न) ⏮️ आधी काय होते & फायदे-तोटे