بیچ استفسار کی پروسیسنگ کے مسائل اور ان کا حل (حصہ 1)

بیچ استفسار کی پروسیسنگ کے مسائل اور ان کا حل (حصہ 1)تقریباً تمام جدید سافٹ ویئر پروڈکٹس کئی خدمات پر مشتمل ہیں۔ اکثر، انٹرسروس چینلز کے طویل جوابی اوقات کارکردگی کے مسائل کا باعث بن جاتے ہیں۔ اس قسم کے مسئلے کا معیاری حل یہ ہے کہ متعدد انٹر سروس درخواستوں کو ایک پیکج میں پیک کیا جائے، جسے بیچنگ کہتے ہیں۔

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

مظاہرے کا منصوبہ

وضاحت کے لیے، آئیے اس ایپلی کیشن میں موجود خدمات میں سے ایک کی مثال دیکھیں جس پر میں فی الحال کام کر رہا ہوں۔

مثالوں کے لیے پلیٹ فارم کے انتخاب کی وضاحتخراب کارکردگی کا مسئلہ بہت عام ہے اور کسی مخصوص زبان یا پلیٹ فارم سے متعلق نہیں ہے۔ یہ مضمون مسائل اور حل کو ظاہر کرنے کے لیے Spring + Kotlin کوڈ کی مثالیں استعمال کرے گا۔ کوٹلن جاوا اور C# ڈویلپرز کے لیے یکساں طور پر قابل فہم (یا ناقابل فہم) ہے؛ اس کے علاوہ، کوڈ جاوا کے مقابلے میں زیادہ کمپیکٹ اور قابل فہم ہے۔ خالص Java ڈویلپرز کے لیے چیزوں کو سمجھنے میں آسان بنانے کے لیے، میں کوٹلن کے کالے جادو سے بچوں گا اور صرف سفید جادو (لومبوک کی روح میں) استعمال کروں گا۔ توسیع کے چند طریقے ہوں گے، لیکن وہ دراصل جاوا کے تمام پروگرامرز کو جامد طریقوں کے طور پر واقف ہیں، اس لیے یہ ایک چھوٹی سی چینی ہوگی جو ڈش کا ذائقہ خراب نہیں کرے گی۔
دستاویز کی منظوری کی خدمت ہے۔ کوئی ایک دستاویز بناتا ہے اور اسے بحث کے لیے پیش کرتا ہے، جس کے دوران ترمیم کی جاتی ہے، اور بالآخر دستاویز پر اتفاق ہو جاتا ہے۔ منظوری کی خدمت خود دستاویزات کے بارے میں کچھ نہیں جانتی ہے: یہ صرف چھوٹے اضافی افعال کے ساتھ منظوری دینے والوں کی ایک چیٹ ہے جس پر ہم یہاں غور نہیں کریں گے۔

لہذا، وہاں چیٹ رومز ہیں (دستاویزات کے مطابق) ان میں سے ہر ایک میں شرکاء کے پہلے سے طے شدہ سیٹ کے ساتھ۔ جیسا کہ باقاعدہ چیٹس میں ہوتا ہے، پیغامات میں متن اور فائلیں ہوتی ہیں اور یہ جوابات یا آگے بھیجے جا سکتے ہیں:

data class ChatMessage(
  // nullable так как появляется только после persist
  val id: Long? = null,
  /** Ссылка на автора */
  val author: UserReference,
  /** Сообщение */
  val message: String,
  /** Ссылки на аттачи */
  // из-за особенностей связки JPA+СУБД проще поддерживать и null, и пустые списки
  val files: List<FileReference>? = null,
  /** Если является ответом, то здесь будет оригинал */
  val replyTo: ChatMessage? = null,
  /** Если является пересылкой, то здесь будет оригинал */
  val forwardFrom: ChatMessage? = null
)

فائل اور صارف کے لنکس دوسرے ڈومینز کے لنکس ہیں۔ یہاں ہم اس طرح رہتے ہیں:

typealias FileReference Long
typealias UserReference Long

صارف کا ڈیٹا Keycloak میں محفوظ کیا جاتا ہے اور REST کے ذریعے موصول ہوتا ہے۔ فائلوں کے لیے بھی ایسا ہی ہوتا ہے: فائلیں اور ان کے بارے میں معلومات ایک علیحدہ فائل اسٹوریج سروس میں رہتی ہیں۔

