تهیه پیش‌فروش دوره «آموزش تکمیلی و پروژه محور Spring Boot» با ۳۵% تخفیف - فقط تا ۱۳ شهریور 

محاسبه‌ی زمان اجرا در کاتلین

یکشنبه ۲۰ مرداد ۹۸ توسط سالار ساری نوایی

در کتابخانه‌ی استاندارد کاتلین، یک تابع کاربردی ایده‌آل برای اندازه‌گیری زمان اجرای یک بلوک یا تابع قرار داده شده است. کد نوشته شده در این تابع بسیار ساده و آسان است اما با این حال از کارایی بالایی نیز برخوردار است، چرا که از آلوده شدن کد توسط فراخوانی‌های متعدد به System.currentTimeMillis() جلوگیری می‌کند.

/**
 * Executes the given [block] and returns elapsed time in milliseconds.
 */
public inline fun measureTimeMillis(block: () -> Unit): Long {
    val start = System.currentTimeMillis()
    block()
    return System.currentTimeMillis() - start
}

تابع measureTimeMillis را به روش‌های زیر می‌توان فراخوانی کرد:

// 1. Use with a block of code
val executionTime = measureTimeMillis {
   // block of code to be measured
}

// 2. Use with a function reference
fun doSth() {
   // body of function to be measured
}

val executionTime = measureTimeMillis(::doSth)

// 3. Use with a function literal
val functionLiteral: () -> Unit {
   // body of function to be measured
}

val executionTime = measureTimeMillis(functionLiteral)

اما اگر کمی دقیق‌تر به measureTimeMillis توجه کنیم، می‌بینیم که این دستور تنها توابعی از نوع () -> Unit را می‌پذیرد. پس تنها کاربرد این تابع در توابعی است که هیچ چیزی را برنمی‌گردانند.

با این شرایط اگر بخواهیم زمان اجرای تابعی مانند تابع زیر را محاسبه کنیم، چه باید کرد؟

fun readAndDecodeFile(): File

بگذارید بررسی کنیم که آیا می‌توانیم measureTimeMillis را به گونه‌ای گسترش بدهیم که با کمک توابع سطح بالاتر کاتلین به هدفی که مد نظرمان است برسیم یا خیر.

تابع استاندارد measureTimeMillis یک مقدار Long را برمی‌گرداند که نشان دهنده‌ی زمان اجرای محاسبه شده است. تابعی که قصد اندازه‌گیری زمان اجرای آن را داریم نیز یک مقدار برمی‌گرداند. پس فرقی ندارد که چطور فراخوانی تابع را درون یک نسخه‌ی اورلود شده از measureTimeMillis قرار بدهیم. در هر صورت باید راهی پیدا کنیم که دو مقدار را برگردانیم.

یکی از روش‌های ممکن این است که یک جفت (Pair) را برگردانیم. اما جفت‌ها و به طور کل تاپل‌ها ساختارهایی جنجالی در زمینه‌ی برنامه‌نویسی شی‌ءگرا هستند. به این دلیل که این موارد هیچ نوع رابطه‌ی معنایی برای مقادیر جفت شده تعریف نمی‌کنند.

اگر قصد ما برگرداندن یک جفت باشد، کد تابع حاصل به این صورت خواهد بود:

inline fun<T> measureTimeMillisPair(function: () -> T): Pair<T, Long> {
    val startTime = System.currentTimeMillis()
    val result: T = function.invoke()
    val endTime = System.currentTimeMillis()

    return Pair(result, endTime - startTime)
}

فارغ از این که نظر شخصی شما پیرامون این مسئله چیست، کاملا قابل مشاهده است که جفت کردن مقدار برگردانده شده‌ی یک تابع با مدت زمان اجرای تابع کار اشتباهی است. این دو مقدار اهداف مختلفی دارند و ذخیره کردن آن‌ها در کنار یکدگیر به نوعی غیرطبیعی است.

همچنین می‌توان به این موضوع اشاره کرد که رویکرد بالا دز محل فراخوانی کمی آزاردهنده است، چرا که کد

val file: File = readAndDecodeFile()

را به

val pair: Pair<File, Long> = measureTimeMillisPair(::readAndDecodeFile)
val file = pair.first
Log.d(TAG, "Read and decode took ${pair.last}ms")

تبدیل می‌کند.

خب چه راه‌های دیگری پیش رو داریم؟ در این جا همان نکته‌ای که به آن اشاره کردیم (متفاوت بودن کاربرد دو مقدار) می‌تواند به ما کمک کند که به یک راهکار بهتر برسیم.

بدیهی است که محاسبه کردن زمان اجرای یک تابع تنها پس از اجرای آن تابع ممکن است و لازم است که با همین منطق به آن توجه کنیم. معمولا هدف ما از به دست آوردن زمان اجرا این است که نتیجه را لاگ کنیم یا آن را در یک فریمورک آماری ذخیره کنیم.

به همین دلیل می‌توانیم فرآیند محاسبه زمان اجرا را به صورت داخلی انجام بدهیم و تنها نتیجه‌ی واقعی تابع محاسبه شده را برگردانیم. البته این فرآیند را نمی‌توان از پیش تعریف کرد تا تابع محاسبه کننده‌ی ما بتواند یک لامبدا را قبول کند. در این صورت می‌توانیم مشخص کنیم که نتیجه‌ی اندازه‌گیری به چه شکلی پردازش شود.

تابع پیشنهادی به این صورت خواهد بود:

inline fun <T> measureTimeMillis(loggingFunction: (Long) -> Unit,
                                 function: () -> T): T {

    val startTime = System.currentTimeMillis()
    val result: T = function.invoke()
    loggingFunction.invoke(System.currentTimeMillis() - startTime)

    return result
}

طریقه‌ی استفاده از تابع نیز به شکل زیر است:

val file: File =
       measureTimeMillis({ time -> Log.d(TAG, "Read and decode took $time") }) {
                readAndDecodeFile()
       }

نتیجه‌ی حاصل شده از این روش قابل قبول است و فرآیند اندازه‌گیری نیز اذیت‌کننده نیست. کد نهایی تغییر چندانی نکرده است و می‌توان آن را به صورت موقت غیرفعال کرد (با کامنت کردن خطوط 2 و 4). توابع سطح بالا نیز به ما کمک می‌کنند تا منطق اصلی تابع در حال اجرا را از کد مربوط به محاسبه‌ی زمان اجرا جدا نگه داریم.


کلیدواژه: اندروید Android کاتلین Kotlin runtime زمان اجرا

منابع: proandroiddev.com

ارسال دیدگاه:
برای ارسال دیگاه باید به سیستم وارد شوید و یا ثبت نام کنید. ثبت نام چند لحظه بیشتر زمان شما را نمیگیرد.