ری ایکٹ میں اعلی تعدد ریئل ٹائم ڈیٹا: رنگ بفرز سے لے کر آف اسکرین کینوس تک

رد عمل بہت سے طریقوں سے بہتر ہے۔ لیکن اگر آپ نے کبھی فی سیکنڈ ہزاروں ڈیٹا پوائنٹس پر کارروائی کی ہے، تو آپ کو جلد ہی احساس ہو جائے گا کہ React کوئی فائر ہوز نہیں ہے۔ یہ زیادہ باغ کی نلی کی طرح ہے۔

بہت زیادہ طاقت لگائیں اور گھاس بھر جائے گی (DOM) یا پائپ پھٹ جائے گا (APP)۔

ایک دوسرا مشاہدہ ہے جو پہلے کے ساتھ جوڑتا ہے۔ لیپ ٹاپ میں 8 سے 16 CPU کور ہوتے ہیں۔ آپ کی React ایپ تقریباً ہمیشہ ان میں سے ایک استعمال کرے گی۔ مرکزی دھاگہ JavaScript، DOM، لے آؤٹ، اور پینٹ کی ترتیبات کو ہینڈل کرتا ہے۔ دوسرے کور بیکار رہتے ہیں جبکہ مرکزی دھاگہ اپنے 60fps فریم بجٹ کو برقرار رکھنے کے لیے جدوجہد کرتا ہے۔

دونوں مسائل کی ایک ہی شکل ہے۔ اس کا مطلب ہے کہ آپ کو React کو گرم راستوں سے بچانے اور ایک سے زیادہ تھریڈ استعمال کرنے کی ضرورت ہے۔ وہ نمونے جو آپ کو وہاں پہنچاتے ہیں وہ فگما کے کینوس انجن، بلومبرگ کے تجارتی ڈیش بورڈ، اور ہر اہم سگنل دیکھنے والے کے پیچھے بھی ہیں جو آپ نے کبھی دیکھے ہیں۔

ایک پروجیکٹ کے لیے 19 الیکٹرو اینسفالوگرافک (ای ای جی) چینلز کو دیکھنے کی ضرورت ہے، ہر ایک تقریباً 1,000 ڈیٹا پوائنٹس فی سیکنڈ منتقل کرتا ہے۔ یہ تقریباً 19,000 اپڈیٹس فی سیکنڈ ہے۔ اگر آپ یہ سب براہ راست React میں ٹائپ کرتے ہیں تو آپ کا UI سست نہیں ہوگا۔ میں ڈرامائی طور پر بیہوش ہو جاتا ہوں۔

یہ آرٹیکل اینڈ ٹو اینڈ آرکیٹیکچر ہے جس کے ساتھ میں آیا ہوں، بشمول رنگ بفرز، ورکرز، مشترکہ میموری، آف مین رینڈرنگ، اور پائیدار ملٹی ٹائم بوجھ کو برقرار رکھنے کے لیے مخصوص پیٹرن۔

انڈیکس

شرطیں

اس مضمون سے زیادہ سے زیادہ فائدہ اٹھانے کے لیے، آپ کو ضرورت ہوگی:

  • React 18 یا 19 کا کام کرنے کا علم۔ آپ کو آرام دہ ہونا چاہئے useState، useEffect، useRefماؤنٹنگ اور ری رینڈرنگ کے درمیان فرق جانیں۔

  • ٹائپ اسکرپٹ کی بنیادی باتیں۔ زیادہ تر مثالیں TypeScript میں ہیں۔ آپ کو بغیر رکے ٹائپ تشریحات پڑھنے کے قابل ہونا چاہئے۔

  • یہ براؤزر کے مین تھریڈ اور ایونٹ لوپ کے بارے میں کسی حد تک سمجھ بوجھ ہے۔ آپ کو ویب ورکر لکھنے کی ضرورت نہیں ہے، لیکن یہ جاننا کہ "مین تھریڈ کو مسدود کرنے” کا مطلب کیا ہے، یہ مرحلہ 3 کو آسان بنا دے گا۔

  • کینوس 2D یا چارٹ لائبریریوں کا علم یہ ایک ضرورت نہیں ہے، لیکن ایک پلس ہے. ایک بار جب آپ اپنے کینوس پر کچھ بھی کھینچ لیں تو آپ تیار ہیں۔

  • ایک لیپ ٹاپ جو جدید ترین کروم یا ایج چلا سکتا ہے۔ مثال پر انحصار کرتا ہے: SharedArrayBuffer، OffscreenCanvasایٹمکس، جس میں کرومیم پر مبنی براؤزرز اور اصلیت کے درمیان تنہائی کی ضرورت ہوتی ہے (بعد میں مضمون میں اس کا احاطہ کیا گیا ہے)۔

ویب ورکر، رنگ بفر، یا ویب جی ایل کے ساتھ کسی پیشگی تجربے کی ضرورت نہیں ہے۔ یہ مضمون ہر ایک کو حقیقی دنیا کے مسئلے کے تناظر میں متعارف کراتا ہے۔

یہ پہلے سے کون کر رہا ہے؟

اس دستاویز کے نمونے تجرباتی نہیں ہیں۔ یہ پروڈکشن ایپس کے پیچھے فن تعمیر ہے جو اعلی تعدد ڈیٹا اکٹھا اور پیش کرتی ہے۔

  • ٹریڈنگ اور فنانس ڈیش بورڈ (Bloomberg, Hyperliquid, dYdX، مارکیٹ کے تمام بڑے مبصرین) کینوس کے ذریعے فراہم کردہ گرڈ کے ذریعے فی سیکنڈ ہزاروں قیمتوں کے ٹکٹس کو آگے بڑھاتے ہیں۔

  • فگما ہم ایک کارکن کے اندر WebAssembly میں پورے کینوس انجن چلاتے ہیں۔ مرکزی دھاگہ صرف کروم کے لیے ری ایکٹ پیش کرتا ہے۔

  • Google Docs اور Microsoft Loop ایک پروجیکشن کے طور پر DOM کا استعمال کرتے ہوئے ایک کارکن پر دستاویز کے ماڈل کو انجام دیتا ہے۔

  • چارٹ لائبریری LightningChart، uPlot، Plotly، اور ECharts کی طرح، وہ کینوس یا WebGL پر کھینچتے ہیں اور ایک ریپر کے طور پر React رکھتے ہیں۔

  • اہم علامات، ای سی جی، ای ای جی اور موشن کیپچر ایپس نمونوں کو باقاعدگی سے 1 کلو ہرٹز سے اوپر پراسیس کریں اور انہیں لائیو پلاٹ میں سٹریم کریں۔

  • آبزرویبلٹی اور اے پی ایم ٹولز (ڈیٹا ڈوگ لائیو ٹیل، گرافانا ریئل ٹائم پینل) ٹیبز کو ریسپانسیو رکھنے کے لیے رینڈرنگ سے الگ مجموعہ۔

  • آڈیو ایڈیٹر اور ویژولائزیشن ٹولکوئی بھی چیز جو ویوفارم ڈسپلے کے ساتھ ویب آڈیو API کا استعمال کرتی ہے۔

  • Transformers.js اور ONNX رن ٹائم ویب بنیادی طور پر، آپ کارکنوں پر ML تخمینہ لگاتے ہیں۔

مختلف ڈومین، ایک ہی چال: React کے پاس ایسی چیز ہوتی ہے جو شاذ و نادر ہی تبدیل ہوتی ہے، ریفریش ریٹ کے ساتھ کچھ اور تبدیل ہوتا ہے، اور ضرورت سے زیادہ کام اس تھریڈ پر ہوتا ہے جو مرکزی دھاگہ نہیں ہے۔

1kHz ریاضی

مسئلہ کو ٹھوس بنانے کے لیے کچھ نمبر۔

  • نمونے ہر 1 ایم ایس آتے ہیں۔

  • 60Hz ڈسپلے ہر 16.67ms پر ریفریش ہوتا ہے۔

  • لہذا، تقریبا ایک فریم میں 16-17 نمونے فی سلسلہ.

  • 19 فعال اسٹریمز کا استعمال کرتے ہوئے (ای ای جی مثال)، 300-320 نمونے فی فریم.

آپ setState ہر نمونے میں، React تقریباً 19,000 رینڈرنگ فی سیکنڈ کی کوشش کرتا ہے۔ یہ ممکن نہیں ہے، اس لیے فریم کو چھوڑ دیا گیا ہے۔ UI stutters اور لیپ ٹاپ پنکھا چل رہا ہے.

آپ setState ~320 نمونوں کے بیچوں کے ساتھ، ایک بار فی فریم، React 60 رینڈرنگ فی سیکنڈ کرتا ہے۔ یہ آسان ہے۔

یہ واحد ریفریم پوری چال ہے اور ذیل کے تمام مراحل سے ظاہر ہوتا ہے۔

کیا عام طور پر غلط ہے

یہاں کوڈ کا وہ ورژن ہے جسے آپ پہلی بار زیادہ تر ریئل ٹائم ری ایکٹ ایپس میں آزمانے پر دیکھیں گے۔ معقول لگتا ہے۔ یہ تمام ہنگامہ آرائی کی وجہ بھی ہے جو ٹیم اگلے دو ہفتے ٹریکنگ میں گزارے گی۔