ان خدمات کے لیے تمام کالز ہیں۔ بھاری درخواستیں. اس کا مطلب یہ ہے کہ ان درخواستوں کو منتقل کرنے کا اوور ہیڈ فریق ثالث سروس کے ذریعہ ان پر کارروائی کرنے میں لگنے والے وقت سے کہیں زیادہ ہے۔ ہمارے ٹیسٹ بینچوں پر، ایسی خدمات کے لیے عام کال کا وقت 100 ms ہے، اس لیے ہم مستقبل میں ان نمبروں کا استعمال کریں گے۔

ہمیں تمام ضروری معلومات کے ساتھ آخری N پیغامات موصول کرنے کے لیے ایک سادہ REST کنٹرولر بنانے کی ضرورت ہے۔ یعنی، ہم سمجھتے ہیں کہ فرنٹ اینڈ میں پیغام کا ماڈل تقریباً ایک جیسا ہے اور تمام ڈیٹا کو بھیجنے کی ضرورت ہے۔ فرنٹ اینڈ ماڈل کے درمیان فرق یہ ہے کہ فائل اور صارف کو لنک بنانے کے لیے انہیں قدرے ڈکرپٹ شدہ شکل میں پیش کرنے کی ضرورت ہے:

/** В таком виде отдаются ссылки на сущности для фронта */
data class ReferenceUI(
  /** Идентификатор для url */
  val ref: String,
  /** Видимое пользователю название ссылки */
  val name: String
)
data class ChatMessageUI(
  val id: Long,
  /** Ссылка на автора */
  val author: ReferenceUI,
  /** Сообщение */
  val message: String,
  /** Ссылки на аттачи */
  val files: List<ReferenceUI>,
  /** Если являтся ответом, то здесь будет оригинал */
  val replyTo: ChatMessageUI? = null,
  /** Если являтся пересылкой, то здесь будет оригинал */
  val forwardFrom: ChatMessageUI? = null
)

ہمیں مندرجہ ذیل کو لاگو کرنے کی ضرورت ہے:

interface ChatRestApi {
  fun getLast(nInt): List<ChatMessageUI>
}

پوسٹ فکس UI کا مطلب ہے فرنٹ اینڈ کے لیے ڈی ٹی او ماڈلز، یعنی جو ہمیں REST کے ذریعے پیش کرنا ہے۔

یہاں حیران کن بات یہ ہے کہ ہم کسی بھی چیٹ شناخت کنندہ کو پاس نہیں کر رہے ہیں اور یہاں تک کہ ChatMessage/ChatMessageUI ماڈل میں بھی کوئی نہیں ہے۔ میں نے یہ جان بوجھ کر کیا تاکہ مثالوں کے کوڈ میں بے ترتیبی نہ ہو (چیٹ الگ تھلگ ہیں، لہذا ہم فرض کر سکتے ہیں کہ ہمارے پاس صرف ایک ہے)۔

فلسفیانہ تفرقہدونوں ChatMessageUI کلاس اور ChatRestApi.getLast طریقہ فہرست ڈیٹا کی قسم کا استعمال کرتے ہیں، جب کہ حقیقت میں یہ ایک ترتیب شدہ سیٹ ہے۔ JDK میں یہ سب برا ہے، لہذا انٹرفیس کی سطح پر عناصر کی ترتیب کا اعلان کرنا (شامل کرنے اور بازیافت کرتے وقت ترتیب کو محفوظ رکھنا) کام نہیں کرے گا۔ لہٰذا یہ عام ہو گیا ہے کہ فہرست کو ایسے معاملات میں استعمال کرنا جہاں آرڈرڈ سیٹ کی ضرورت ہو (وہاں LinkedHashSet بھی ہے، لیکن یہ انٹرفیس نہیں ہے)۔
اہم پابندی: ہم فرض کریں گے کہ جوابات یا منتقلی کی کوئی لمبی زنجیریں نہیں ہیں۔ یعنی وہ موجود ہیں لیکن ان کی لمبائی تین پیغامات سے زیادہ نہیں ہے۔ پیغامات کا پورا سلسلہ فرنٹ اینڈ پر منتقل ہونا چاہیے۔

بیرونی خدمات سے ڈیٹا حاصل کرنے کے لیے درج ذیل APIs ہیں:

interface ChatMessageRepository {
  fun findLast(nInt): List<ChatMessage>
}
data class FileHeadRemote(
  val id: FileReference,
  val name: String
)
interface FileRemoteApi {
  fun getHeadById(idFileReference): FileHeadRemote
  fun getHeadsByIds(idSet<FileReference>): Set<FileHeadRemote>
  fun getHeadsByIds(idList<FileReference>): List<FileHeadRemote>
  fun getHeadsByChat(): List<FileHeadRemote>
}
data class UserRemote(
  val id: UserReference,
  val name: String
)
interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote>
  fun getUsersByIds(idList<UserReference>): List<UserRemote>
}

