You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何并行调试Kotlin纯函数的两组参数执行过程?

并行对比纯函数不同参数执行过程的实现方案

针对你不想修改原函数源码、需要并行对比同一纯函数不同参数执行过程的需求,以下是几种可行的非侵入式实现方案:

方案一:AspectJ编译时织入(推荐)

通过AspectJ的切面能力,在编译阶段自动给目标函数的每一行插入日志逻辑,无需改动原函数代码,最终收集两个线程的执行数据并按行对比。

1. 配置Gradle依赖

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.9.20'
    id 'io.freefair.aspectj.post-compile-weaving' version '8.6'
}

dependencies {
    implementation 'org.aspectj:aspectjrt:1.9.20'
}

2. 编写切面类

import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.After
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Pointcut
import java.util.concurrent.ConcurrentHashMap

@Aspect
class FunctionTracer {
    // 定义切点:匹配目标test1函数的所有执行行
    @Pointcut("execution(* test1(..)) && within(你的包名.*)")
    fun test1Execution() {}

    // 存储每个线程的行号与变量值映射
    private val threadData = ConcurrentHashMap<Thread, MutableMap<Int, String>>()

    @After("test1Execution() && cflow(execution(* test1(..)))")
    fun logLineExecution(joinPoint: JoinPoint) {
        val thread = Thread.currentThread()
        val lineNum = joinPoint.sourceLocation.line
        val stackFrame = joinPoint.staticPart.asInstanceOf<org.aspectj.lang.reflect.CodeSignature>().let { sig ->
            // 通过AspectJ的代码签名获取局部变量表,读取res的值
            val localVarIndex = sig.parameterNames.indexOf("res") + sig.parameterCount
            val localVarValue = joinPoint.args.getOrNull(localVarIndex)
            "res=$localVarValue"
        }
        val lineMap = threadData.computeIfAbsent(thread) { mutableMapOf() }
        lineMap[lineNum] = stackFrame
    }

    // 对外提供获取线程执行数据的方法
    fun getThreadData(thread: Thread): Map<Int, String> = threadData[thread] ?: emptyMap()
}

3. 并行执行并对比输出

fun main() {
    val tracer = FunctionTracer()
    val thread1 = Thread { test1(1) }
    val thread2 = Thread { test1(0) }

    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()

    // 收集两个线程的执行数据
    val data1 = tracer.getThreadData(thread1)
    val data2 = tracer.getThreadData(thread2)

    // 按行号排序后对比输出
    val allLines = (data1.keys + data2.keys).sorted()
    allLines.forEach { line ->
        val val1 = data1[line] ?: "无数据"
        val val2 = data2[line] ?: "无数据"
        println("line$line: thread1: $val1 vs thread2: $val2")
    }
}

注意:如果局部变量res的位置无法通过参数索引获取,可结合ASM工具解析字节码的局部变量表,精准定位变量位置。

方案二:JDI自定义调试器

利用Java Debug Interface(JDI)编写调试客户端,在目标函数的每一行设置断点,实时捕获两个线程的变量值并对比输出。

1. 编写JDI调试代码

import com.sun.jdi.*
import com.sun.jdi.connect.AttachingConnector
import com.sun.jdi.event.BreakpointEvent
import com.sun.jdi.request.BreakpointRequest
import java.util.concurrent.CountDownLatch

fun main() {
    // 连接到以调试模式启动的目标JVM
    val vmManager = Bootstrap.virtualMachineManager()
    val connector = vmManager.attachingConnectors().find { it.name() == "com.sun.jdi.SocketAttach" } as AttachingConnector
    val args = connector.defaultArguments().apply {
        get("hostname").setValue("localhost")
        get("port").setValue("5005")
    }
    val vm = connector.attach(args)

    // 获取目标类和函数
    val targetClass = vm.classesByName("你的包名.你的类名").first()
    val test1Method = targetClass.methodsByName("test1").first()

    // 在函数的每一行设置断点
    val breakpoints = mutableListOf<BreakpointRequest>()
    test1Method.allLineLocations().forEach { location ->
        val bpReq = vm.eventRequestManager().createBreakpointRequest(location)
        bpReq.setSuspendPolicy(BreakpointRequest.SUSPEND_EVENT_THREAD)
        bpReq.enable()
        breakpoints.add(bpReq)
    }

    // 存储线程执行数据,用CountDownLatch同步同一行的两个线程
    val threadData = mutableMapOf<ThreadReference, MutableMap<Int, String>>()
    val lineLatchMap = mutableMapOf<Int, CountDownLatch>()

    // 处理调试事件
    while (true) {
        val eventSet = vm.eventQueue().remove()
        eventSet.forEach { event ->
            if (event is BreakpointEvent) {
                val thread = event.thread()
                val lineNum = event.location().lineNumber()
                // 获取当前栈帧的res变量值
                val stackFrame = thread.frame(0)
                val resVar = stackFrame.visibleVariables().find { it.name() == "res" } as LocalVariable
                val resValue = stackFrame.getValue(resVar).value()

                // 记录当前线程的行数据
                val lineMap = threadData.computeIfAbsent(thread) { mutableMapOf() }
                lineMap[lineNum] = "res=$resValue"

                // 同步两个线程的同一行输出
                val latch = lineLatchMap.computeIfAbsent(lineNum) { CountDownLatch(2) }
                latch.countDown()
                if (latch.count == 0L) {
                    val val1 = threadData.values.first()[lineNum]!!
                    val val2 = threadData.values.last()[lineNum]!!
                    println("line$lineNum: thread1: $val1 vs thread2: $val2")
                    lineLatchMap.remove(lineNum)
                }
                eventSet.resume()
            }
        }
    }
}

2. 启动目标程序

目标程序需以调试模式启动:

kotlin -J-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005 你的类名Kt

方案三:ByteBuddy字节码修改

通过ByteBuddy在运行时动态修改目标函数的字节码,插入日志逻辑,同样无需改动原源码:

import net.bytebuddy.ByteBuddy
import net.bytebuddy.agent.ByteBuddyAgent
import net.bytebuddy.asm.Advice
import net.bytebuddy.matcher.ElementMatchers.named
import java.util.concurrent.ConcurrentHashMap

val threadData = ConcurrentHashMap<Thread, MutableMap<Int, String>>()

fun main() {
    ByteBuddyAgent.install()
    ByteBuddy()
        .redefine(你的类名::class.java)
        .visit(Advice.to(Test1Advice::class.java).on(named("test1")))
        .make()
        .load(你的类名::class.java.classLoader)

    // 并行执行并对比输出(同方案一的main逻辑)
}

class Test1Advice {
    @Advice.OnMethodEnter
    fun enter(@Advice.Origin line: String) {
        val lineNum = line.substringAfterLast(':').toInt()
        // 初始化行记录
        threadData.computeIfAbsent(Thread.currentThread()) { mutableMapOf() }[lineNum] = ""
    }

    @Advice.OnMethodExit(onThrowable = Throwable::class)
    fun exit(@Advice.Local("res") res: Int, @Advice.Origin line: String) {
        val lineNum = line.substringAfterLast(':').toInt()
        threadData[Thread.currentThread()]!![lineNum] = "res=$res"
    }
}

内容的提问来源于stack exchange,提问作者A-_-S

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:02:59