import { useEffect, useState } from "react";

export default function NaiveChart({ socket }) {
  const [data, setData] = useState([]);

  useEffect(() => {
    socket.on("newPoint", (point: number) => {
      setData((prev) => [...prev, point]); // re-renders every time
    });
  }, [socket]);

  return 

{data.length} points

; }

تینوں مسائل کا خلاصہ سات سطروں میں کیا گیا ہے۔

  1. کوئی بھی آنے والا نمونہ دوبارہ رینڈر کو متحرک کرے گا۔ 1 kHz پر، یہ فی سیکنڈ 1,000 بار رینڈر کرتا ہے۔ رد عمل اس کے بارے میں کبھی خوش نہیں ہوگا۔

  2. [...prev, point] ہر دھکا ایک نئی صف مختص کرتا ہے۔ 1kHz پر، یہ ایک نئی صف فی ملی سیکنڈ ہے اور کوڑا اٹھانے والے کو یہ سب صاف کرنا پڑتا ہے۔ ڈھیر اٹھایا گیا ہے، جی سی لمبا ہے، اور پنکھا چل رہا ہے۔

  3. صفوں کی کوئی اوپری حد نہیں ہے۔ اگر ہم اسے ایک گھنٹے تک چلاتے ہیں، تو ہمارے پاس میموری میں 3.6 ملین نمبر ہوتے ہیں، اور React کو ہر رینڈر میں ان سب کو مدنظر رکھنا ہوتا ہے۔

حل صرف ایک چال نہیں ہے۔ یہ چھوٹی چھوٹی چیزوں کا ایک ڈھیر ہے، ہر ایک ان ناکامی کے طریقوں میں سے ایک کو حل کرتا ہے اور آخر کار واحد دھاگے، مرکزی دھاگے کے لیے ایک گہرا مسئلہ حل کرتا ہے۔

ذہنی ماڈل: ایئر ٹریفک کنٹرول اور کچن بریگیڈ

ٹکڑے کو سیدھا رکھنے کے لیے کوڈ سے پہلے دو مشابہتیں ہیں:

سب سے پہلے، ہوائی ٹریفک کنٹرول: تین کردار، ایک ہوائی اڈہ۔

  • کہ کنٹرول ٹاور (آپ کا اسٹور) ہر جہاز (ہر نمونہ) کو دیکھتا ہے اور اس کا پتہ لگاتا ہے کہ یہ کہاں ہے۔

  • کہ زمینی عملہ (رد عمل) رن وے اور گیٹس کو ترتیب دیتا ہے، ہوائی جہاز کے ذریعہ استعمال کردہ ترتیب۔

  • کہ پائلٹ (لوپ ڈرا) یہ دراصل اڑتا ہے۔ وہ اجازت کے لیے ٹاور کو اسکین کرتے ہیں اور ہر چند سیکنڈ میں کام کرتے ہیں۔

جب بھی ہوائی جہاز حرکت کرتا ہے تو کنٹرول ٹاور زمینی عملے سے دروازوں کو دور نہیں کرتا ہے۔ یہ صرف ڈیٹا رکھتا ہے۔ جب نظام الاوقات تبدیل ہوتا ہے تو گراؤنڈ اسٹاف گیٹس کو دوبارہ ترتیب دے گا، جو کہ شاذ و نادر ہی ہوتا ہے۔ ٹاور کے ڈیٹا کی بنیاد پر پائلٹ مسلسل اپنی رفتار سے کام کرتا ہے۔

دوسرا، کچن بریگیڈ: چوٹی کے وقت ریستوراں کا کچن۔ ایک شیف تمام پکوان نہیں بنا سکتا۔ کلاسک بریگیڈ میں ساس، مچھلی، پیسٹری، گارڈ مینیجر، پلیٹنگ اور سروس کے اسٹیشن شامل ہیں۔ ہر اسٹیشن کے پاس کھانے کا ایک ٹکڑا ہوتا ہے۔ سہولت کار وقت کو مربوط کرتا ہے۔

  • کہ سہولت کار یہ آپ کا مین تھریڈ ہے۔

  • کہ اسٹیشن یہ آپ کا کارکن ہے۔

  • کہ چڑھایا کھڑکی یہ آپ کی مشترکہ یادداشت ہے۔

  • کہ میز پر کھانا فراہم کردہ فریم۔

ایک واحد شیف جو تمام کارروائیوں کو ترتیب وار انجام دینے کی کوشش کرتا ہے مرکزی دھاگے کے لیے وقف فرنٹ اینڈ ہے۔ بریگیڈ ورکرز پر مبنی بریگیڈ ہے۔ اس لیے نہیں کہ شیف سولوسٹ سے زیادہ تیز ہے، بلکہ اس لیے کہ کٹنگ، چٹنی اور سیرنگ بیک وقت ہوتی ہے، اس لیے بریگیڈ تیز تر ہے۔ سہولت کار کھانا نہیں بناتا، وہ کوآرڈینیٹ کرتا ہے۔

ذیل میں سب کچھ ان دو خیالات کا ایک ٹھوس اطلاق ہے۔

مرحلہ 1: نمونے کو رد عمل کی حالت میں مت چھوڑیں۔

ریئل ٹائم ری ایکٹ ایپس کے ساتھ سب سے بڑی غلطی ہر نمونے کو ریاست کے طور پر پیش کرنا ہے، جو کہ ایسا نہیں ہے۔ ملک فیصلہ کرتا ہے کون سے اجزاء موجود ہیں اور ان کو کیسے ترتیب دیا گیا ہے۔. لائیو پلاٹ ایک جزو ہے۔ 60,000 نمونے جو سکرول کیے جا رہے ہیں وہ 60,000 ریاستی ٹکڑے نہیں ہیں۔ وہ ایک بفر ہیں۔

آپ React سے باہر ایک اسٹور چاہتے ہیں۔ ٹائپ شدہ صفوں کے لیے رنگ بفرز مستقل وقت کے اندراجات اور ایک محدود ہیپ فراہم کرتے ہیں۔

type Listener = () => void;

export function createSampleStore(capacity: number) {
  const buf = new Float32Array(capacity);
  let head = 0;
  let size = 0;
  const listeners = new Set();

  return {
    push(sample: number) {
      buf[head] = sample;
      head = (head + 1) % capacity;
      if (size < capacity) size++;
    },
    pushBatch(samples: Float32Array) {
      for (let i = 0; i < samples.length; i++) {
        buf[head] = samples[i];
        head = (head + 1) % capacity;
        if (size < capacity) size++;
      }
    },
    read(): Float32Array {
      if (size < capacity) return buf.subarray(0, size);
      const out = new Float32Array(capacity);
      out.set(buf.subarray(head));
      out.set(buf.subarray(0, head), capacity - head);
      return out;
    },
    subscribe(l: Listener) {
      listeners.add(l);
      return () => listeners.delete(l);
    },
  };
}

دیکھیں یہاں کیا نہیں ہے۔ نہیں useStateکوئی سیٹر نہیں، کچھ بھی ردعمل نہیں کرتا. یہ صرف سادہ جاوا اسکرپٹ ہے۔ ایک اسٹور فی سیکنڈ میں ایک ملین دھکے کی اجازت دے سکتا ہے اور React کو کوئی پرواہ نہیں ہے کیونکہ React سبسکرائب نہیں کرتا ہے۔

بصری رنگ بفر میکانزم مندرجہ ذیل ہے:

ہمارے پاس ہر دھکے اور گود میں سر پیش قدمی ہے۔ تاریخی ترتیب میں آخری N نمونوں کی ونڈو حاصل کریں۔ نتیجہ مستقل وقت اور ایک پابند ڈھیر ہے۔

اگر آپ ابھی تک فارمیٹ شدہ بفر نہیں چاہتے ہیں، تو ایک آسان حل یہ ہے کہ رولنگ ونڈو کو اسٹیٹ کے بجائے ریفرنس پر ڈالیں۔

import { useEffect, useRef } from "react";

export default function RefChart({ socket }) {
  const bufferRef = useRef([]);

  useEffect(() => {
    socket.on("newPoint", (point: number) => {
      bufferRef.current.push(point);
      if (bufferRef.current.length > 1000) {
        bufferRef.current.shift();
      }
    });
  }, [socket]);

  return 

Streaming {bufferRef.current.length} points

; }

یہ "اس سے 10 گنا تیز ہے” NaiveChart"ابھی بھی اچھا نہیں” ورژن۔ رینڈرنگ ہر بار آپ کے دبانے پر نہیں چلے گی، لیکن آپ کو کوئی بھی اپ ڈیٹ نظر نہیں آئے گا جب تک کہ کوئی اور چیز دوبارہ پیش نہ کی جائے۔ اصل پلاٹنگ کے لیے حوالہ دیکھیں: requestAnimationFrame ایک لوپ کھینچیں (مرحلہ 2 دیکھیں)۔

مرحلہ 2: شکل کو قدر سے الگ کریں۔

ری ایکٹ رینڈرز جب: شکل UI بدل گیا ہے۔ نیا پلاٹ؟ دوبارہ پیش کریں۔ کیا آپ نے پلاٹ ہٹا دیا؟ دوبارہ پیش کریں۔ کیا یہ لائن سے بالکل بدل گیا؟ دوبارہ پیش کریں۔ یہ 1000Hz پر نہیں ہوتا ہے۔ یہ ایک منٹ میں کئی بار ہوتا ہے جب صارف کسی چیز پر کلک کرتا ہے۔