یہ دیکھا جا سکتا ہے کہ بیرونی خدمات ابتدائی طور پر بیچ پروسیسنگ کے لیے فراہم کرتی ہیں، اور دونوں قسموں میں: Set کے ذریعے (عناصر کی ترتیب کو محفوظ کیے بغیر، منفرد کلیدوں کے ساتھ) اور فہرست کے ذریعے (یہاں ڈپلیکیٹس ہو سکتے ہیں - آرڈر محفوظ ہے)۔

سادہ نفاذ

بولی نفاذ

ہمارے REST کنٹرولر کا پہلا سادہ عمل زیادہ تر معاملات میں کچھ اس طرح نظر آئے گا:

class ChatRestController(
  private val messageRepository: ChatMessageRepository,
  private val userRepository: UserRemoteApi,
  private val fileRepository: FileRemoteApi
) : ChatRestApi {
  override fun getLast(nInt) =
    messageRepository.findLast(n)
      .map it.toFrontModel() }
  
  private fun ChatMessage.toFrontModel(): ChatMessageUI =
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = userRepository.getUserById(author).toFrontReference(),
      message = message,
      files = files?.let files ->
        fileRepository.getHeadsByIds(files)
          .map it.toFrontReference() }
      } ?: listOf(),
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
}

سب کچھ بہت واضح ہے، اور یہ ایک بڑا پلس ہے۔

ہم بیچ پروسیسنگ کا استعمال کرتے ہیں اور بیچوں میں ایک بیرونی سروس سے ڈیٹا وصول کرتے ہیں۔ لیکن ہماری پیداوری کا کیا ہو رہا ہے؟

ہر پیغام کے لیے، مصنف کے فیلڈ پر ڈیٹا حاصل کرنے کے لیے UserRemoteApi کو ایک کال کی جائے گی اور تمام منسلک فائلوں کو حاصل کرنے کے لیے FileRemoteApi کو ایک کال کی جائے گی۔ ایسا لگتا ہے۔ بتاتے چلیں کہ ChatMessage کے فارورڈ فرام اور ریپلائی ٹو فیلڈز اس طرح حاصل کیے گئے ہیں کہ اس کے لیے غیر ضروری کالز کی ضرورت نہیں ہے۔ لیکن انہیں ChatMessageUI میں تبدیل کرنے سے تکرار ہو جائے گی، یعنی کال کاؤنٹرز میں نمایاں اضافہ ہو سکتا ہے۔ جیسا کہ ہم نے پہلے نوٹ کیا، آئیے فرض کریں کہ ہمارے پاس زیادہ گھوںسلا نہیں ہے اور یہ سلسلہ تین پیغامات تک محدود ہے۔

نتیجے کے طور پر، ہمیں ہر پیغام کے لیے دو سے چھ کالیں بیرونی خدمات اور پیغامات کے پورے پیکج کے لیے ایک JPA کال موصول ہوں گی۔ کالوں کی کل تعداد 2*N+1 سے 6*N+1 تک مختلف ہوگی۔ حقیقی یونٹس میں یہ کتنا ہے؟ ہم کہتے ہیں کہ ایک صفحہ کو رینڈر کرنے میں 20 پیغامات درکار ہیں۔ انہیں حاصل کرنے کے لیے، آپ کو 4 s سے 10 s تک کی ضرورت ہوگی۔ خوفناک! میں اسے 500ms کے اندر رکھنا چاہوں گا۔ اور چونکہ انہوں نے فرنٹ اینڈ پر بغیر کسی رکاوٹ کے طومار کرنے کا خواب دیکھا تھا، اس لیے اس اختتامی نقطہ کے لیے کارکردگی کی ضروریات کو دگنا کیا جا سکتا ہے۔

پیشہ:

  1. کوڈ جامع اور خود دستاویزی ہے (ایک سپورٹ ٹیم کا خواب)۔
  2. کوڈ آسان ہے، لہذا اپنے آپ کو پاؤں میں گولی مارنے کے تقریباً کوئی مواقع نہیں ہیں۔
  3. بیچ پروسیسنگ کسی اجنبی چیز کی طرح نہیں لگتی ہے اور یہ منطق میں باضابطہ طور پر مربوط ہے۔
  4. منطقی تبدیلیاں آسان ہوں گی اور مقامی ہوں گی۔

تفریق:

بہت چھوٹے پیکٹوں کی وجہ سے خوفناک کارکردگی۔

