Kotlin: شرح تحسين أداء كوتلن
آخر تحديث: 2026-08-26
الأداء ليس تحسينًا مبكرًا — إنه اتخاذ الخيارات الصحيحة: يستبدل Charlie سلاسل List بـ Sequence لتجنب تخصيص مجموعات وسيطة لملايين الطلبات، ويستخدم IntArray بدلاً من Array<Int> لإزالة التعبئة، ويتحقق من كل تحسين بـ JMH.
1. ما ستتعلمه
- الدوال
inline: إزالة تخصيص كائنات لامدا - اختيار المجموعات:
ListمقابلSequenceمقابلFlowمقابلArray - ضبط جدول الكوروتينات
- مصفوفات الأنواع الأولية:
IntArrayمقابلArray<Int> - تطبيق Charlie: تحسين إنتاجية مليون طلب
2. قصة مهندس معمارية حقيقي
(1) المشكلة: نفاد الذاكرة عند معالجة مليون طلب
أنشأ OrderProcessor الخاص بـ Charlie مجموعة جديدة في كل خطوة عند معالجة مليون طلب — بلغت ذروة الذاكرة 4 جيجابايت، ووصلت توقفات GC إلى ثانيتين.
(2) حل تحسين الأداء
KOTLIN
// قبل: 3 مجموعات وسيطة، ذروة ذاكرة 4 جيجابايت
orders.map { enrich(it) }.filter { it.total > 100 }.groupBy { it.region }
// بعد: 0 مجموعات وسيطة، ذروة ذاكرة 200 ميجابايت
orders.asSequence()
.map { enrich(it) }
.filter { it.total > 100 }
.groupBy { it.region } // مجموعة نهائية واحدة فقط
التقييم الكسول لـ Sequence + مصفوفات الأنواع الأولية + ضبط تجمع الكوروتينات = انخفاض الذاكرة بنسبة 95%، تحسين الإنتاجية 10 أضعاف.
3. الدوال المضمّنة تزيل العبء
(1) التكلفة الخفية للامدا
KOTLIN
// غير مضمّنة: تُترجم اللامدا إلى فئة مجهولة
fun process(order: Order, block: (Order) -> String): String {
return block(order) // ينشئ كائن Function1 في كل استدعاء
}
// مضمّنة: كود اللامدا يُدمج في موقع الاستدعاء
inline fun process(order: Order, block: (Order) -> String): String {
return block(order) // بدون تخصيص كائن!
}
(2) قياس تأثير inline
KOTLIN
// قياس دقيق بـ JMH (مفاهيمي)
// غير مضمّنة: ~50 نانوثانية لكل استدعاء (تخصيص كائن)
// مضمّنة: ~5 نانوثانية لكل استدعاء (بدون تخصيص، JIT يدمج كذلك)
(3) إرشادات استخدام inline
| السيناريو | التوصية | السبب |
|---|---|---|
| دالة رتبة أعلى (نص 1-5 أسطر) | ✅ inline | إزالة تخصيص كائن لامدا |
| معامل نوع reified | ✅ يجب inline | الحفاظ على النوع وقت الترجمة |
| نص دالة كبير (>20 سطر) | ❌ لا تُضمّن | زيادة حجم الكود |
| لامدا مُخزّنة/مُمرّرة | ❌ لا تُضمّن | اللامدا المضمّنة لم تعد موجودة |
4. اختيار المجموعات
(1) أربع مناهج لمعالجة البيانات
KOTLIN
// 1. List (فوري): كل خطوة تنشئ مجموعة جديدة
val result1 = orders
.map { enrich(it) } // List جديدة
.filter { it.total > 100 } // List جديدة
.toList() // List جديدة
// 2. Sequence (كسول): تعالج عنصرًا واحدًا في كل مرة
val result2 = orders.asSequence()
.map { enrich(it) }
.filter { it.total > 100 }
.toList() // List واحدة فقط
// 3. Flow (كسول غير متزامن): تدفق كسول يدعم suspend
val result3 = orders.asFlow()
.map { enrichAsync(it) } // يمكن أن يكون suspend
.filter { it.total > 100 }
.toList() // List واحدة فقط
// 4. Array: أقل عبء، حجم ثابت
val result4 = ordersArray
.map { enrich(it) } // تنشئ Array جديدة
.filter { it.total > 100 } // لا يوجد filter على Array
(2) شجرة قرار اختيار المجموعات
flowchart LR
A[معالجة بيانات] --> B{يحتاج غير متزامن؟}
B -->|نعم| C[Flow]
B -->|لا| D{حجم البيانات؟}
D -->|كبير >10K| E{خطوات متعددة؟}
D -->|صغير <10K| F[List]
E -->|نعم| G[Sequence]
E -->|لا| F
A --> H{أنواع أولية؟}
H -->|نعم| I[IntArray/DoubleArray]
H -->|لا| A
(3) مقارنة الأداء
| المنهج | الذاكرة | المعالج | الأفضل لـ |
|---|---|---|---|
List |
O(n×خطوات) | منخفض (صديق للتخزين المؤقت) | بيانات صغيرة، خطوات قليلة |
Sequence |
O(1) | متوسط (خط أنابيب كامل لكل عنصر) | بيانات كبيرة، خطوات كثيرة |
Flow |
O(1) | متوسط + عبء غير متزامن | مصادر بيانات غير متزامنة |
Array |
O(n) | الأدنى (بدون تعبئة) | أنواع أولية، حجم ثابت |
5. مصفوفات الأنواع الأولية
(1) IntArray مقابل Array<Int>
KOTLIN
// Array<Int>: كل عنصر هو كائن Integer مُعبَّأ
val boxed: Array<Int> = Array(1_000_000) { it }
// الذاكرة: ~24 ميجابايت (4 ميجابايت بيانات + 20 ميجابايت رؤوس كائنات)
// IntArray: مصفوفة int[] أولية، بدون تعبئة
val primitive: IntArray = IntArray(1_000_000) { it }
// الذاكرة: ~4 ميجابايت (بدون عبء كائنات)
// توفير 6 أضعاف الذاكرة مع المصفوفات الأولية!
(2) اختيار المصفوفة الأولية
| النوع | مصفوفة مُعبَّأة | مصفوفة أولية | التوفير |
|---|---|---|---|
| Int | Array<Int> |
IntArray |
~6 أضعاف |
| Long | Array<Long> |
LongArray |
~6 أضعاف |
| Double | Array<Double> |
DoubleArray |
~6 أضعاف |
| Boolean | Array<Boolean> |
BooleanArray |
~8 أضعاف |
6. ضبط جدول الكوروتينات
(1) اختيار المُجدوِل
KOTLIN
// Default: مكثف المعالج (التوازي = أنوية المعالج)
launch(Dispatchers.Default) { sortLargeCollection() }
// IO: إدخال/إخراج حظرية (حتى 64 خيط افتراضيًا)
launch(Dispatchers.IO) { queryDatabase() }
// مخصص: لأنماط إدخال/إخراج محددة
val orderIoDispatcher = Executors.newFixedThreadPool(32)
.asCoroutineDispatcher()
// زيادة حجم تجمع IO
System.setProperty("kotlinx.coroutines.io.parallelism", "128")
(2) مقارنة ضبط المُجدوِلات
| السيناريو | الافتراضي | بعد الضبط | التحسين |
|---|---|---|---|
| 64 استعلام قاعدة بيانات متزامن | IO افتراضي 64 خيط | مخصص 128 خيط | مضاعفة الإنتاجية |
| حساب مكثف للمعالج | افتراضي 4 أنوية | ثابت 8 خيوط | مضاعفة الإنتاجية |
| حمل عمل مختلط | IO مشترك | تجمعات خيوط معزولة | تقليل زمن الاستجابة 50% |
7. قياس JMH
(1) تهيئة JMH
KOTLIN
// build.gradle.kts
dependencies {
implementation("org.openjdk.jmh:jmh-core:1.37")
implementation("org.openjdk.jmh:jmh-generator-annprocess:1.37")
}
(2) مثال قياس
KOTLIN
@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
open class OrderProcessingBenchmark {
private lateinit var orders: List<Order>
@Setup
fun setup() {
orders = (1..100_000).map {
Order("ORD-$it", it * 10.0, "CONFIRMED", "Customer-$it")
}
}
@Benchmark
fun listPipeline(): Map<String, List<Order>> {
return orders
.filter { it.total > 1_000 }
.map { it.copy(total = it.total * 0.9) }
.groupBy { it.customer }
}
@Benchmark
fun sequencePipeline(): Map<String, List<Order>> {
return orders.asSequence()
.filter { it.total > 1_000 }
.map { it.copy(total = it.total * 0.9) }
.groupBy { it.customer }
}
}
8. مثال كامل: تحسين إنتاجية مليون طلب
KOTLIN
// ============================================
// OrderProcessor - تحسين الأداء
// الميزة: Sequence، IntArray، الكوروتينات، القياس
// ============================================
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
data class Order(val id: String, val total: Double, val status: String, val customer: String)
fun generateOrders(count: Int): List<Order> = (1..count).map {
Order("ORD-$it", it * 0.1, if (it % 5 == 0) "CANCELLED" else "CONFIRMED", "CUST-${it % 100}")
}
// استراتيجية 1: List (فوري)
fun processWithList(orders: List<Order>): Double {
return orders
.filter { it.status != "CANCELLED" }
.map { it.total }
.sum()
}
// استراتيجية 2: Sequence (كسول)
fun processWithSequence(orders: List<Order>): Double {
return orders.asSequence()
.filter { it.status != "CANCELLED" }
.map { it.total }
.sum()
}
// استراتيجية 3: IntArray (أولي، بدون تعبئة)
fun processWithIntArray(totals: DoubleArray): Double {
return totals.sum()
}
// استراتيجية 4: الكوروتينات (متوازية)
suspend fun processWithCoroutines(orders: List<Order>, chunkSize: Int = 10_000): Double {
return coroutineScope {
orders.chunked(chunkSize)
.map { chunk -> async(Dispatchers.Default) { chunk.filter { it.status != "CANCELLED" }.sumOf { it.total } } }
.awaitAll()
.sum()
}
}
fun main() = runBlocking {
val orderCount = 1_000_000
println("توليد $orderCount طلب...")
val orders = generateOrders(orderCount)
val totalsArray = DoubleArray(orderCount) { orders[it].total }
println("\n=== قياس الأداء ===\n")
// List
val listTime = measureTimeMillis { val r = processWithList(orders); println("نتيجة List: \$$r USD") }
println("وقت List: ${listTime}ms\n")
// Sequence
val seqTime = measureTimeMillis { val r = processWithSequence(orders); println("نتيجة Sequence: \$$r USD") }
println("وقت Sequence: ${seqTime}ms\n")
// IntArray
val arrayTime = measureTimeMillis { val r = processWithIntArray(totalsArray); println("نتيجة DoubleArray: \$$r USD") }
println("وقت DoubleArray: ${arrayTime}ms\n")
// الكوروتينات
val coroTime = measureTimeMillis { val r = processWithCoroutines(orders); println("نتيجة Coroutine: \$$r USD") }
println("وقت Coroutine: ${coroTime}ms\n")
// ملخص
println("=== ملخص التحسين ===")
println(" تسريع List مقابل Sequence: ${"%.1f".format(listTime.toDouble() / seqTime)}x")
println(" تسريع List مقابل DoubleArray: ${"%.1f".format(listTime.toDouble() / arrayTime)}x")
println(" تسريع List مقابل Coroutine: ${"%.1f".format(listTime.toDouble() / coroTime)}x")
}
الإخراج (عينة؛ النتائج الفعلية تعتمد على الأجهزة):
TEXT
📖 للعرض فقط
توليد 1000000 طلب...
=== قياس الأداء ===
نتيجة List: $4.99995E7 USD
وقت List: 120ms
نتيجة Sequence: $4.99995E7 USD
وقت Sequence: 45ms
نتيجة DoubleArray: $5.0E7 USD
وقت DoubleArray: 3ms
نتيجة Coroutine: $4.99995E7 USD
وقت Coroutine: 35ms
=== ملخص التحسين ===
تسريع List مقابل Sequence: 2.7x
تسريع List مقابل DoubleArray: 40.0x
تسريع List مقابل Coroutine: 3.4x
9. أمثلة عملية سريعة
▶ مثال: اختيار بنية البيانات
KOTLIN
import kotlin.system.measureTimeMillis
fun main() {
val n = 10_000_000
// IntArray - الأسرع للأنواع البدائية
val intArr = IntArray(n) { it }
val intArrSum = measureTimeMillis {
var sum = 0L
for (v in intArr) sum += v
}
println("IntArray sum: ${intArrSum}ms")
// List<Int> - أبطأ بسبب boxing
val intList = (0 until n).toList()
val listSum = measureTimeMillis {
var sum = 0L
for (v in intList) sum += v
}
println("List<Int> sum: ${listSum}ms")
// map vs for
val data = (1..1_000_000).toList()
val mapTime = measureTimeMillis {
val result = data.map { it * 2 }.filter { it > 1000 }.sum()
}
val forTime = measureTimeMillis {
var sum = 0
for (v in data) {
val doubled = v * 2
if (doubled > 1000) sum += doubled
}
}
println("map+filter+sum: ${mapTime}ms, for loop: ${forTime}ms")
// HashMap vs TreeMap lookup
val map = (1..100_000).associateWith { "value-$it" }
val hashLookup = measureTimeMillis {
repeat(100_000) { map[it + 1] }
}
println("HashMap lookups: ${hashLookup}ms")
}
**الإخراج:
TEXT
📖 للعرض فقط
IntArray sum: ~3ms
List<Int> sum: ~15ms
map+filter+sum: ~25ms, for loop: ~10ms
HashMap lookups: ~5ms
▶ مثال: تجنب تخصيص الكائنات
KOTLIN
import kotlin.system.measureTimeMillis
class Order(val id: String, val total: Double)
// ❌ سيء: ينشئ closure جديد في كل تكرار
fun processBad(orders: List<Order>): Double {
var sum = 0.0
for (order in orders) {
// lambda ينشئ كائن جديد في كل تكرار
listOf(order.total).forEach { sum += it }
}
return sum
}
// ✅ جيد: حلقة مباشرة بدون lambda
fun processGood(orders: List<Order>): Double {
var sum = 0.0
for (order in orders) {
sum += order.total
}
return sum
}
// ✅ أفضل: استخدام fold (دالة inline)
fun processBest(orders: List<Order>): Double =
orders.fold(0.0) { acc, order -> acc + order.total }
fun main() {
val orders = List(1_000_000) { Order("ORD-$it", (it + 1) * 1.0) }
val badTime = measureTimeMillis { processBad(orders) }
val goodTime = measureTimeMillis { processGood(orders) }
val bestTime = measureTimeMillis { processBest(orders) }
println("Lambda allocation: ${badTime}ms")
println("Direct loop: ${goodTime}ms")
println("Fold (inline): ${bestTime}ms")
}
**الإخراج:
TEXT
📖 للعرض فقط
Lambda allocation: ~150ms
Direct loop: ~30ms
Fold (inline): ~25ms
▶ مثال: String concatenation
KOTLIN
import kotlin.system.measureTimeMillis
fun main() {
val n = 50_000
// ❌ سيء: String concatenation في حلقة
val concatTime = measureTimeMillis {
var s = ""
for (i in 1..n) s += "$i,"
}
println("String concat: ${concatTime}ms")
// ✅ جيد: StringBuilder
val builderTime = measureTimeMillis {
val sb = StringBuilder()
for (i in 1..n) {
sb.append(i)
sb.append(",")
}
sb.toString()
}
println("StringBuilder: ${builderTime}ms")
// ✅ أفضل: joinToString
val joinTime = measureTimeMillis {
(1..n).joinToString(",")
}
println("joinToString: ${joinTime}ms")
// ✅ ممتاز: buildString (inline lambda)
val buildTime = measureTimeMillis {
buildString {
for (i in 1..n) {
append(i)
append(",")
}
}
}
println("buildString: ${buildTime}ms")
}
**الإخراج:
TEXT
📖 للعرض فقط
String concat: ~500ms (O(n²))
StringBuilder: ~10ms (O(n))
joinToString: ~8ms
buildString: ~8ms
▶ مثال: lazy initialization
KOTLIN
import kotlin.system.measureTimeMillis
class ExpensiveResourceEager {
val data: String = loadExpensiveData() // يُحمّل فورًا
private fun loadExpensiveData(): String {
Thread.sleep(500) // محاكاة تكلفة
return "Data loaded"
}
}
class ExpensiveResourceLazy {
val data: String by lazy { loadExpensiveData() } // يُحمّل عند الحاجة فقط
private fun loadExpensiveData(): String {
Thread.sleep(500)
return "Data loaded"
}
}
fun main() {
// eager: 500ms فورًا
val eagerTime = measureTimeMillis { ExpensiveResourceEager() }
// lazy: ~0ms (لم نصل للبيانات)
val lazyTime = measureTimeMillis { ExpensiveResourceLazy() }
println("Eager init: ${eagerTime}ms")
println("Lazy init (no access): ${lazyTime}ms")
// الوصول إلى lazy يُحمّل
val resource = ExpensiveResourceLazy()
val accessTime = measureTimeMillis { resource.data }
println("Lazy first access: ${accessTime}ms")
}
**الإخراج:
TEXT
📖 للعرض فقط
Eager init: ~500ms
Lazy init (no access): ~0ms
Lazy first access: ~500ms
▶ مثال: caching نتائج مكلفة
KOTLIN
import kotlin.system.measureTimeMillis
class ExpensiveCalculation {
// memoization - يخزن النتائج
private val cache = mutableMapOf<Int, Long>()
fun fibonacci(n: Int): Long {
if (n <= 1) return n.toLong()
// ذاكرة التخزين المؤقت
cache[n]?.let { return it }
val result = fibonacci(n - 1) + fibonacci(n - 2)
cache[n] = result
return result
}
}
class ExpensiveCalculationNoCache {
fun fibonacci(n: Int): Long {
if (n <= 1) return n.toLong()
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
fun main() {
val calc = ExpensiveCalculation()
val calcNoCache = ExpensiveCalculationNoCache()
// بدون تخزين - بطيء جدًا لـ n=35
val noCacheTime = measureTimeMillis { calcNoCache.fibonacci(35) }
println("No cache fib(35): ${noCacheTime}ms")
// مع تخزين - أول مرة بطيء، ثم سريع
val firstTime = measureTimeMillis { calc.fibonacci(35) }
println("Cached fib(35) first: ${firstTime}ms")
val secondTime = measureTimeMillis { calc.fibonacci(35) }
println("Cached fib(35) second: ${secondTime}ms")
}
**الإخراج:
TEXT
📖 للعرض فقط
No cache fib(35): ~500ms
Cached fib(35) first: ~1ms (مع cache)
Cached fib(35) second: ~0ms
▶ مثال: استخدام companion object للذاكرة المشتركة
KOTLIN
class OrderProcessor {
// ❌ سيء: كل instance له نسخته الخاصة
private val regex = Regex("^[A-Z]{3}-\\d{5}$")
fun validate(id: String): Boolean = regex.matches(id)
}
// ✅ جيد: companion object - ذاكرة مشتركة
class OrderProcessorV2 {
companion object {
// تُنشأ مرة واحدة فقط لكل Class
private val ID_REGEX = Regex("^[A-Z]{3}-\\d{5}$")
private val VALID_ORDERS = mutableSetOf<String>()
}
fun validate(id: String): Boolean = ID_REGEX.matches(id)
}
// الوصول للذاكرة المشتركة
fun main() {
// كل instance له regex خاص به
val processors = List(100) { OrderProcessor() }
// OrderProcessorV2: regex واحد فقط للكل
// companion object كـ singleton
println("First ID regex: ${OrderProcessorV2.validate("ORD-001")}")
println("Invalid: ${OrderProcessorV2.validate("invalid")}")
}
**الإخراج:
TEXT
📖 للعرض فقط
First ID regex: true
Invalid: false
▶ مثال: تحسين حلقات بـ indexes
KOTLIN
import kotlin.system.measureTimeMillis
fun sumListDirect(list: List<Int>): Long {
var sum = 0L
for (i in list.indices) {
sum += list[i]
}
return sum
}
fun sumListForEach(list: List<Int>): Long {
var sum = 0L
for (v in list) {
sum += v
}
return sum
}
fun sumListIndexed(list: List<Int>): Long {
var sum = 0L
list.forEachIndexed { _, v -> sum += v }
return sum
}
fun main() {
val data = (1..1_000_000).toList()
val direct = measureTimeMillis { sumListDirect(data) }
val forEach = measureTimeMillis { sumListForEach(data) }
val indexed = measureTimeMillis { sumListIndexed(data) }
println("for-i with indices: ${direct}ms")
println("for-v (forEach-like): ${forEach}ms")
println("forEachIndexed with lambda: ${indexed}ms")
}
**الإخراج:
TEXT
📖 للعرض فقط
for-i with indices: ~5ms
for-v (forEach-like): ~6ms
forEachIndexed with lambda: ~80ms (with allocation)
❓ أسئلة شائعة
س متى يجب تحسين الأداء؟
ج اكتب كودًا صحيحًا أولاً، قِس اختناقات الأداء، ثم حسّن حيث يهم. التحسين المبكر جذر كل الشرور — لكن اختيار بنية البيانات الصحيحة (Sequence مقابل List) ليس تحسينًا مبكرًا؛ إنه قرار تصميم.
س هل يزيد inline حجم APK/IPA؟
ج نعم. كل استدعاء مضمّن ينسخ الكود إلى موقع الاستدعاء. الدوال الصغيرة كثيرة الاستدعاء تستفيد أكثر من التضمين. تضمين الدوال الكبيرة يسبب انتفاخًا. المكتبة القياسية توازن هذا بالفعل بشكل جيد.
س هل Sequence دائمًا أسرع من List؟
ج ليس دائمًا. للبيانات الصغيرة (<1000) والعمليات أحادية الخطوة، قد تكون List أسرع (صديقة للتخزين المؤقت، بدون عبء غلاف Sequence). Sequence تتألق مع 3+ خطوات عمليات على مجموعات بيانات كبيرة.
س أيهما أسرع، IntArray أم
List<Int>؟ج IntArray يتجنب التعبئة وتخصيص الكائنات — الاجتياز والجمع أسرع 5-10 أضعاف. لكن IntArray لا يدعم العوامل الوظيفية (map/filter) — ستحتاج حلقات يدوية أو تحويلات.
س هل الكوروتينات دائمًا أسرع من الخيوط؟
ج لا. للمهام المكثفة للمعالج، الكوروتينات والخيوط تتأدى بشكل مشابه. الكوروتينات تتفوق في سيناريوهات إدخال/إخراج — التعامل مع إدخال/إخراج متزامن هائل بخيوط قليلة، متجنبة عبء حظر الخيوط.
س كيف أقيس أداء كوتلن بشكل صحيح؟
ج استخدم JMH (Java Microbenchmark Harness). لا تستخدم
measureTimeMillis للقياسات الدقيقة — ترجمة JIT و GC وتحميل الفئات تشوّه النتائج جميعًا. JMH يتولى كل ذلك تلقائيًا.📖 ملخص
inlineيزيل تخصيص كائنات لامدا، لكن تضمين الدوال الكبيرة يزيد حجم الكود- التقييم الكسول لـ
Sequenceيتجنب المجموعات الوسيطة؛ مُفضّل للبيانات الكبيرة + عمليات متعددة الخطوات IntArray/DoubleArrayتتجنب التعبئة؛ الحسابات العددية أسرع 5-10 أضعاف- ضبط جدول الكوروتينات: استخدم
Dispatchers.IOلإدخال/إخراج حظرية،Dispatchers.Defaultلمهام مكثفة المعالج - JMH هو الأداة القياسية للقياس الدقيق؛ تجنب القياس اليدوي
- خطوات التحسين: صحيح ← قِس ← حسّن ← قِس مجددًا
📝 تمارين
- مبتدئ (⭐): استخدم
measureTimeMillisلمقارنة وقت تنفيذListمقابلSequenceعلى 100,000 عنصر. تلميح:asSequence() - متوسط (⭐⭐): استخدم
DoubleArrayلحساب إجمالي قيم الطلبات وقارن الأداء معList<Double>. تلميح:DoubleArray(size) { ... } - متقدم (⭐⭐⭐): اكتب قياس JMH رسميًا يقارن إنتاجية List/Sequence/Flow على مليون طلب. تلميح:
@Benchmark+@Setup