کہ قدر ہر پلاٹ کے اندر یہ ڈیٹا کی شرح کے ساتھ تبدیل ہوتا ہے۔ آپ کو کبھی بھی رد عمل کو ہاتھ نہیں لگانا چاہئے۔

خاص طور پر:

function LivePlot({ store }: { store: SampleStore }) {
  const canvasRef = useRef(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d")!;

    let raf = 0;
    const draw = () => {
      const samples = store.read();
      drawSeries(ctx, samples);
      raf = requestAnimationFrame(draw);
    };
    raf = requestAnimationFrame(draw);
    return () => cancelAnimationFrame(raf);
  }, [store]);

  return ;
}

رد عمل اس جزو کو ایک بار پیش کرتا ہے۔ کہ useEffect یہ ایک بار چلتا ہے۔ کہ requestAnimationFrame لوپ ہر فریم اسٹوریج سے پڑھتا ہے۔

ذخیروں کو اس شرح پر آگے بڑھایا جاسکتا ہے جس کا ذریعہ انتظام کرسکتا ہے۔ چاہے ماخذ 100 یا 100,000 نمونے فی سیکنڈ بھیجے، صارفین کو ایک ہموار 60fps لائن نظر آئے گی۔ وائرنگ جبلت samples ~ میں useState یہ غلط ہے، اس لیے مزاحمت کریں۔

اگر آپ کو لوپ میں ردعمل کی ضرورت ہو تو اختیاری سبسکرپشن

بعض اوقات ایک جز اصل میں ڈیٹا پر انحصار کرتا ہے (خلاصہ کے اعدادوشمار، محور کے لیبلز، یا حقیقی وقت کی قدر کے بیجز)۔ اس کو حاصل کرنے کے لیے، اختیاری سبسکرپشنز کے ساتھ ایک ذخیرہ استعمال کریں تاکہ صرف ان اجزاء کو دوبارہ پیش کیا جا سکے جن کا آپ خیال رکھتے ہیں۔ Zustand اسے آسان بناتا ہے۔

import { create } from "zustand";

const useDataStore = create<{ latest: number | null; setLatest: (v: number) => void }>((set) => ({
  latest: null,
  setLatest: (v) => set({ latest: v }),
}));

function LatestBadge() {
  // Only re-renders when latest changes, not when other store fields do.
  const latest = useDataStore((state) => state.latest);
  return 

Latest: {latest?.toFixed(2)}

; }