یہ نقطہ نظر اکثر سادہ خدمات یا پروٹو ٹائپس میں دیکھا جا سکتا ہے۔ اگر تبدیلیاں کرنے کی رفتار اہم ہے، تو یہ نظام کو پیچیدہ بنانے کے قابل نہیں ہے۔ ایک ہی وقت میں، ہماری انتہائی سادہ سروس کے لیے کارکردگی خوفناک ہے، اس لیے اس نقطہ نظر کے لاگو ہونے کا دائرہ بہت تنگ ہے۔

بولی متوازی پروسیسنگ

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

متوازی پروسیسنگ کو لاگو کرنا بہت آسان ہے:

override fun getLast(nInt) =
  messageRepository.findLast(n).parallelStream()
    .map it.toFrontModel() }
    .collect(toList())

متوازی میسج پروسیسنگ کا استعمال کرتے ہوئے، ہم مثالی طور پر 300–700 ms حاصل کرتے ہیں، جو کہ ایک سادہ نفاذ کے مقابلے میں بہت بہتر ہے، لیکن پھر بھی کافی تیز نہیں ہے۔

اس نقطہ نظر کے ساتھ، userRepository اور fileRepository کی درخواستوں کو ہم وقت سازی کے ساتھ عمل میں لایا جائے گا، جو زیادہ موثر نہیں ہے۔ اسے ٹھیک کرنے کے لیے، آپ کو کال لاجک کو کافی حد تک تبدیل کرنا پڑے گا۔ مثال کے طور پر، CompletionStage کے ذریعے (عرف Completable Future):

private fun ChatMessage.toFrontModel(): ChatMessageUI =
  CompletableFuture.supplyAsync {
    userRepository.getUserById(author).toFrontReference()
  }.thenCombine(
    files?.let {
      CompletableFuture.supplyAsync {
        fileRepository.getHeadsByIds(files).map it.toFrontReference() }
      }
    } ?: CompletableFuture.completedFuture(listOf())
  ) authorfiles ->
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = author,
      message = message,
      files = files,
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
  }.get()!!

یہ دیکھا جا سکتا ہے کہ ابتدائی طور پر سادہ میپنگ کوڈ کم سمجھ میں آ گیا ہے۔ اس کی وجہ یہ ہے کہ ہمیں بیرونی خدمات پر کالیں الگ کرنی پڑیں جہاں سے نتائج استعمال کیے جاتے ہیں۔ یہ اپنے آپ میں برا نہیں ہے۔ لیکن کالوں کو یکجا کرنا خاص طور پر خوبصورت نہیں لگتا اور ایک عام رد عمل والے "نوڈل" سے ملتا جلتا ہے۔

اگر آپ کوروٹین استعمال کرتے ہیں، تو سب کچھ زیادہ مہذب نظر آئے گا:

private fun ChatMessage.toFrontModel(): ChatMessageUI =
  join(
    userRepository.getUserById(author).toFrontReference() },
    files?.let fileRepository.getHeadsByIds(files)
      .map it.toFrontReference() } } ?: listOf() }
  ).let (author, files) ->
    ChatMessageUI(
      id = id ?: throw IllegalStateException("$this must be persisted"),
      author = author,
      message = message,
      files = files,
      forwardFrom = forwardFrom?.toFrontModel(),
      replyTo = replyTo?.toFrontModel()
    )
  }

کہاں:

fun <ABjoin(a: () -> Ab: () -> B) =
  runBlocking(IO{
    awaitAll(async a() }async b() })
  }.let {
    it[0as to it[1as B
  }

نظریاتی طور پر، اس طرح کے متوازی پروسیسنگ کا استعمال کرتے ہوئے، ہمیں 200-400 ms ملے گا، جو پہلے سے ہی ہماری توقعات کے قریب ہے۔

بدقسمتی سے، اس طرح کی اچھی متوازی نہیں ہوتی ہے، اور ادا کرنے کی قیمت کافی ظالمانہ ہے: صرف چند صارفین ایک ہی وقت میں کام کرنے کے ساتھ، خدمات کو درخواستوں کی ایک لہر کا سامنا کرنا پڑے گا جن پر بہرحال متوازی کارروائی نہیں کی جائے گی، لہذا ہم ہمارے اداس 4s پر واپس آ جائیں گے۔

ایسی سروس استعمال کرتے وقت میرا نتیجہ 1300 پیغامات پر کارروائی کے لیے 1700–20 ms ہے۔ یہ پہلے نفاذ کے مقابلے میں تیز ہے، لیکن پھر بھی مسئلہ حل نہیں کرتا۔

متوازی سوالات کے متبادل استعمالاگر تھرڈ پارٹی سروسز بیچ پروسیسنگ فراہم نہیں کرتی ہیں تو کیا ہوگا؟ مثال کے طور پر، آپ انٹرفیس طریقوں کے اندر بیچ پروسیسنگ کے نفاذ کی کمی کو چھپا سکتے ہیں:

interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toSet())
  fun getUsersByIds(idList<UserReference>): List<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toList())
}

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

  1. پیغام پر مبنی متوازی پروسیسنگ کو آسانی سے لاگو کریں۔
  2. اچھی اسکیل ایبلٹی۔

