প্রচুর সংখ্যক করটিইন যদিও হালকা ওজনের, তবুও অ্যাপ্লিকেশনগুলির দাবি করতে সমস্যা হতে পারে
আমি তাদের সমস্যাটির প্রকৃত ব্যয়ের পরিমাণ নির্ধারণ করে একটি সমস্যা হিসাবে "অনেকগুলি কর্টিন" এই কল্পকাহিনীটি দূর করতে চাই।
প্রথমত, আমাদের যে কর্টিন প্রসঙ্গে এটি সংযুক্ত রয়েছে তার থেকে কর্টিন নিজেই বিচ্ছিন্ন করা উচিত । আপনি এভাবে ন্যূনতম ওভারহেড সহ কেবল একটি কর্টিন তৈরি করেন:
GlobalScope.launch(Dispatchers.Unconfined) {
suspendCoroutine<Unit> {
continuations.add(it)
}
}
এই অভিব্যক্তিটির মান Job
হ'ল একটি স্থগিত কর্টিন। ধারাবাহিকতা ধরে রাখতে, আমরা এটিকে বিস্তৃত ক্ষেত্রের তালিকায় যুক্ত করেছি।
আমি এই কোডটি বেঞ্চমার্ক করেছি এবং উপসংহারে এসেছি যে এটি 140 বাইট বরাদ্দ করে এবং 100 টি ন্যানোসেকেন্ডগুলি সম্পূর্ণ করতে লাগে । সুতরাং এটি কোনও হালকা ওজনের একটি কর্টিন।
প্রজননযোগ্যতার জন্য, আমি এই কোডটি ব্যবহার করেছি:
fun measureMemoryOfLaunch() {
val continuations = ContinuationList()
val jobs = (1..10_000).mapTo(JobList()) {
GlobalScope.launch(Dispatchers.Unconfined) {
suspendCoroutine<Unit> {
continuations.add(it)
}
}
}
(1..500).forEach {
Thread.sleep(1000)
println(it)
}
println(jobs.onEach { it.cancel() }.filter { it.isActive})
}
class JobList : ArrayList<Job>()
class ContinuationList : ArrayList<Continuation<Unit>>()
এই কোডটি কর্টিনগুলির একটি গোছা শুরু করে এবং তারপরে ঘুমায় যাতে আপনার কাছে ভিজুয়ালভিএম এর মতো একটি মনিটরিং সরঞ্জাম সহ গাদা বিশ্লেষণ করার সময় হয়। আমি বিশেষায়িত ক্লাস তৈরি করেছি JobList
এবং ContinuationList
কারণ এটি হিপ ডাম্প বিশ্লেষণ করা সহজ করে তোলে।
আরো একটি সম্পূর্ণ গল্প পেতে, আমি নিচের কোড ব্যবহার করা এছাড়াও খরচ পরিমাপ withContext()
এবং async-await
:
import kotlinx.coroutines.*
import java.util.concurrent.Executors
import kotlin.coroutines.suspendCoroutine
import kotlin.system.measureTimeMillis
const val JOBS_PER_BATCH = 100_000
var blackHoleCount = 0
val threadPool = Executors.newSingleThreadExecutor()!!
val ThreadPool = threadPool.asCoroutineDispatcher()
fun main(args: Array<String>) {
try {
measure("just launch", justLaunch)
measure("launch and withContext", launchAndWithContext)
measure("launch and async", launchAndAsync)
println("Black hole value: $blackHoleCount")
} finally {
threadPool.shutdown()
}
}
fun measure(name: String, block: (Int) -> Job) {
print("Measuring $name, warmup ")
(1..1_000_000).forEach { block(it).cancel() }
println("done.")
System.gc()
System.gc()
val tookOnAverage = (1..20).map { _ ->
System.gc()
System.gc()
var jobs: List<Job> = emptyList()
measureTimeMillis {
jobs = (1..JOBS_PER_BATCH).map(block)
}.also { _ ->
blackHoleCount += jobs.onEach { it.cancel() }.count()
}
}.average()
println("$name took ${tookOnAverage * 1_000_000 / JOBS_PER_BATCH} nanoseconds")
}
fun measureMemory(name:String, block: (Int) -> Job) {
println(name)
val jobs = (1..JOBS_PER_BATCH).map(block)
(1..500).forEach {
Thread.sleep(1000)
println(it)
}
println(jobs.onEach { it.cancel() }.filter { it.isActive})
}
val justLaunch: (i: Int) -> Job = {
GlobalScope.launch(Dispatchers.Unconfined) {
suspendCoroutine<Unit> {}
}
}
val launchAndWithContext: (i: Int) -> Job = {
GlobalScope.launch(Dispatchers.Unconfined) {
withContext(ThreadPool) {
suspendCoroutine<Unit> {}
}
}
}
val launchAndAsync: (i: Int) -> Job = {
GlobalScope.launch(Dispatchers.Unconfined) {
async(ThreadPool) {
suspendCoroutine<Unit> {}
}.await()
}
}
এটি উপরের কোডটি থেকে পাওয়া টিপিক্যাল আউটপুট:
Just launch: 140 nanoseconds
launch and withContext : 520 nanoseconds
launch and async-await: 1100 nanoseconds
হ্যাঁ, async-await
প্রায় দ্বিগুণ সময় লাগে withContext
তবে এটি এখনও একটি মাইক্রোসেকেন্ড ond আপনার অ্যাপ্লিকেশনটিতে "সমস্যা" হওয়ার জন্য আপনাকে এগুলি একটি শক্ত লুপে চালু করতে হবে, ছাড়াও প্রায় কিছুই করতে হবে না।
ব্যবহার করে measureMemory()
আমি কলটিতে নিম্নলিখিত মেমরির দাম খুঁজে পেয়েছি:
Just launch: 88 bytes
withContext(): 512 bytes
async-await: 652 bytes
এর ব্যয়টি async-await
হ'ল 140 বাইটের চেয়ে বেশি withContext
, আমরা একটি কর্টিনের মেমরির ওজন হিসাবে পেয়েছি। এটি CommonPool
প্রসঙ্গ স্থাপনের সম্পূর্ণ ব্যয়ের একটি অংশ মাত্র ।
তাহলে কর্মক্ষমতা / মেমরি প্রভাব মধ্যে ফয়সালা করার একমাত্র নির্ণায়ক ছিল withContext
এবং async-await
, উপসংহার সেখানে বাস্তব ব্যবহারের ক্ষেত্রে 99% তাদের মধ্যে কোন প্রাসঙ্গিক পার্থক্য যে হতে হবে।
আসল কারণ হ'ল withContext()
একটি সহজ এবং আরও সরাসরি API, বিশেষত ব্যতিক্রম পরিচালনার ক্ষেত্রে:
- একটি ব্যতিক্রম যা এর মধ্যে পরিচালিত হয় না
async { ... }
তার পিতামাতার কাজ বাতিল হয়ে যায়। আপনি ম্যাচ থেকে ব্যতিক্রমগুলি কীভাবে পরিচালনা করবেন তা নির্বিশেষে এটি ঘটে await()
। আপনি যদি coroutineScope
এটির জন্য কোনও প্রস্তুতি না নিয়ে থাকেন তবে এটি আপনার সম্পূর্ণ অ্যাপ্লিকেশনটি নামিয়ে আনতে পারে।
withContext { ... }
কেবলমাত্র withContext
কলটি দিয়ে হ্যান্ডেল না করা ব্যতিক্রম , আপনি এটিকে অন্য যেকোন মত হ্যান্ডেল করেন।
withContext
আপনি প্যারেন্ট কাউটিন স্থগিত করে এবং সন্তানের জন্য অপেক্ষা করছেন এই সত্যটি কাজে লাগিয়ে অপ্টিমাইজড হওয়ার ঘটনা ঘটে but তবে এটি কেবল একটি যুক্ত বোনাস।
async-await
আপনি যেখানে প্রকৃত সম্মতি চান সে ক্ষেত্রে তাদের সংরক্ষণ করা উচিত, যাতে আপনি ব্যাকগ্রাউন্ডে বেশ কয়েকটি কর্টিন চালু করেন এবং কেবল তখনই তাদের জন্য অপেক্ষা করুন। সংক্ষেপে:
async-await-async-await
- না, ব্যবহার withContext-withContext
async-async-await-await
- এটি এটি ব্যবহার করার উপায়।
withContext
নির্বিশেষে একটি নতুন কর্টিন তৈরি হয়। এটি আমি সোর্স কোড থেকে দেখতে পাচ্ছি।