رائٹ پر RAF انضمام کے ساتھ اس کو جوڑیں (کال setLatest آپ کو ایک React جزو ملتا ہے جو ڈیٹا کی شرح سے قطع نظر 60fps پر آسانی سے اپ ڈیٹ ہوتا ہے (ایک بار فی فریم، ایک بار فی نمونہ نہیں)۔

useSyncExternalStore یہ بنیادی طور پر ایک جیسا ہے اور کسی بھی پب-سب ریپوزٹری کے لیے کام کرے گا، بشمول اوپر والا رنگ بفر۔ اپنی ٹیم کے لیے ہلکی چیز استعمال کریں۔

مرحلہ 3: بھاری کام کو مرکزی دھاگے سے ہٹا دیں۔

سیونگ اور لازمی ڈرا لوپس رکاوٹ میں ری ایکٹ کے تعاون کو ہینڈل کرتے ہیں۔ مرکزی دھاگہ خود اب بھی تمام مجموعہ، تجزیہ اور ریاضی کر رہا ہے۔ کوئی بھی چیز جو زیادہ سے زیادہ 50,000 نمونے فی سیکنڈ فی سٹریم سے زیادہ ہے اس کے لیے مزید نمونوں کی ضرورت ہوگی۔

براؤزر فرار ہونے کے چار راستے فراہم کرتے ہیں: ویب ورکرز، ٹرانسپورٹ ایبل اشیاء، SharedArrayBuffer + جوہری، اور OffscreenCanvas. ہر ایک مخصوص مسئلہ حل کرتا ہے۔

رینڈرر کے عمل کے ساتھ براؤزر پراسیس ماڈل جس میں ایک اہم دھاگہ، سرشار کارکنان، مشترکہ کارکنان، سروس ورکرز، اور کمپوزیٹر تھریڈز شامل ہیں۔ ایک GPU عمل، بشمول WebGL اور WebGPU کے لیے GPU تھریڈز۔ ایک نیٹ ورک کا عمل، بشمول بازیافت اور ویب ساکٹس کے لیے نیٹ ورک تھریڈز، جس میں پوسٹ میسج کے ذریعے ہونے والی ابتدائی اور ورکر اقسام کے درمیان مواصلت ہوتی ہے۔

مرکزی دھاگہ JavaScript، DOM، لے آؤٹ، اور پینٹ کی ترتیبات کو چلاتا ہے۔ ورکر ایک الگ تھلگ جاوا اسکرپٹ سیاق و سباق ہوتا ہے جس کے اپنے ایونٹ لوپ ہوتے ہیں۔ اسے DOM تک رسائی حاصل نہیں ہے، میموری کو بطور ڈیفالٹ شیئر نہیں کر سکتا، اور تمام کراس تھریڈڈ پیغامات غیر مطابقت پذیر ہیں اور جب تک نہ بھیجے جائیں اس کی کاپی نہیں کی جاتی ہے۔

اس ماڈل کے لیے ڈیزائن کرتے وقت تین خصوصیات اہم ہیں:

  • کارکنوں کو DOM تک رسائی حاصل نہیں ہے۔ یہی بات ہے۔ وہ خالص جاوا اسکرپٹ چلاتے ہیں۔ ڈیٹا آپریشنز، نیٹ ورک پارسنگ، ریاضی، کوڈیکس اور ایم ایل انفرنس کے لیے مثالی۔

  • مواصلات غیر مطابقت پذیر ہے۔ فنکشن کے وسط میں پڑھنے کے لیے کوئی مشترکہ متغیرات نہیں ہیں۔ درخواستوں اور واقعات کے لیے اپنے API کی منصوبہ بندی کریں۔

  • آپ کے ڈیٹا کی کاپی (جب تک کہ آپ اسے منتقل یا شیئر کریں): ساختی نقل ڈیفالٹ ہے؛ نقل و حمل کے قابل اشیاء کاپیاں چھوڑ دیں۔ SharedArrayBuffer مستقل طور پر چھوڑ دیں۔

وقف پریکٹیشنر

جمع کرنے کے لیے کارکنوں کی کم از کم تعداد ہے:

// worker.ts
self.onmessage = (event) => {
  const { samples } = event.data;
  // Decode, filter, decimate. The main thread doesn't see any of this.
  const summary = computeStats(samples);
  postMessage(summary);
};
// main thread
import { useEffect, useRef } from "react";

export default function WorkerChart({ socket, store }) {
  const workerRef = useRef();

  useEffect(() => {
    workerRef.current = new Worker(new URL("./worker.ts", import.meta.url), {
      type: "module",
    });

    workerRef.current.onmessage = (event) => {
      store.pushBatch(event.data); // pre-processed, cheap to ingest
    };

    socket.on("newPoint", (point: number) => {
      workerRef.current?.postMessage({ point });
    });

    return () => workerRef.current?.terminate();
  }, [socket, store]);

  return ;
}

کارکنوں کے بارے میں اندرونی بنانے کے لئے تین چیزیں۔

سب سے پہلے، کارکن رن ٹائم کے نقطہ نظر سے اصل عمل ہیں۔ ہر ایک کا اپنا ہیپ، ایونٹ لوپ، اور ہے۔ globalThis. ایک شروع کرنے میں تقریباً 1 سے 5 ایم ایس لگتے ہیں۔ گرم راستے کے اندر نہ گھمائیں۔

دوسرا، ماڈیول ورکرز تازہ ترین ڈیفالٹ ہیں۔ type: "module" کارکن کے اندر ES ماڈیولز کو فعال کریں، بشمول: import. میراث importScripts کلاسک کارکنوں کے لیے ہے، لہذا اسے نئے کوڈ میں استعمال نہ کریں۔

آخر میں، بنڈلرز کارکنوں کے بارے میں جانتے ہیں۔ Vite، Webpack، esbuild اور Rspack سبھی کا پتہ چلتا ہے: new Worker(new URL("./x.ts", import.meta.url)) پیٹرن بنائیں اور کارکنوں کے لیے الگ الگ ٹکڑے بنائیں۔

ورکر پولز کو کور میں پیمانہ بناتا ہے۔

CPU کے پابند کاموں کے لیے (بائنری فریموں کو پارس کرنا، آڈیو کو ڈیکوڈنگ کرنا، FFTs کا حساب لگانا)، پول تمام دستیاب کوروں میں کام کو تقسیم کرتا ہے۔

class WorkerPool {
  private workers: Worker[];
  private next = 0;
  private pending = new Map void>();

  constructor(scriptUrl: URL, size = Math.max(1, navigator.hardwareConcurrency - 1)) {
    this.workers = Array.from({ length: size }, () => {
      const w = new Worker(scriptUrl, { type: "module" });
      w.onmessage = (event) => {
        const cb = this.pending.get(event.data.id);
        if (!cb) return;
        this.pending.delete(event.data.id);
        cb(event.data.result);
      };
      return w;
    });
  }

  async run(kind: string, payload: unknown, transferables: Transferable[] = []): Promise {
    const id = crypto.randomUUID();
    const result = new Promise((resolve) => this.pending.set(id, resolve as (r: unknown) => void));
    const worker = this.workers[this.next];
    this.next = (this.next + 1) % this.workers.length;
    worker.postMessage({ id, kind, payload }, transferables);
    return result;
  }
}

export const pool = new WorkerPool(new URL("./decoder.worker.ts", import.meta.url));

راؤنڈ رابن کی تعیناتی، مائنس 1 کارکن فی کور۔ مماثل جواب آنے پر زیر التواء درخواستیں حل ہو جاتی ہیں۔ یہ سستا، قابل پیشن گوئی ہے، اور میموری کی بینڈوتھ کی حد تک پہنچنے تک لکیری طور پر پیمانہ ہے۔

وہ اشیاء جنہیں منتقل کیا جا سکتا ہے، وہ راستے جن کی نقل نہیں کی جا سکتی

postMessage بنیادی طور پر یہ پے لوڈ کو نقل کرتا ہے۔ 10MB بفر کی نقل تیار کرنے میں ملی سیکنڈ لگتے ہیں اور میموری کے استعمال کو دوگنا کر دیتے ہیں۔ حل یہ ہے۔ منتقل اس کے بجائے بفر۔

پوسٹ میسج طریقوں کا شانہ بہ شانہ موازنہ: پرائمری پوسٹ میسیج ساختی نقل کے ذریعے بنیادی اور ورکر ہیپس دونوں پر کاپیوں کے ساتھ بفر کو نقل کرتا ہے، جب کہ قابل منتقلی ملکیت کو منتقل کرتا ہے، اس لیے جب ملکیت پرائمری سے کارکن کی طرف منتقل ہوتی ہے، وہی بفر ایک بار موجود ہوتا ہے۔

مطلب: اگر بفر بھیجا جاتا ہے تو بھیجنے والا رسائی کھو دیتا ہے۔ وصول کنندہ کو بغیر کسی کاپی اور بغیر O(1) ہینڈ آف کے یہ ملتا ہے۔

const buf = new ArrayBuffer(10 * 1024 * 1024); // 10 MB
new Uint8Array(buf).set(somePayload);

worker.postMessage({ buf }, [buf]); // second arg = list of transferables
// `buf` is now detached on this side. Accessing it throws.

قابل منتقلی اقسام کی 2026 کی فہرست میں شامل ہیں: ArrayBuffer (اور کوئی بھی ان پٹ سرنی منظر جس کی حمایت کرتا ہے) MessagePort، ImageBitmap، OffscreenCanvasندی کی قسم (ReadableStream، WritableStream، TransformStream) RTCDataChannel، VideoFrame، AudioDataویب ٹرانسپورٹ کا سلسلہ۔ React اور ریئل ٹائم کام کے لیے سب سے زیادہ مفید ہیں: ArrayBuffer، OffscreenCanvasاور MessagePort.

ایک عام خرابی یہ ہے کہ اگر آپ منتقل کرنا بھول جاتے ہیں، تو یہ خاموشی سے آپ کو سست کردے گا۔ ایپ کام کرتی ہے لیکن تمام پیغامات کو کاپی کرتی ہے۔ پروفائل کارکن postMessage فون کال اگر آپ "چھوٹے” پے لوڈ کے لیے ملی سیکنڈ دیکھ رہے ہیں، تو آپ اسے نقل کر رہے ہیں جب اسے بھیجا جانا چاہیے۔

// Bad: copies every frame.
worker.postMessage({ samples: float32Array });

// Good: transfers the underlying buffer.
worker.postMessage({ samples: float32Array }, [float32Array.buffer]);

ٹرانسمیشن کے بعد بفرز کو الگ کر دیا جاتا ہے، اس لیے بھیجنے والے کو ان کی تخلیق جاری رکھنے کے لیے انہیں دوبارہ مختص کرنا چاہیے (یا انہیں پہلے سے مختص کردہ بفر پول سے کھینچنا چاہیے)۔

SharedArrayBuffer اور اٹامکس

بفر کو جاری کرکے اپنا ہاتھ منتقل کریں۔ شیئر کریں دو تھریڈز کو ایک ہی وقت میں ایک ہی میموری دیکھنے کی اجازت دیتا ہے۔

const sab = new SharedArrayBuffer(1024 * 1024); // 1 MB shared
worker.postMessage({ sab });

// Both main and worker now hold references to the same memory.
const viewMain = new Int32Array(sab);
// Inside worker:
// const viewWorker = new Int32Array(event.data.sab);

تین خصوصیات اہم ہیں:

SharedArrayBuffer کراس سورس آئسولیشن کی ضرورت ہے۔ آپ کا صفحہ اس طرح نظر آنا چاہئے: Cross-Origin-Opener-Policy: same-origin اور Cross-Origin-Embedder-Policy: require-corp. ان ہیڈرز کے بغیر SharedArrayBuffer براؤزر میں غیر متعینہ۔ ہم بعد میں مضمون میں مزید تفصیل سے اس کا احاطہ کریں گے۔

آپ کو بھی ضرورت ہے Atomics ہم وقت سازی کے لیے۔ کوآرڈینیشن کے بغیر ایک ہی میموری پر ایک سے زیادہ تھریڈز لکھنا غیر متعینہ نتائج پیدا کرے گا۔ Atomics ٹائپ کردہ سرنی نظاروں پر موازنہ اور تبادلہ، لوڈ، اسٹور، اپینڈ، چائلڈ، انتظار، اور مطلع آپریشن فراہم کرتا ہے۔

const sab = new SharedArrayBuffer(8);
const view = new Int32Array(sab);

// Main thread: wake any worker that's waiting on slot 0.
Atomics.store(view, 0, 1);
Atomics.notify(view, 0, 1);

// Worker: block until slot 0 changes from 0.
const result = Atomics.wait(view, 0, 0); // "ok", "not-equal", or "timed-out"

اور لاک فری رنگ بفر قاتل ایپ ہے۔ بغیر تالے کے دو دھاگوں کے درمیان پروڈیوسر صارفین کی قطار postMessage کوئی باہمی اور GC دباؤ نہیں۔ ڈیٹا مشترکہ بفر میں ہے۔ ایٹمکس پڑھنے/لکھنے کے مقامات کو ایڈجسٹ کرتا ہے۔

یہاں ایک کم سے کم SPSC (واحد پروڈیوسر، واحد صارف) رنگ بفر ہے:

type SharedRing = {
  data: Float32Array;          // payload
  control: Int32Array;         // [head, tail]
};

function createSharedRing(capacity: number): SharedRing {
  const sab = new SharedArrayBuffer(capacity * 4 + 16);
  const control = new Int32Array(sab, 0, 4);   // [head, tail, ...]
  const data = new Float32Array(sab, 16, capacity);
  return { data, control };
}

function push(ring: SharedRing, value: number): boolean {
  const head = Atomics.load(ring.control, 0);
  const tail = Atomics.load(ring.control, 1);
  const next = (head + 1) % ring.data.length;
  if (next === tail) return false; // full
  ring.data[head] = value;
  Atomics.store(ring.control, 0, next);
  return true;
}

function pop(ring: SharedRing): number | null {
  const tail = Atomics.load(ring.control, 1);
  const head = Atomics.load(ring.control, 0);
  if (tail === head) return null; // empty
  const value = ring.data[tail];
  Atomics.store(ring.control, 1, (tail + 1) % ring.data.length);
  return value;
}

پروڈیوسر ایک تھریڈ سے لکھتا ہے۔ صارف پڑھتا ہے اور کسی اور چیز کو بلاک نہیں کرتا، کچھ بھی نہیں۔ postMessage ان کے درمیان۔ سگنلنگ ریٹ پر، یہ واحد فن تعمیر ہے جو قابل توسیع ہے۔

سنگل پروڈیوسر سنگل کنزیومر رِنگ بفر بذریعہ SharedArrayBuffer: پروڈیوسر (کلیکشن ورکر) ہیڈ پوائنٹر کو لکھتا ہے اور اس میں اضافہ کرتا ہے، صارف (رینڈر تھریڈ) ٹیل پوائنٹر سے پڑھتا ہے اور اس میں اضافہ کرتا ہے۔ دونوں پوائنٹرز کو مشترکہ بفر میں محفوظ کیا جاتا ہے اور جوہری آپریشن کے طور پر اپ ڈیٹ کیا جاتا ہے۔

ملٹی پروڈیوسر یا ملٹی کنزیومر قطاروں کے لیے موازنہ اور سویپ لوپ کی ضرورت ہے (Atomics.compareExchange)۔ وہ بہت تیزی سے بدل جاتے ہیں۔ زیادہ تر پروڈکشن سیٹنگز جب ممکن ہو تو SPSC کا استعمال کرتی ہیں اور ممکن نہ ہونے پر میسج پاس کرنے پر واپس آتی ہیں۔

الیکٹران میں، اسے مرکزی عمل سے جمع کیا جاتا ہے۔

ایک ہی خیال ایک سطح اوپر لاگو ہوتا ہے۔ الیکٹران اور اس کے مقامی SDK کا استعمال کرتے ہوئے، آپ نمونے جمع کر سکتے ہیں: اہم عملہم اسے وہاں بفر کرتے ہیں اور پیش کنندہ کے ریفریش سائیکل کی بنیاد پر آئی پی سی کے ذریعے بیچز پاس کرتے ہیں۔ ایک آئی پی سی پیغام فی نمونہ 1 کلو ہرٹز پر آئی پی سی کو پورا کرتا ہے۔ 16 نمونوں کے بیچ کے ساتھ فی فریم ایک IPC پیغام سیدھا ہے۔

// electron main: buffer in Node, flush at 60Hz
let pending: number[] = [];

device.on("sample", (value) => pending.push(value));

setInterval(() => {
  if (pending.length === 0) return;
  const batch = new Float32Array(pending);
  pending = [];
  // Transferable to avoid the structured-clone copy.
  mainWindow.webContents.send("device:samples", batch.buffer, [batch.buffer]);
}, 1000 / 60);
// preload: expose a thin subscription API to the renderer
contextBridge.exposeInMainWorld("device", {
  onSamples: (cb: (samples: Float32Array) => void) => {
    const handler = (_: unknown, buf: ArrayBuffer) => cb(new Float32Array(buf));
    ipcRenderer.on("device:samples", handler);
    return () => ipcRenderer.removeListener("device:samples", handler);
  },
});
// renderer: feed the store from the bridge
useEffect(() => {
  return window.device.onSamples((batch) => store.pushBatch(batch));
}, []);

رینڈرر کا رینڈرر تھریڈ صرف ایک بیچ فی فریم پر اثر انداز ہوتا ہے، قطع نظر آلہ کی رفتار سے۔ ڈیٹا آپریشنز اپ اسٹریم ہوتے ہیں۔

مرحلہ 4: استعمال کرکے ڈیفالٹ رینڈرنگ کو بند کریں۔ OffscreenCanvas

DOM سنگل تھریڈڈ ہے۔ کینوس API کے لئے بھی یہی ہے۔ OffscreenCanvas اسے توڑ دو۔ ایک کینوس جو کارکنوں کو بھیجا جا سکتا ہے اور مرکزی سے آزادانہ طور پر تیار کیا جا سکتا ہے۔

// Main thread
const canvas = canvasRef.current!;
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen }, [offscreen]);
// Worker
let ctx: OffscreenCanvasRenderingContext2D | null = null;

