[Ktor - Android - performances] Hi, we are migrati...
# ktor
a
[Ktor - Android - performances] Hi, we are migrating our REST handling from Retrofit + kotlinx.serizalization JSON to Ktor + kotlinx.serizalization JSON in order to move network layer to KMP and reuse it on iOS. While testing, we noticed significant performance degradation when we executed 10-20 REST calls in parallel (response body size ~2MB each): Ktor + kotlinx.serizalization JSON • Triggering much more Garbage collections with a bunch of following logs in Logcat: ◦ Waiting for a blocking GC Alloc ◦ WaitForGcToComplete blocked Alloc on Background for 868.208ms • Heap spike up to 500MB Retrofit + kotlinx.serizalization JSON • Heap spike up to 250MB • Not triggering GC as mentioned above What could be the reason for this? Is it possible that Ktor + kotlinx.serizalization JSON is creating much more short-lived objects and consumes more memory in order to parse large JSON responses?
a
Can you please share a code snippet used to measure heap usage and GC behavior on Android with Ktor and kotlinx.serialization framework?
a
We are observing heap usage and GC behaviour from Logcat; we don't have code that measures it. This is how we are using Ktor + kotlinx.serialization EventRepositoryImpl.kt
Copy code
EventRepositoryImpl(
   private val eventRemoteSource: EventRemoteSource,
   ...
): EventRepository {

    override suspend fun getEvent(eventId: String): Event? {
        val apiEvent: ApiEvent = eventRemoteSource.getEvent(eventId: String)
     
        return mapEvent(
            apiEvent = apiEvent,
            struct = structRepository.getStruct().firstOrNull(),
        )
    }   
    ...
}
EventRemoteSourceImpl.kt
Copy code
internal class EventRemoteSourceImpl(
    private val eventRestRemoteSource: EventRestRemoteSource,
    ...
) : EventRemoteSource {

    override suspend fun getEvent(eventId: String): ApiEvent? =
            eventRestRemoteSource.getByEventId(query.eventId)
                .successOrElse { ApiOfferV2RestData(data = null) }
                .data
                ?.firstOrNull()
    }
}
EventRestRemoteSource.kt
Copy code
internal class EventRestRemoteSource(
    httpClient: HttpClient,
    kmpAppConfig: KmpAppConfig,
    ...
) : BaseRestRemoteSource(
    httpClient = httpClient,
    defaultRequestHeaders = { RequestHeaders(userAgent = kmpAppConfig.userAgent) }
) {

    suspend fun getByEventId(eventId: String): ApiResponseResult<ApiOfferV2RestData<List<ApiEvent>>> = execute {
            val response = get(
                urlString = getConfiguration<OfferApiConfiguration>()
                    .getEventByIdRestUrl(
                        eventId = eventId,
                        includeResultedOdds = true,
                    )
            )
            decodeApiResponse(response)
    }
    ...
}
ApiResponseResult.kt
Copy code
suspend inline fun <reified T : Any> decodeApiResponse(
    response: HttpResponse,
): ApiResponseResult<T> {
    return try {
        val responseData = response.body<T>()
        ApiResponseResult.Success(responseData)
    } catch (e: CancellationException) {
        throw e
    } catch (e: Exception) {
        Logger.e("decodeApiResponse", e)
        val errorResponse = handleApiError(response)
        ApiResponseResult.Failure(ApiFailure.ApiParsingError(errorResponse))
    }
}
When we replace EventRestRemoteSource with another one which is using Retrofit, we don't see this: • Triggering much more Garbage collections with a bunch of following logs in Logcat: ◦ Waiting for a blocking GC Alloc ◦ WaitForGcToComplete blocked Alloc on Background for 868.208ms • Heap spike up to 500MB (visible in logcat as well after GC clears the memory)
a
Is it possible for you to profile the application and share the HPROF dump?
h
i’ve also observed that kotlinx allocates more objects than moshi but not by this significant amount, my numbers were somewhere aligned with this https://github.com/Kotlin/kotlinx.serialization/issues/2657