如何测量 Kotlin 协程中异步 query/request 的执行时间
How to measure execution time of an aync query/request inside Kotlin coroutines
我有一个微服务,我正在使用 Kotlin 协程异步执行一堆数据库查询,我想监控每个查询的执行时间以实现潜在的性能优化。
我的实现是这样的:
val requestSemaphore = Semaphore(5)
val baseProductsNos = productRepository.getAllBaseProductsNos()
runBlocking {
baseProductsNos
.chunked(500)
.map { batchOfProductNos ->
launch {
requestSemaphore.withPermit {
val rawBaseProducts = async {
productRepository.getBaseProducts(batchOfProductNos)
}
val mediaCall = async {
productRepository.getProductMedia(batchOfProductNos)
}
val productDimensions = async {
productRepository.getProductDimensions(batchOfProductNos)
}
val allowedCountries = async {
productRepository.getProductNosInCountries(batchOfProductNos, countriesList)
}
val variants = async {
productRepository.getProductVariants(batchOfProductNos)
}
// here I wait for all the results and then some processing on thm
}
}
}.joinAll()
}
如您所见,我使用 Semaphore 来限制并行作业的数量,并且所有存储库方法都是可暂停的,而这些正是我想要测量执行时间的方法。这是 ProductRepository 中的一个实现示例:
suspend fun getBaseProducts(baseProductNos: List<String>): List<RawBaseProduct> =
withContext(Dispatchers.IO) {
namedParameterJdbcTemplateMercator.query(
getSqlFromResource(baseProductSql),
getNamedParametersForBaseProductNos(baseProductNos),
RawBaseProductRowMapper()
)
}
为此,我尝试了这个:
val rawBaseProductsCall = async {
val startTime = System.currentTimeMillis()
val result = productRepository.getBaseProducts(productNos)
val endTime = System.currentTimeMillis()
logger.info("${TemporaryLog("call-duration", "rawBaseProductsCall", endTime - startTime)}")
result
}
但是这个测量总是 returns 与顺序实现(没有协程)相比,平均值的不一致结果,我能想到的唯一解释是这个测量包括暂停时间,显然我只对查询在没有暂停时间的情况下执行所花费的时间感兴趣。
我不知道我正在尝试做的事情在 Kotlin 中是否可行,但看起来 支持这一点。所以我将感谢任何帮助在 Kotlin 中做类似的事情。
更新:
在我的例子中,我使用常规的 java 库来查询数据库,所以我的数据库查询只是常规的阻塞调用,这意味着我现在测量时间的方式是正确的。
如果我使用 R2DBC 的某些实现来查询我的数据库,我在问题中所做的假设将是有效的。
我自己不会做 Kotlin,所以无法提供代码示例。
但理论上您知道何时提出请求,因此请记住请求旁边的变量中的时间戳(id、token、...)。一旦响应可用(无论你如何了解它)存储第二个时间戳,然后打印经过时间的差异。
我怀疑你会更接近那个。
我不知道这是有意还是失误,但你在这里只使用了一个线程。你启动了数十个甚至数百个协程,它们都在为这个单一线程相互争斗。如果您在“这里我等待所有结果,然后对 thm 进行一些处理”中执行任何 CPU 密集型处理,那么在它工作时,所有其他协程必须等待从 withContext(Dispatchers.IO)
恢复。如果您想使用多个线程,请将 runBlocking {}
替换为 runBlocking(Dispatchers.Default) {}
。
不过,它并没有解决问题,而是减轻了它的影响。关于正确的修复:如果您只需要测量在 IO 中花费的时间,那么...仅测量 IO 中的时间。只需将您的测量值移到 withContext(Dispatchers.IO)
内,我认为结果会更接近您的预期。否则,就像站在建筑物外面测量房间的大小一样。
您不想测量协程启动或挂起时间,因此您需要测量不会挂起的代码块,即..您的数据库调用来自 java 库
例如 stdlib 提供了一些不错的函数,例如 measureTimedValue
val (duration, result) = measureTimedValue {
doWork()
// eg: productRepository.getBaseProducts(batchOfProductNos)
}
logger.info("operation took $duration")
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/measure-timed-value.html
我有一个微服务,我正在使用 Kotlin 协程异步执行一堆数据库查询,我想监控每个查询的执行时间以实现潜在的性能优化。
我的实现是这样的:
val requestSemaphore = Semaphore(5)
val baseProductsNos = productRepository.getAllBaseProductsNos()
runBlocking {
baseProductsNos
.chunked(500)
.map { batchOfProductNos ->
launch {
requestSemaphore.withPermit {
val rawBaseProducts = async {
productRepository.getBaseProducts(batchOfProductNos)
}
val mediaCall = async {
productRepository.getProductMedia(batchOfProductNos)
}
val productDimensions = async {
productRepository.getProductDimensions(batchOfProductNos)
}
val allowedCountries = async {
productRepository.getProductNosInCountries(batchOfProductNos, countriesList)
}
val variants = async {
productRepository.getProductVariants(batchOfProductNos)
}
// here I wait for all the results and then some processing on thm
}
}
}.joinAll()
}
如您所见,我使用 Semaphore 来限制并行作业的数量,并且所有存储库方法都是可暂停的,而这些正是我想要测量执行时间的方法。这是 ProductRepository 中的一个实现示例:
suspend fun getBaseProducts(baseProductNos: List<String>): List<RawBaseProduct> =
withContext(Dispatchers.IO) {
namedParameterJdbcTemplateMercator.query(
getSqlFromResource(baseProductSql),
getNamedParametersForBaseProductNos(baseProductNos),
RawBaseProductRowMapper()
)
}
为此,我尝试了这个:
val rawBaseProductsCall = async {
val startTime = System.currentTimeMillis()
val result = productRepository.getBaseProducts(productNos)
val endTime = System.currentTimeMillis()
logger.info("${TemporaryLog("call-duration", "rawBaseProductsCall", endTime - startTime)}")
result
}
但是这个测量总是 returns 与顺序实现(没有协程)相比,平均值的不一致结果,我能想到的唯一解释是这个测量包括暂停时间,显然我只对查询在没有暂停时间的情况下执行所花费的时间感兴趣。
我不知道我正在尝试做的事情在 Kotlin 中是否可行,但看起来
更新:
在我的例子中,我使用常规的 java 库来查询数据库,所以我的数据库查询只是常规的阻塞调用,这意味着我现在测量时间的方式是正确的。
如果我使用 R2DBC 的某些实现来查询我的数据库,我在问题中所做的假设将是有效的。
我自己不会做 Kotlin,所以无法提供代码示例。
但理论上您知道何时提出请求,因此请记住请求旁边的变量中的时间戳(id、token、...)。一旦响应可用(无论你如何了解它)存储第二个时间戳,然后打印经过时间的差异。
我怀疑你会更接近那个。
我不知道这是有意还是失误,但你在这里只使用了一个线程。你启动了数十个甚至数百个协程,它们都在为这个单一线程相互争斗。如果您在“这里我等待所有结果,然后对 thm 进行一些处理”中执行任何 CPU 密集型处理,那么在它工作时,所有其他协程必须等待从 withContext(Dispatchers.IO)
恢复。如果您想使用多个线程,请将 runBlocking {}
替换为 runBlocking(Dispatchers.Default) {}
。
不过,它并没有解决问题,而是减轻了它的影响。关于正确的修复:如果您只需要测量在 IO 中花费的时间,那么...仅测量 IO 中的时间。只需将您的测量值移到 withContext(Dispatchers.IO)
内,我认为结果会更接近您的预期。否则,就像站在建筑物外面测量房间的大小一样。
您不想测量协程启动或挂起时间,因此您需要测量不会挂起的代码块,即..您的数据库调用来自 java 库
例如stdlib 提供了一些不错的函数,例如 measureTimedValue
val (duration, result) = measureTimedValue {
doWork()
// eg: productRepository.getBaseProducts(batchOfProductNos)
}
logger.info("operation took $duration")
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/measure-timed-value.html