self.onmessage = (event) => {
  if (event.data.canvas) {
    ctx = event.data.canvas.getContext("2d");
    return;
  }
  if (event.data.samples && ctx) {
    drawSeries(ctx, event.data.samples);
  }
};

مرکزی دھاگہ اب ڈرائنگ لوپ سے مقابلہ کیے بغیر کلکس، ہوور اور دیگر تعاملات کو سنبھالنے کے لیے آزاد ہے۔ کارکنان اپنے پاس موجود ڈیٹا کی بنیاد پر اپنی رفتار سے ڈرا کرتے ہیں۔

کے ساتھ مل کر SharedArrayBufferآپ کو براؤزر میں دستیاب ریئل ٹائم رینڈرنگ پائپ لائن فراہم کرتا ہے۔

مین تھریڈ سے باہر رینڈرنگ پائپ لائن: ڈیٹا سورس اسے جمع کرنے والے کارکن کو فراہم کرتا ہے جو SharedArrayBuffer کو لکھتا ہے۔ Renderer جو OffscreenCanvas کا مالک ہے اسے GPU کمانڈز پر عمل کرنے کے لیے پڑھتا ہے جو DOM کو اپ ڈیٹ کرتے ہیں، اور مرکزی تھریڈ React UI کے لیے SharedArrayBuffer سے خلاصہ پڑھتا ہے۔

حصول کارکن مشترکہ بفر پر نمونے لکھتے ہیں۔ پیش کنندہ اسے پڑھتا اور کھینچتا ہے۔ مین صرف خلاصہ پڑھتا ہے (FPS، تازہ ترین قدر، چینل لیبل) اور کروم کو رینڈر کرتا ہے۔ مین کے ایونٹ لوپ سے کوئی ڈیٹا نہیں گزرتا ہے۔

1kHz پر 19-چینل سگنل کے لیے، یہ واحد فن تعمیر ہے جو ہیڈ روم والے لیپ ٹاپ پر 60fps کی ضمانت دیتا ہے۔

مرحلہ 5: ڈرائنگ سے پہلے قتل عام

ایک 1200 پکسل چوڑا کینوس زیادہ سے زیادہ 1200 منفرد X پوزیشنوں پر ڈسپلے کر سکتا ہے۔ اگر آپ کے پاس ایک کھڑکی میں 60,000 نمونے ہیں اور آپ ان سب کو کھینچتے ہیں، تو آپ صارف کی نظر سے 50 گنا زیادہ کام کر رہے ہیں۔

فی پکسل کالم درست پوائنٹ کا انتخاب کریں۔ سگنل کے لیے "کم سے کم” پیٹرن مناسب ہے۔ ہر پکسل کالم کے لیے، اس حد کی کم از کم اور زیادہ سے زیادہ قدریں تلاش کریں اور ان کے درمیان عمودی لکیر کھینچیں۔ یہ بصری طور پر تمام پوائنٹس ڈرائنگ کے برابر ہے، لیکن بہت سستا ہے۔

function drawDecimated(
  ctx: CanvasRenderingContext2D,
  samples: Float32Array,
  width: number,
) {
  const samplesPerPixel = samples.length / width;
  ctx.beginPath();
  for (let x = 0; x < width; x++) {
    const start = Math.floor(x * samplesPerPixel);
    const end = Math.floor((x + 1) * samplesPerPixel);
    let min = Infinity;
    let max = -Infinity;
    for (let i = start; i < end; i++) {
      const v = samples[i];
      if (v < min) min = v;
      if (v > max) max = v;
    }
    ctx.moveTo(x, scale(min));
    ctx.lineTo(x, scale(max));
  }
  ctx.stroke();
}

ڈیسیمیشن ریئل ٹائم ویژولائزیشن میں واحد سب سے بڑی سی پی یو جیت ہے، اور تقریباً کوئی بھی ایسا نہیں کرتا ہے۔

ایک زیادہ ضعف وفاداری یہ ہوگی: LTTB (سب سے بڑی سہ رخی تین بالٹیاں)ایک الگورتھم جو کم از کم زیادہ سے زیادہ بصری ظاہری شکل کو محفوظ رکھتے ہوئے فی بالٹی ایک نمائندہ نمونہ منتخب کرتا ہے۔ یہ پڑھنے کے قابل ہے اگر آپ غیر سگنل والے ڈیٹا کی منصوبہ بندی کر رہے ہیں، جیسے کہ اسٹاک چارٹ، جہاں انفرادی بلندی اور کمیاں اہم ہیں۔ زیادہ تر اچھی چارٹنگ لائبریریوں (uPlot، Plotly، ECharts) میں ڈیسیمیشن بذریعہ ڈیفالٹ شامل ہوتا ہے۔

مرحلہ 6: بیرونی پیش کنندہ کو لپیٹیں۔

آپ کو ہمیشہ ڈرا لوپ خود لکھنے کی ضرورت نہیں ہے۔ لائف سائیکل مینجمنٹ میں بہترین ردعمل ظاہر کریں۔ لازمی چارٹنگ لائبریری میں بہترین خام کارکردگی ہے۔ جب لائبریری تیز اندرونی لوپ کو ہینڈل کرتی ہے تو رد عمل کو ماؤنٹ کرنے اور چارٹس کو ان ماؤنٹ کرنے دینا اکثر صحیح اقدام ہوتا ہے۔

import Uplot from "uplot";
import "uplot/dist/uPlot.min.css";
import { useEffect, useRef } from "react";

export default function UPlotChart({ data }: { data: AlignedData }) {
  const ref = useRef(null);
  const plotRef = useRef(null);

  useEffect(() => {
    const opts = {
      title: "Realtime Chart",
      width: 600,
      height: 300,
      series: [{}, { label: "Signal" }],
    };
    plotRef.current = new Uplot(opts, data, ref.current!);
    return () => plotRef.current?.destroy();
  }, []);

  useEffect(() => {
    plotRef.current?.setData(data); // imperative update, no React render
  }, [data]);

  return ;
}

uPlot، TimeChart، ECharts، Plotly، LightningChart، اور دیگر سبھی اس شکل کی پیروی کرتے ہیں: اسے اندرونی طور پر فوری بنائیں۔ useEffectکال setData بنیادی طور پر، ان ماؤنٹ ہونے پر اسے حذف کر دیا جاتا ہے۔ آرکیسٹریٹس کا رد عمل اور لائبریری رینڈر۔

ایک لازمی پیش کنندہ کے لئے ایک عام نمونہ ہے:

function ChartWrapper({ config, data }) {
  const ref = useRef(null);

  useEffect(() => {
    const chart = new SomeFastChartLib(ref.current, config);
    chart.setData(data);
    return () => chart.destroy();
  }, [config, data]);

  return ;
}

