[debugger] try to avoid arrays creation in batch evaluator

GitOrigin-RevId: 5f3a994c078248831e344005bffd8e4bbee8548c
This commit is contained in:
Egor Ushakov
2024-11-12 19:36:35 +00:00
committed by intellij-monorepo-bot
parent 0e7ce4cda3
commit 442ec9c204
2 changed files with 95 additions and 81 deletions
@@ -5,7 +5,7 @@ import com.intellij.debugger.JavaDebuggerBundle;
import com.intellij.debugger.engine.*;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.evaluation.EvaluationContext;
import com.intellij.debugger.engine.jdi.ThreadReferenceProxy;
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.impl.DebuggerUtilsImpl;
import com.intellij.openapi.diagnostic.Logger;
@@ -13,7 +13,6 @@ import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.rt.debugger.BatchEvaluatorServer;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.jdi.MethodImpl;
import com.sun.jdi.*;
import org.jetbrains.annotations.NotNull;
@@ -27,9 +26,6 @@ public final class BatchEvaluator {
private static final Logger LOG = Logger.getInstance(BatchEvaluator.class);
private final DebugProcess myDebugProcess;
private boolean myBatchEvaluatorChecked;
private ClassType myBatchEvaluatorClass;
private Method myBatchEvaluatorMethod;
private static final Key<BatchEvaluator> BATCH_EVALUATOR_KEY = new Key<>("BatchEvaluator");
public static final Key<Boolean> REMOTE_SESSION_KEY = new Key<>("is_remote_session_key");
@@ -38,60 +34,18 @@ public final class BatchEvaluator {
private BatchEvaluator(DebugProcess process) {
myDebugProcess = process;
myDebugProcess.addDebugProcessListener(new DebugProcessListener() {
@Override
public void processDetached(@NotNull DebugProcess process, boolean closedByUser) {
myBatchEvaluatorChecked = false;
myBatchEvaluatorClass = null;
myBatchEvaluatorMethod = null;
}
});
}
public boolean hasBatchEvaluator(EvaluationContext evaluationContext) {
if (!myBatchEvaluatorChecked) {
myBatchEvaluatorChecked = true;
if (DebuggerUtilsImpl.isRemote(myDebugProcess)) {
// optimization: for remote sessions the BatchEvaluator is not there for sure
return false;
}
ThreadReferenceProxy thread = evaluationContext.getSuspendContext().getThread();
if (thread == null) {
return false;
}
ThreadReference threadReference = thread.getThreadReference();
if (threadReference == null) {
return false;
}
try {
myBatchEvaluatorClass = (ClassType)myDebugProcess.findClass(evaluationContext, BatchEvaluatorServer.class.getName(),
evaluationContext.getClassLoader());
}
catch (EvaluateException ignored) {
}
if (myBatchEvaluatorClass != null) {
myBatchEvaluatorMethod = DebuggerUtils.findMethod(myBatchEvaluatorClass, "evaluate", "([Ljava/lang/Object;)Ljava/lang/String;");
}
}
return myBatchEvaluatorMethod != null;
}
public void invoke(ToStringCommand command) {
DebuggerManagerThreadImpl.assertIsManagerThread();
final EvaluationContext evaluationContext = command.getEvaluationContext();
final SuspendContext suspendContext = evaluationContext.getSuspendContext();
if (!Registry.is("debugger.batch.evaluation.force") &&
(!Registry.is("debugger.batch.evaluation") || !hasBatchEvaluator(evaluationContext))) {
if (!Registry.is("debugger.batch.evaluation.force") && !Registry.is("debugger.batch.evaluation")) {
myDebugProcess.getManagerThread().invokeCommand(command);
}
else {
EvaluationContext evaluationContext = command.getEvaluationContext();
SuspendContext suspendContext = evaluationContext.getSuspendContext();
List<ToStringCommand> toStringCommands = myBuffer.get(suspendContext);
if (toStringCommands == null) {
toStringCommands = new ArrayList<>();
@@ -113,33 +67,38 @@ public final class BatchEvaluator {
return batchEvaluator;
}
private boolean doEvaluateBatch(List<ToStringCommand> requests, EvaluationContext evaluationContext) {
private static boolean doEvaluateBatch(List<ToStringCommand> requests, EvaluationContext evaluationContext) {
try {
if (!hasBatchEvaluator(evaluationContext)) {
return false;
}
DebugProcess debugProcess = evaluationContext.getDebugProcess();
List<Value> values = ContainerUtil.map(requests, ToStringCommand::getValue);
ArrayType objectArrayClass = (ArrayType)debugProcess.findClass(
evaluationContext,
"java.lang.Object[]",
evaluationContext.getClassLoader());
if (objectArrayClass == null) {
return false;
String helperMethodName;
ArrayReference argArray = null;
List<Value> args;
if (values.size() > 10) {
ArrayType objectArrayClass = (ArrayType)debugProcess.findClass(
evaluationContext,
"java.lang.Object[]",
evaluationContext.getClassLoader());
argArray = DebuggerUtilsEx.mirrorOfArray(objectArrayClass, values, evaluationContext);
args = Collections.singletonList(argArray);
helperMethodName = "evaluate";
}
else {
args = values;
helperMethodName = "evaluate" + values.size();
}
// reserve one extra element for the return value to avoid gc collection of the result
ArrayReference argArray =
DebuggerUtilsEx.mirrorOfArray(objectArrayClass, ContainerUtil.append(values, (Value)null), evaluationContext);
Value result = ((DebugProcessImpl)debugProcess).invokeMethod(
evaluationContext, myBatchEvaluatorClass, myBatchEvaluatorMethod, Collections.singletonList(argArray),
MethodImpl.SKIP_ASSIGNABLE_CHECK, true);
if (result instanceof StringReference stringReference) {
byte[] bytes = stringReference.value().getBytes(StandardCharsets.ISO_8859_1);
String value = DebuggerUtils.getInstance().processCollectibleValue(
() -> DebuggerUtilsImpl.invokeHelperMethod((EvaluationContextImpl)evaluationContext, BatchEvaluatorServer.class, helperMethodName, args, false),
result -> result instanceof StringReference ? ((StringReference)result).value() : null,
evaluationContext);
if (argArray != null) {
DebuggerUtilsEx.enableCollection(argArray);
}
if (value != null) {
byte[] bytes = value.getBytes(StandardCharsets.ISO_8859_1);
try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes))) {
int count = 0;
while (dis.available() > 0) {
@@ -165,7 +124,7 @@ public final class BatchEvaluator {
return true;
}
}
catch (EvaluateException e) {
catch (ObjectCollectedException | EvaluateException e) {
LOG.error(e);
}
return false;
@@ -8,17 +8,15 @@ package com.intellij.rt.debugger;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
@SuppressWarnings("unused")
public final class BatchEvaluatorServer {
/**
* Serialize result in one String to avoid multiple getValue commands from the resulting array
* Serialize the result in one String to avoid multiple getValue commands from the resulting array
*/
public static String evaluate(Object[] args) throws IOException {
// the last element is always null, it is reserved for the return value (to avoid gc collection)
Object[] objects = Arrays.copyOf(args, args.length - 1);
public static String evaluate(Object[] objects) throws IOException {
ByteArrayOutputStream bas = new ByteArrayOutputStream();
//noinspection IOResourceOpenedButNotSafelyClosed
DataOutputStream dos = new DataOutputStream(bas);
for (Object object : objects) {
String res;
@@ -37,8 +35,65 @@ public final class BatchEvaluatorServer {
dos.writeBoolean(error);
dos.writeUTF(res);
}
String result = bas.toString("ISO-8859-1");
args[args.length - 1] = result; // store the result as the last array element to avoid it being collected
return result;
return bas.toString("ISO-8859-1");
}
public static String evaluate1(Object arg1) throws IOException {
return evaluate(new Object[]{arg1});
}
public static String evaluate2(Object arg1, Object arg2) throws IOException {
return evaluate(new Object[]{arg1, arg2});
}
public static String evaluate3(Object arg1, Object arg2, Object arg3) throws IOException {
return evaluate(new Object[]{arg1, arg2, arg3});
}
public static String evaluate4(Object arg1, Object arg2, Object arg3, Object arg4) throws IOException {
return evaluate(new Object[]{arg1, arg2, arg3, arg4});
}
public static String evaluate5(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) throws IOException {
return evaluate(new Object[]{arg1, arg2, arg3, arg4, arg5});
}
public static String evaluate6(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) throws IOException {
return evaluate(new Object[]{arg1, arg2, arg3, arg4, arg5, arg6});
}
public static String evaluate7(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7)
throws IOException {
return evaluate(new Object[]{arg1, arg2, arg3, arg4, arg5, arg6, arg7});
}
public static String evaluate8(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object arg8)
throws IOException {
return evaluate(new Object[]{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8});
}
public static String evaluate9(Object arg1,
Object arg2,
Object arg3,
Object arg4,
Object arg5,
Object arg6,
Object arg7,
Object arg8,
Object arg9) throws IOException {
return evaluate(new Object[]{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9});
}
public static String evaluate10(Object arg1,
Object arg2,
Object arg3,
Object arg4,
Object arg5,
Object arg6,
Object arg7,
Object arg8,
Object arg9,
Object arg10) throws IOException {
return evaluate(new Object[]{arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10});
}
}