Cons:

  1. متوازی طور پر مختلف خدمات کی درخواستوں پر کارروائی کرتے وقت ڈیٹا کے حصول کو اس کی پروسیسنگ سے الگ کرنے کی ضرورت ہے۔
  2. تیسری پارٹی کی خدمات پر بوجھ میں اضافہ۔

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

کیشنگ۔

آپ بیرونی خدمات کے لیے JPA کی روح کے مطابق کیشنگ کر سکتے ہیں، یعنی موصول ہونے والی اشیاء کو سیشن کے اندر اسٹور کر سکتے ہیں تاکہ انہیں دوبارہ موصول نہ ہو (بشمول بیچ پروسیسنگ کے دوران)۔ آپ اس طرح کے کیشز خود بنا سکتے ہیں، آپ اسپرنگ کو اس کے @Cacheable کے ساتھ استعمال کر سکتے ہیں، اس کے علاوہ آپ ہمیشہ EhCache جیسے ریڈی میڈ کیشے کو دستی طور پر استعمال کر سکتے ہیں۔

ایک عام مسئلہ یہ ہوگا کہ کیش صرف اس صورت میں کارآمد ہیں جب ان میں ہٹ ہوں۔ ہمارے معاملے میں، مصنف کے فیلڈ پر ہٹ ہونے کا بہت امکان ہے (آئیے کہتے ہیں، 50%)، لیکن فائلوں پر کوئی ہٹ نہیں ہوگی۔ یہ نقطہ نظر کچھ بہتری فراہم کرے گا، لیکن یہ پیداواری صلاحیت کو یکسر تبدیل نہیں کرے گا (اور ہمیں ایک پیش رفت کی ضرورت ہے)۔

انٹرسیشن (لمبی) کیچز کے لیے پیچیدہ باطل منطق کی ضرورت ہوتی ہے۔ عام طور پر، جتنی دیر بعد آپ انٹرسیشن کیچز کا استعمال کرتے ہوئے کارکردگی کے مسائل کو حل کرنے پر اتریں گے، اتنا ہی بہتر ہے۔

پیشہ:

  1. کوڈ کو تبدیل کیے بغیر کیشنگ کو لاگو کریں۔
  2. پیداواری صلاحیت میں کئی گنا اضافہ (کچھ معاملات میں)۔

Cons:

  1. اگر غلط استعمال کیا جائے تو کارکردگی میں کمی کا امکان۔
  2. بڑی میموری اوور ہیڈ، خاص طور پر لمبی کیچز کے ساتھ۔
  3. پیچیدہ باطل، غلطیاں جن میں رن ٹائم میں دوبارہ پیدا کرنے میں مشکل مسائل پیدا ہوں گے۔

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

ہماری مثال میں، کیچز تقریباً 25% کی کارکردگی میں اضافہ فراہم کریں گے۔ ایک ہی وقت میں، کیچز کے کافی نقصانات ہیں، لہذا میں انہیں یہاں استعمال نہیں کروں گا۔

کے نتائج

لہذا، ہم نے ایک سروس کے سادہ نفاذ کو دیکھا جو بیچ پروسیسنگ کا استعمال کرتی ہے، اور اسے تیز کرنے کے کئی آسان طریقے۔

ان تمام طریقوں کا بنیادی فائدہ سادگی ہے، جس کے بہت سے خوشگوار نتائج برآمد ہوتے ہیں۔

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

دو اہم سمتیں ہیں جن میں آپ حل تلاش کر سکتے ہیں:

  • اعداد و شمار کے ساتھ غیر مطابقت پذیر کام (پیراڈیم شفٹ کی ضرورت ہوتی ہے، لہذا اس مضمون میں اس پر بحث نہیں کی گئی ہے)؛
  • ہم وقت ساز پروسیسنگ کو برقرار رکھتے ہوئے بیچوں کو بڑھانا۔

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

ماخذ: www.habr.com

نیا تبصرہ شامل کریں