جب تک آپ کسی ایسے علاقے میں نہ ہوں جہاں آپ کو اپنی ڈرائنگ پر پکسل لیول کنٹرول کی ضرورت ہو، یہ سب سے زیادہ ROI اقدام ہے۔ پہلے لائبریریوں تک رسائی حاصل کریں، اور اگر کوئی مناسب لائبریری نہ ہو تو صرف اپنا ڈرائنگ لوپ لکھیں۔

مرحلہ 7: اگر کینوس کافی نہیں ہے تو WebGL استعمال کریں۔

Canvas 2D ہر فریم میں ہزاروں لائن سیگمنٹس کو آرام سے ہینڈل کرتا ہے۔ اس کے بعد یہ ملی سیکنڈ استعمال کرنے لگتا ہے۔ stroke() خود

WebGL (یا ایک اعلی سطحی ریپر جیسے: regl، twgl، pixi.js، deck.gl) رینڈرنگ کو GPU میں منتقل کرتا ہے۔ بغیر پسینے کے لاکھوں پوائنٹس حاصل کرنے کی صلاحیت حاصل کرنے کے لیے آپ کو پیشگی اخراجات (شیڈر رائٹنگ اور بفر مینجمنٹ) ادا کرنا ہوں گے۔

یہاں ایک کم سے کم WebGL "سنگل ورٹیکس شیڈر کے ساتھ لائن کی پٹی” کا خاکہ ہے۔

const gl = canvas.getContext("webgl2")!;

const program = gl.createProgram()!;
// ... compile vertex + fragment shaders, link, get attribute location ...

const samplesBuffer = gl.createBuffer();
function drawWebGL(samples: Float32Array) {
  gl.bindBuffer(gl.ARRAY_BUFFER, samplesBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, samples, gl.STREAM_DRAW);
  gl.useProgram(program);
  gl.drawArrays(gl.LINE_STRIP, 0, samples.length);
}

پیٹرن Canvas 2D جیسا ہی ہے۔ RAF لوپ کے اندر ایک لازمی ڈرا کال۔ فرق یہ ہے کہ GPU اصل راسٹرائزیشن کرتا ہے۔ زیادہ تر ریئل ٹائم چارٹنگ لائبریریاں جو "ملین پوائنٹس” کا دعوی کرتی ہیں اندرونی طور پر بالکل ایسا ہی کر رہی ہیں۔

رینڈرنگ کے اختیارات کا موازنہ کرنے والا فیصلہ ٹری: ہزاروں پوائنٹس کے ساتھ کینوس 2D، دسیوں ہزار پوائنٹس یا اس سے زیادہ کے ساتھ WebGL، 3D کے لیے WebGPU، حسب ضرورت شیڈرز، یا قابل اعتماد کمپیوٹ۔

Canvas 2D پر ڈیفالٹ، اور صرف WebGL پر بڑھیں اگر آپ یہ ثابت کر سکیں کہ کینوس پروفائلر کے لیے ایک رکاوٹ ہے۔

مرحلہ 8: میموری کو فلیٹ رکھیں

ریئل ٹائم ایپس آہستہ آہستہ مر جاتی ہیں۔ یہ ایک گھنٹہ تک آسانی سے چلتا ہے اور پھر لیپ ٹاپ کا پنکھا اندر چلا جاتا ہے۔ اس کی وجہ تقریبا ہمیشہ میموری ہوتی ہے۔

اپنے کولہوں کو پرسکون رکھنے کے لیے یہ تین اصول ہیں:

سب سے پہلے، عددی ڈیٹا کے لیے ٹائپ کردہ صفوں کا استعمال کریں۔ کوئی راستہ نہیں Float32Array 60,000 فلوٹنگ پوائنٹ نمبرز 240KB ہے۔ ایک ہی سائز کا ایک باقاعدہ JavaScript ارے تقریباً 1.4MB ہوگا اور ہر دھکے کے ساتھ GC پریشر پیدا کرے گا۔

دوسرا، اس نے سب کچھ ایک ساتھ باندھ دیا۔ صف میں اضافہ نہ کریں، رنگ بفر استعمال کریں۔ تاریخ، قطاروں، اور زیر التواء کاموں پر حدود رکھیں۔ جو بھی ~ کر سکتے ہیں۔ آخر کار یہ لامحدود طور پر بڑھے گا۔

تیسرا، بفرز کو دوبارہ استعمال کریں۔ سیٹ اپ پر ایک بار مختص کریں اور فریموں میں دوبارہ استعمال کریں۔ 60fps پر، فی فریم ایک نئی صف مختص کرنا 60 مختص فی سیکنڈ فی پلاٹ ہے۔ پلاٹوں کی تعداد سے ضرب کریں۔

// Bad: fresh array every frame.
function drawFrame() {
  const snapshot = store.read(); // returns a new Float32Array
  drawDecimated(ctx, snapshot, width);
}

// Better: reuse a draw buffer.
const drawBuf = new Float32Array(WINDOW_SIZE);

function drawFrame() {
  store.readInto(drawBuf); // writes into the existing buffer
  drawDecimated(ctx, drawBuf, width);
}

یہ اصول React کے لیے مخصوص نہیں ہیں۔ یہ ایک اصول ہے جس کی پیروی تمام ریئل ٹائم سسٹمز کرتے ہیں۔ ان کا ذکر کرنے کی وجہ یہ ہے کہ React کی سوچ کی شکل ("ہر بار جب آپ پیش کرتے ہیں تو ایک نئی صف حاصل کی جاتی ہے”) اس کے برعکس ہے جو حقیقی وقت چاہتا ہے۔

مرحلہ 9: نظام الاوقات کی حکمت عملی

تحفظات کے بغیر کارکن پول صرف ایک قطار ہے۔ کبھی کبھی آپ صرف ترجیحات چاہتے ہیں۔ صارف کے مرکز کے کاموں کو پس منظر کے جھاڑو سے پہلے ہونا چاہئے۔

ترجیحی قطار

چھوٹا ترجیحی شیڈولر:

type Priority = "high" | "normal" | "low";

class PriorityPool {
  private queues: Record = { high: [], normal: [], low: [] };
  private idle: Worker[];

  enqueue(job: Job, priority: Priority = "normal") {
    if (this.idle.length > 0) {
      const worker = this.idle.pop()!;
      this.dispatch(worker, job);
    } else {
      this.queues[priority].push(job);
    }
  }

  private next(): Job | null {
    for (const p of ["high", "normal", "low"] as const) {
      const j = this.queues[p].shift();
      if (j) return j;
    }
    return null;
  }

  private dispatch(worker: Worker, job: Job) {
    worker.postMessage(job.payload, job.transferables);
    worker.onmessage = (event) => {
      job.resolve(event.data);
      const next = this.next();
      if (next) this.dispatch(worker, next);
      else this.idle.push(worker);
    };
  }
}

عام طور پر، تین بالٹیاں کافی ہوتی ہیں: ہائی (صارف کی طرف سے شروع کی گئی)، درمیانی (مستحکم ریاست کی کارروائیاں)، اور کم (بیک گراؤنڈ سویپ، پری فیچ، ٹیلی میٹری فلش)۔

ٹکڑے ٹکڑے کرنے کے قابل کام

ایک آپریشن جس میں 2 سیکنڈ لگتے ہیں آپریٹر کو 2 سیکنڈ کے لیے بلاک کر دیتے ہیں۔ کارکنوں کو اعلی ترجیحی کاموں کا جواب دینے کی اجازت دینے کے لیے، آپ کو کاموں کو ٹکڑا کرنے کے قابل ہونے کی ضرورت ہے۔

// In the worker:
self.onmessage = async (event) => {
  const { id, kind, payload } = event.data;
  if (kind === "decode_large") {
    const total = payload.byteLength;
    for (let i = 0; i < total; i += CHUNK) {
      const chunk = decodeChunk(payload, i, Math.min(i + CHUNK, total));
      self.postMessage({ id, kind: "progress", chunk, offset: i });
      // Yield so the worker can check its message queue.
      await new Promise((r) => setTimeout(r, 0));
    }
    self.postMessage({ id, kind: "done" });
  }
  if (kind === "cancel") {
    // ... abort the current job
  }
};

ایک کارکن کے اندر حاصل کرنا مفت نہیں ہے، لیکن یہ کارکن کو منسوخی کے پیغامات یا اعلی ترجیحی کاموں کو سنبھالنے کی اجازت دیتا ہے جو بڑے کاموں میں شامل ہیں۔ طویل مدتی کاموں کے لیے (جیسے کہ فرم ویئر کو چمکانا، بڑی فائلوں کو ڈی کوڈ کرنا، یا پیمانے پر پیش کرنا)، یہ ضروری ہے۔

فین آؤٹ اور فین ان

ایک بڑے کام کو N ٹکڑوں میں تقسیم کریں، ہر ایک کو مختلف کارکن کو بھیجیں، اور نتائج جمع کریں۔

async function decodeFile(buffer: ArrayBuffer): Promise {
  const chunks = splitBuffer(buffer, 8);
  const results = await Promise.all(
    chunks.map((chunk) => pool.run("decode", chunk, [chunk])),
  );
  return results.flat();
}

یہ متوازی کام کے بوجھ کے لیے کارکنوں کی تعداد تک لکیری رفتار فراہم کرتا ہے۔ بیچ کے کاموں کے لیے یہ بہت اہم ہے جیسے ریکارڈنگ کو ڈی کوڈنگ کرنا، طویل دستاویزات کا خلاصہ کرنا، یا فولڈر ایمبیڈنگ کا حساب لگانا۔

مرحلہ 10: کارکردگی کی مسلسل پیمائش

پروفائلر میں اسپائکس ایک چیز ہیں۔ کچھ معاملات میں، ایک گھنٹہ کے دوران سست ہپ رینگنا ہوتا ہے. ریئل ٹائم ایپس کے لیے، ہم دو چیزوں کی پیمائش کرتے ہیں:

سب سے پہلے، فریم کی مستقل مزاجی. کیا آپ مسلسل 60fps کو مار رہے ہیں یا ہر وقت فریم چھوڑ رہے ہیں؟ ایک سادہ FPS میٹر:

let lastTime = performance.now();
let frames = 0;

function rafLoop(now: number) {
  frames++;
  if (now - lastTime >= 1000) {
    const fps = (frames * 1000) / (now - lastTime);
    frames = 0;
    lastTime = now;
    console.log(`fps: ${fps.toFixed(1)}`);
  }
  requestAnimationFrame(rafLoop);
}
requestAnimationFrame(rafLoop);

ایک زیادہ ایماندارانہ پیمائش ونڈو میں بدترین فریم ہے۔ اوسط فریم ٹائم ہنگامہ کو چھپاتا ہے۔

دوسرا، ہپ استحکام. DevTools Memory ٹیب کھولیں، ہیپ اسنیپ شاٹ لیں، ایپ کو 10 منٹ تک چلائیں، دوسرا سنیپ شاٹ لیں اور موازنہ کریں۔ فرق فلیٹ ہونا چاہئے۔ اگر یہ بڑھتا ہے تو، ایک رساو ہے. یعنی ایسی بندشیں جو مستقل سامعین، بڑھتی ہوئی صفوں، یا بڑی اشیاء کو رکھتی ہیں۔

اصلی ایپس کے لیے، آپ اپنے تجزیات میں ہیپ کے استعمال اور FPS کو جوڑ سکتے ہیں تاکہ صارفین کی شکایت کرنے سے پہلے ریگریشن پکڑ سکے۔

کیس اسٹڈی: 19 ای ای جی چینلز

اس آرٹیکل کو شروع کرنے والے پروجیکٹ پر واپس جائیں، ہر ایک 1 کلو ہرٹز پر 19 چینلز ہیں، سبھی بیک وقت پیش کیے گئے ہیں، اور سبھی کو کئی گھنٹے کے ریکارڈنگ سیشن میں ہموار رہنے کی ضرورت ہے۔

سب سے پہلے استعمال کیا جاتا ہے بجلی کا چارٹ. طاقتور، قابل اور خوبصورت۔ لیکن یہ بھی بھاری ہے۔ میموری کے استعمال میں نمایاں اضافہ ہوا، چارٹس کے انٹرنلز نے خود ہمارے استعمال کے معاملے کے لیے بہت زیادہ کام لیا (مکمل کثیر محور مالیاتی چارٹ کے بجائے 19 سادہ لائن پلاٹ)، اور لائسنسنگ رگڑ کا ایک نقطہ تھا۔

ہم ہیں پلاٹ. یہ چھوٹا، تیز، اور خاص طور پر ٹائم سیریز کے لیے لکھا گیا ہے۔ میموری کا استعمال کم ہو گیا، فی فریم رینڈرنگ کا وقت "بعض اوقات بجٹ سے زیادہ” سے "ہمیشہ انڈر بجٹ” میں چلا گیا، اور میرے کمپیوٹر نے آواز آنا بند کر دی جیسے یہ شروع ہونے ہی والا ہے۔ صرف چارٹ لائبریری کو تبدیل کرنے سے ہمیں زیادہ تر خالی جگہ مل گئی جس کی ہمیں ضرورت تھی۔

چارٹ کے ارد گرد فن تعمیر نے باقی کیا. پائپ لائن چار دھاگوں پر چلتی ہے۔

  • 1 جمع کرنے والا کارکن آلہ SDK سے بذریعہ IPC (الیکٹران کا مقامی عمل رینڈرر تک) پڑھیں۔

  • ایک SharedArrayBuffer اس میں تمام 19 چینلز کے لیے رولنگ ونڈوز ہیں۔

  • 1 پیش کنندہ SAB پڑھیں اور ڈرا کریں۔ OffscreenCanvas.

  • مرکزی دھاگہ کروم رینڈرنگ: چینل لیبلز، کنٹرولز اور ایف پی ایس میٹر۔

مشترکہ بفر لے آؤٹ ایک بڑی چیز ہے۔ Float32Array اس طرح ترتیب دیا گیا: [channel * samplesPerChannel + sampleIndex]. حصول کارکن ایک نیا نمونہ بناتا ہے اور ہیڈ پوائنٹر فی چینل سیٹ کرتا ہے ( Int32Array SAB ٹکڑا)۔ پیش کنندہ ہر فریم کو تازہ ترین ونڈو پڑھتا ہے۔

const CHANNELS = 19;
const WINDOW_SAMPLES = 60_000;

const sab = new SharedArrayBuffer(CHANNELS * WINDOW_SAMPLES * 4 + CHANNELS * 4);
const heads = new Int32Array(sab, 0, CHANNELS);
const samples = new Float32Array(sab, CHANNELS * 4, CHANNELS * WINDOW_SAMPLES);

function writeSample(channel: number, value: number) {
  const head = Atomics.load(heads, channel);
  samples[channel * WINDOW_SAMPLES + head] = value;
  Atomics.store(heads, channel, (head + 1) % WINDOW_SAMPLES);
}

پیش کنندہ چینل بفر کو پڑھتا ہے، فی پکسل کالم کو ختم کرتا ہے، اور ڈرا کرتا ہے:

// render.worker.ts
function drawFrame() {
  const ctx = offscreenCtx;
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  for (let ch = 0; ch < CHANNELS; ch++) {
    const start = ch * WINDOW_SAMPLES;
    drawDecimatedRow(ctx, samples.subarray(start, start + WINDOW_SAMPLES), ch);
  }
  requestAnimationFrame(drawFrame);
}

2024 M3 MacBook Pro 1kHz پر 19 چینلز کے ساتھ 60fps، یا 144fps اگر ڈسپلے سپورٹ کرتا ہے۔ مین تھریڈ کا استعمال 1% سے نیچے رہتا ہے۔ کارکن اسپیئر کور استعمال کرتے ہیں۔ صارفین مقامی ایپس کی ضرورت محسوس کرتے ہیں۔

سبق، مختصراً، یہ ہے کہ زیادہ تر کام آپ کے اندرونی لوپ کے لیے صحیح ٹول کو منتخب کرنے اور اس سے باہر نکلنے کے لیے آتا ہے۔

بینچ مارکس: سنگل تھریڈڈ اور ملٹی تھریڈڈ

2024 M3 MacBook Pro پر اسی طرح کے کام کا بوجھ چلانے کے نتائج یہ ہیں۔ وہ مضمر ہیں، وعدے نہیں۔

کام کا بوجھ صرف مین تھریڈ کارکن پول ورکرز + ایس اے بی ورکر + SAB + آف اسکرین کینوس
100MB بائنری فائل کو پارس کریں۔ 4.2 سیکنڈ (UI منجمد) 1.1 سیکنڈ 1.0 سیکنڈ 1.0 سیکنڈ
1,000 فریمز کو ڈی کوڈ کیا گیا۔ 920ms 280ms 240ms 240ms
1M پوائنٹ چارٹ رینڈرنگ 24fps 24fps 28fps 60fps
ٹیلی میٹری: 4 اسٹریمز x 1000Hz 22fps 38fps 55fps 60fps
EEG: 19 چینلز x 1kHz 12fps 25fps 48fps 60fps (144fps ممکن ہے)
مین تھریڈ JS ٹائم فی فریم 22ms 8ms 4ms < 1ms
میموری اوپر بیس لائن +50MB +20MB +20MB
آپریٹر اپ ٹائم (پہلی کال) 0 2-5ms 2-5ms 5-10ms

پیٹرن: صرف کارکن مدد کرتے ہیں، کارکنان اور مشترکہ میموری زیادہ مدد کرتے ہیں۔ ورکر پلس مشترکہ میموری پلس OffscreenCanvas مجھے پیغام ملتا ہے "مین تھریڈ کچھ نہیں کر رہا ہے اور چارٹ ابھی تک ہموار ہے۔"

چارٹ پر مرکوز ایپس کے لیے 'ورکر' سے 'ورکر پلس' تک کی چھلانگ OffscreenCanvas"ایک واحد سب سے بڑی تعمیراتی بہتری ہے جسے آپ اپنے براؤزر کو چھوڑے بغیر استعمال کر سکتے ہیں۔

COOP/COEP کیچ

SharedArrayBuffer سپیکٹر/میلٹ ڈاؤن کے بعد 2020 میں ہائی ریزولوشن ٹائمرز کو بڑھا دیا گیا ہے۔ اسے استعمال کرنے کے لیے، صفحہ پر دو HTTP ہیڈر فراہم کیے جائیں:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

یہ صفحہ "کراس اوریجن آئسولیشن" کے لیے منتخب کرے گا۔ ایک الگ تھلگ سیاق و سباق کے اندر، SharedArrayBuffer موجود ہے performance.now() یہ ہائی ریزولوشن ہے اور مختلف دیگر محدود APIs کام کرتے ہیں۔

ایک الگ تھلگ سیاق و سباق سے باہر، SharedArrayBuffer غیر متعینہ performance.now() یہ تقریبا 1ms صحت سے متعلق تک محدود ہے اور جوہری پائے جاتے ہیں۔

لاگت اہم ہے۔ require-corp اس کا مطلب ہے کہ تمام کراس اوریجن وسائل (CDN سے امیجز، ایمبیڈڈ یوٹیوب ویڈیوز، تھرڈ پارٹی فونٹس، اینالیٹکس اسکرپٹس) کو واضح طور پر درج ذیل ترتیبات کے ذریعے منتخب کیا جانا چاہیے: Cross-Origin-Resource-Policy: cross-origin یا Cross-Origin-Embedder-Policy: credentialless. بہت سی تیسری پارٹی کی خدمات ایسا نہیں کرتی ہیں، جس سے سرایت ٹوٹ جاتی ہے۔

دو عملی اختیارات ہیں:

  • الیکٹران ایپس کے لیے: آپ ان ہیڈرز کو آسانی سے استعمال کرنے کے لیے اپنے پیش کنندہ کے عمل کو ترتیب دے سکتے ہیں۔ زیادہ تر پروڈکشن الیکٹران ایپس جو ریئل ٹائم ویژولائزیشن چاہتی ہیں اسے بطور ڈیفالٹ فعال کرتی ہیں۔

  • براؤزر ایپس کے لیے: آپ کی حاصل کردہ کارکردگی کا موازنہ کریں بمقابلہ ایمبیڈنگز کے وزن سے جو آپ کھو سکتے ہیں۔ اگر آپ کی ایپ مرکزی توجہ کا مرکز ہے (Figma، Google Docs)، تو اس کے لیے جائیں۔ اگر آپ فریق ثالث کے ویجٹ پر انحصار کرتے ہیں، تو قیمتیں حقیقی ہیں۔

بہت سے چارٹس یا سگنلز والی ایپس کے لیے جنہیں SAB کی ضرورت ہوتی ہے، الیکٹران کا راستہ عام طور پر صاف ہوتا ہے۔

پیداوار کی تجارت

مندرجہ بالا سبھی کے لیے پانچ اصل اخراجات یہ ہیں:

  • کوڈ کی پیچیدگی: ورکر سنٹرک ایپس میں سنگل تھریڈڈ ایپس (مین + ورکر + مشترکہ اقسام) کے مقابلے سورس فائلوں کی تعداد 2 سے 4 گنا زیادہ ہوتی ہے۔ مہذب پیمانے پر یہ اس کے قابل ہے، لیکن معمولی ایپس کے لیے یہ تکلیف دہ ہے۔

  • ڈیبگنگ: ڈھیر کے نشانات دھاگوں میں تقسیم ہوتے ہیں۔ Chrome DevTools اسے 2026 میں اچھی طرح سے ہینڈل کرتا ہے (ہر کارکن کا اپنا ڈیبگر پینل ہوتا ہے)، لیکن اسے اب بھی سنگل تھریڈ والے بگ سے زیادہ کام کی ضرورت ہوتی ہے۔

  • بنڈل سائز: ہر کارکن ایک الگ حصہ ہے۔ ایک کارکن کے اندر درخت ہلانے والے بعض اوقات مرکزی کارکن سے بدتر (کم بالغ) ہوتے ہیں۔ ورکر بنڈل کا الگ سے آڈٹ کریں۔

  • آغاز کا انتظار کا وقت: ایپ سٹارٹ اپ پر ایک کارکن کو پیدا کرنے سے 50 سے 200 ms کا اضافہ ہوتا ہے۔ اسپلش اسکرین پر پری وارمنگ یا پہلے فریم میں تاخیر کی اجازت دیں۔

  • براؤزر API فرق: localStorage، documentاور زیادہ تر DOM APIs کارکنوں پر دستیاب نہیں ہیں۔ کچھ لائبریریاں جن پر آپ انحصار کرتے ہیں اور خاموشی سے توڑ دیتے ہیں۔ لائبریریوں کو بنڈل کرنے سے پہلے جن کی آپ نے ابھی تک کوشش نہیں کی ہے، انہیں کارکن کے حوالے سے جانچیں۔

لازمی رینڈرنگ پیٹرن کے تین فوائد اور نقصانات ہیں:

  • ڈیٹا کی اعلانیہ حرکت پذیری: چارٹ فریم، لیبل، اور کنٹرول سبھی اعلانیہ ہیں۔ چارٹ کے اندر موجود ڈیٹا ضروری ہے۔

  • رینڈر شدہ آؤٹ پٹ کی آسان سنیپ شاٹ ٹیسٹنگ: کینوس میں ایسا DOM نہیں ہے جس سے استفسار کیا جا سکے۔ ڈرا پاتھ سے الگ ڈیٹا پاتھ کی جانچ کریں۔ اسٹوریج آؤٹ پٹ کے سنیپ شاٹس لیں، پکسلز نہیں۔

  • اندرونی لوپس کے بارے میں رد عمل کی جزو کی کہانی: ڈرا لوپ ایک بندش ہے۔ ڈرا لوپ بنانا کسی جزو کی تعمیر سے زیادہ مشکل ہے۔ اپنے اجزاء کی حدود کا انتخاب احتیاط سے کریں تاکہ ہر کینوس ایک کام کرے۔

زیادہ تر ایپس کے لیے، یہ قیمت کے قابل نہیں ہے۔ ان ایپس کے لیے جنہیں 1kHz ڈیٹا آسانی سے رینڈر کرنے کی ضرورت ہے، یہ داخلہ کی قیمت ہے۔

کیا ہمیں اسے اس طرح بنانا چاہئے؟

اگر آپ کے ڈیٹا کی شرح 30 اپ ڈیٹس فی سیکنڈ فی سلسلہ سے کم ہے، تو اس میں سے کوئی بھی ضروری نہیں ہے۔ معصوم setState یہ بیچ بہ بیچ کی بنیاد پر کام کرتا ہے۔ پہلے اپنا پروفائل بنائیں، دوسرا بہتر بنائیں۔

اس فن تعمیر کا حامل ہے اگر:

  • بہت سے سلسلے یا سینسر

  • پھٹے بغیر مسلسل کھایا جاتا ہے۔

  • UX کا وعدہ گھنٹوں کے لیے ہموار آپریشن ہے، سیکنڈ نہیں۔

  • میں وہاں جانے کے لیے UI کو اپنی مادری زبان میں دوبارہ نہ لکھنے کی تجویز کروں گا۔

بورنگ رولز: شروع کرنا requestAnimationFrame انضمام اور بیرونی اسٹوریج۔ اگر یہ کافی نہیں ہے تو اپنے کارکنوں کو فروغ دیں۔ ورکرز کے کام کرتے وقت مشترکہ میموری کو فروغ دیں۔ postMessage یہ ایک رکاوٹ ہے۔ فروغ OffscreenCanvas جب رینڈرنگ لوپ خود ہی رکاوٹ ہے۔ ہر قدم ایک حقیقی تعمیراتی سرمایہ کاری ہے۔ انہیں ترتیب سے لے لو۔

ختم

ردعمل، اگر صحیح طریقے سے استعمال کیا جائے تو، حقیقی وقت کے تصور کو سنبھال سکتا ہے۔ تمام کام کرنے کے لیے React کو دھکیلنے کے بجائے، اسے اپنے کنڈکٹر کے طور پر استعمال کریں۔ پیشہ ور لائبریریوں اور کارکنوں کو بھاری لفٹنگ کرنے دیں۔

یہاں تین اصول ہیں جو میرے بنائے ہوئے ہر ہائی فریکوئینسی ری ایکٹ ایپ پر لاگو ہوتے ہیں:

  1. ورکر کام کرتا ہے اور مین UI کرتا ہے۔ اگر مین ریاضی کر رہا ہے، تو آپ نے اسے غلط جگہ پر رکھا ہے۔

  2. ہو سکے تو بھیجیں، اگر ضروری ہو تو شیئر کریں۔ دونوں نے کلون کو شکست دی۔ اشتراک بھیجنے سے زیادہ پیچیدہ ہے۔

  3. ری ایکٹ کو آرکیسٹریٹ کرنے دیں۔ خصوصی ٹولز پیش کرنے کی کوشش کریں۔ اسٹور ڈیٹا کا مالک ہے، ڈرا لوپ اقدار کا مالک ہے، اور React شکلوں کا مالک ہے۔

اس کو صحیح طریقے سے کرنے سے آپ کی React ایپ کو ایک واحد کور سسٹم بننے سے روکے گا جو ہائی فریکوئنسی ڈیٹا پر اڑتا ہے۔ واقعی ایک ملٹی کور سسٹم بنیں جو آپ کے ہارڈ ویئر کے ساتھ پیمانہ بناتا ہے، بغیر قطروں کے کھاتا ہے، اور بغیر کسی رکاوٹ کے پیش کرتا ہے۔

بنیادی وہیں ہے۔ ان کا استعمال کریں۔

حوالہ جات

Scroll to Top