From 9839657e205739f66fc6c11f45c6237d830d23ef Mon Sep 17 00:00:00 2001 From: Dmitry Trofimov Date: Mon, 11 Apr 2011 20:50:04 +0400 Subject: [PATCH] Optimized deferred output processing memory consumption(removed excessive new String() creation). --- .../execution/impl/ConsoleBuffer.java | 88 +++++++++---------- .../execution/impl/ConsoleViewImpl.java | 65 ++++++++++---- 2 files changed, 92 insertions(+), 61 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleBuffer.java b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleBuffer.java index 8d0b691364a1..d49efffeb8b8 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleBuffer.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleBuffer.java @@ -39,7 +39,7 @@ import static com.intellij.execution.impl.ConsoleViewImpl.TokenInfo; *

* Note: basically this class consists of functionality that is cut from {@link ConsoleViewImpl} in order to make it possible * to cover it by tests. - * + * * @author Denis Zhdanov * @since 4/5/11 5:26 PM */ @@ -54,9 +54,9 @@ public class ConsoleBuffer { *

* Feel free to check rationale for using this approach at {@link #myCyclicBufferSize} contract. */ - private final Deque myDeferredOutput = new ArrayDeque(); + private final Deque myDeferredOutput = new ArrayDeque(); private final Set myContentTypesToNotStripOnCycling = new HashSet(); - + /** * Main console usage scenario assumes the following: *

@@ -85,8 +85,8 @@ public class ConsoleBuffer {
    * to be moved. Current constant defines default size of that small buffers.
    */
 
-  private final int     myCyclicBufferSize;
-  private final int     myCyclicBufferUnitSize;
+  private final int myCyclicBufferSize;
+  private final int myCyclicBufferUnitSize;
   private final boolean myUseCyclicBuffer;
 
   /**
@@ -94,7 +94,9 @@ public class ConsoleBuffer {
    */
   private int myDeferredOutputLength;
 
-  /** Buffer for deferred stdin and stderr output. */
+  /**
+   * Buffer for deferred stdin and stderr output.
+   */
   private StringBuffer myDeferredUserInput = new StringBuffer();
 
   /**
@@ -120,7 +122,7 @@ public class ConsoleBuffer {
     final String useCycleBufferProperty = System.getProperty("idea.cycle.buffer.size");
     return useCycleBufferProperty == null || !"disabled".equalsIgnoreCase(useCycleBufferProperty);
   }
-  
+
   private static int getCycleBufferSize() {
     final String cycleBufferSizeProperty = System.getProperty("idea.cycle.buffer.size");
     if (cycleBufferSizeProperty == null) return 1024 * 1024;
@@ -143,7 +145,7 @@ public class ConsoleBuffer {
   public boolean isEmpty() {
     return myDeferredOutput.isEmpty() || (myDeferredOutput.size() == 1 && myDeferredOutput.getFirst().length() <= 0);
   }
-  
+
   public int getLength() {
     return myDeferredOutputLength;
   }
@@ -168,19 +170,16 @@ public class ConsoleBuffer {
     return myDeferredOutput;
   }
 
-  public String getText() {
-    if (myDeferredOutput.size() > 1) {
+  public StringBuilder[] getText() {
+    if (myDeferredOutput.size() > 0) {
       final StringBuilder buffer = new StringBuilder();
       for (StringBuilder builder : myDeferredOutput) {
         buffer.append(builder);
       }
-      return buffer.toString();
-    }
-    else if (myDeferredOutput.size() == 1) {
-      return myDeferredOutput.getFirst().substring(0);
+      return myDeferredOutput.toArray(new StringBuilder[myDeferredOutput.size()]);
     }
     else {
-      return "";
+      return new StringBuilder[0];
     }
   }
 
@@ -190,14 +189,14 @@ public class ConsoleBuffer {
    * 

* {@link ConsoleViewContentType#USER_INPUT} is considered to be such a type by default, however, it's possible to overwrite that * via the current method. - * - * @param types content types that should not be stripped during the buffer's cycling + * + * @param types content types that should not be stripped during the buffer's cycling */ public void setContentTypesToNotStripOnCycling(@NotNull Collection types) { myContentTypesToNotStripOnCycling.clear(); myContentTypesToNotStripOnCycling.addAll(types); } - + public void clear() { if (myUseCyclicBuffer) { myDeferredOutput.clear(); @@ -213,7 +212,7 @@ public class ConsoleBuffer { myDeferredUserInput = new StringBuffer(); myDeferredTokens.clear(); } - + @Nullable public String cutFirstUserInputLine() { final String text = myDeferredUserInput.substring(0, myDeferredUserInput.length()); @@ -238,16 +237,16 @@ public class ConsoleBuffer { public void replaceUserText(int startOffset, int endOffset, String text) { myDeferredUserInput.replace(startOffset, endOffset, text); } - + /** * Asks current buffer to store given text of the given type. - * - * @param s text to store - * @param contentType type of the given text - * @param info hyperlink info for the given text (if any) - * @return text that is actually stored (there is a possible case that the buffer is full and given text's type - * is considered to have lower priority than the stored one, hence, it's better to drop given text completely - * or partially) and number of existed symbols removed during storing the given data + * + * @param s text to store + * @param contentType type of the given text + * @param info hyperlink info for the given text (if any) + * @return text that is actually stored (there is a possible case that the buffer is full and given text's type + * is considered to have lower priority than the stored one, hence, it's better to drop given text completely + * or partially) and number of existed symbols removed during storing the given data */ @NotNull public Pair print(@NotNull String s, @NotNull ConsoleViewContentType contentType, @Nullable HyperlinkInfo info) { @@ -272,7 +271,7 @@ public class ConsoleBuffer { myDeferredTypes.add(contentType); s = StringUtil.convertLineSeparators(s, !SystemInfo.isMac); - + myDeferredOutputLength += s.length(); StringBuilder bufferToUse; if (myDeferredOutput.isEmpty()) { @@ -286,7 +285,7 @@ public class ConsoleBuffer { if (bufferToUse.length() >= myCyclicBufferUnitSize) { myDeferredOutput.add(bufferToUse = new StringBuilder(myCyclicBufferUnitSize)); } - + if (bufferToUse.length() < myCyclicBufferUnitSize) { int numberOfSymbolsToAdd = Math.min(myCyclicBufferUnitSize - bufferToUse.length(), s.length() - offset); bufferToUse.append(s.substring(offset, offset + numberOfSymbolsToAdd)); @@ -297,7 +296,7 @@ public class ConsoleBuffer { if (contentType == ConsoleViewContentType.USER_INPUT) { myDeferredUserInput.append(s); } - + ConsoleUtil.addToken(s.length(), info, contentType, myDeferredTokens); return new Pair(s, trimmedSymbolsNumber); } @@ -360,7 +359,7 @@ public class ConsoleBuffer { // } // } //} - + /** * IJ console works as follows - it receives managed process outputs from dedicated thread that serves that process and * pushes it to the {@link Document document} of editor used to represent process console. Important point here is that process @@ -371,8 +370,8 @@ public class ConsoleBuffer { * to push to the document then. Current method serves exactly that purpose, i.e. it's expected to be called when new chunk of * text is received from the underlying process and trims existing text buffer if necessary. * - * @param numberOfNewSymbols number of symbols read from the managed process output - * @return number of newly read symbols that should be accepted + * @param numberOfNewSymbols number of symbols read from the managed process output + * @return number of newly read symbols that should be accepted */ @SuppressWarnings({"ForLoopReplaceableByForEach"}) private int trimDeferredOutputIfNecessary(final int numberOfNewSymbols) { @@ -392,13 +391,13 @@ public class ConsoleBuffer { } Context context = new Context(numberOfSymbolsToRemove); - + TIntArrayList indicesOfTokensToRemove = new TIntArrayList(); for (int i = 0; i < myDeferredTokens.size(); i++) { TokenInfo tokenInfo = myDeferredTokens.get(i); tokenInfo.startOffset -= context.removedSymbolsNumber; tokenInfo.endOffset -= context.removedSymbolsNumber; - + if (!context.canContinueProcessing()) { // Just update token offsets. myDeferredTypes.add(tokenInfo.contentType); @@ -435,8 +434,9 @@ public class ConsoleBuffer { TokenInfo tokenInfo = myDeferredTokens.get(0); if (tokenInfo.startOffset > 0) { final HyperlinkInfo hyperlinkInfo = tokenInfo.getHyperlinkInfo(); - myDeferredTokens.add(0, hyperlinkInfo != null ? new HyperlinkTokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset, hyperlinkInfo) - : new TokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset)); + myDeferredTokens + .add(0, hyperlinkInfo != null ? new HyperlinkTokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset, hyperlinkInfo) + : new TokenInfo(ConsoleViewContentType.USER_INPUT, 0, tokenInfo.startOffset)); myDeferredTypes.add(ConsoleViewContentType.USER_INPUT); } } @@ -482,7 +482,7 @@ public class ConsoleBuffer { } } } - + private int remove(@NotNull Context context, int tokenLength) { int removedSymbolsNumber = 0; int remainingTotalNumberOfSymbolsToRemove = context.numberOfSymbolsToRemove - context.removedSymbolsNumber; @@ -518,7 +518,7 @@ public class ConsoleBuffer { } return removedSymbolsNumber; } - + private final class Context { public final int numberOfSymbolsToRemove; @@ -537,11 +537,11 @@ public class ConsoleBuffer { currentBuffer = null; } } - + public boolean canContinueProcessing() { return removedSymbolsNumber < numberOfSymbolsToRemove && currentBuffer != null; } - + public boolean nextBuffer() { if (iterator.hasNext()) { currentBuffer = iterator.next(); @@ -551,9 +551,9 @@ public class ConsoleBuffer { return false; } } - + @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"}) - private void dumpDeferredOutput() { + private void dumpDeferredOutput() { if (!DEBUG_PROCESSING) { return; } @@ -567,7 +567,7 @@ public class ConsoleBuffer { } log("-----------------------------------------------------------------------------------------------------"); } - + @SuppressWarnings({"UnusedDeclaration", "CallToPrintStackTrace"}) private static void log(Object o) { //try { diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java index c410e9239c92..2b6345156e85 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.java @@ -16,6 +16,7 @@ package com.intellij.execution.impl; +import com.google.common.collect.Lists; import com.intellij.codeInsight.navigation.IncrementalSearchHandler; import com.intellij.execution.ConsoleFolding; import com.intellij.execution.ExecutionBundle; @@ -69,7 +70,6 @@ import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.text.LineTokenizer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.Navigatable; import com.intellij.psi.PsiDocumentManager; @@ -102,7 +102,7 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArraySet; public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableConsoleView, DataProvider, OccurenceNavigator { - + private @NonNls String CONSOLE_VIEW_POPUP_MENU = "ConsoleView.PopupMenu"; private static final Logger LOG = Logger.getInstance("#com.intellij.execution.impl.ConsoleViewImpl"); @@ -475,7 +475,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo private void printHyperlink(String s, ConsoleViewContentType contentType, HyperlinkInfo info) { synchronized (LOCK) { - Pair pair = myBuffer.print(s, contentType, info); + Pair pair = myBuffer.print(s, contentType, info); s = pair.first; myContentSize += s.length() - pair.second; @@ -492,7 +492,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo } protected void beforeExternalAddContentToDocument(int length, ConsoleViewContentType contentType) { - myContentSize+=length; + myContentSize += length; addToken(length, null, contentType); } @@ -545,8 +545,8 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo }, null, DocCommandGroupId.noneGroupId(document)); } - - final String text; + + final StringBuilder[] text; final Collection contentTypes; int deferredTokensSize; synchronized (LOCK) { @@ -555,7 +555,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo if (myEditor == null) return; text = myBuffer.getText(); - + contentTypes = Collections.unmodifiableCollection(new HashSet(myBuffer.getDeferredTokenTypes())); List deferredTokens = myBuffer.getDeferredTokens(); for (TokenInfo deferredToken : deferredTokens) { @@ -567,7 +567,14 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo final Document document = myEditor.getDocument(); final int oldLineCount = document.getLineCount(); final boolean isAtEndOfDocument = myEditor.getCaretModel().getOffset() == document.getTextLength(); - boolean cycleUsed = myBuffer.isUseCyclicBuffer() && document.getTextLength() + text.length() > myBuffer.getCyclicBufferSize(); + int textLength = 0; + + final List textLines = splitToLines(text); + for (String line : textLines) { + textLength += line.length(); + } + + boolean cycleUsed = myBuffer.isUseCyclicBuffer() && document.getTextLength() + textLength > myBuffer.getCyclicBufferSize(); CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { public void run() { int offset = myEditor.getCaretModel().getOffset(); @@ -576,15 +583,15 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo myEditor.getScrollingModel().accumulateViewportChanges(); } try { - String[] strings = text.split("\\r"); - for (int i = 0; i < strings.length - 1; i++) { - document.insertString(document.getTextLength(), strings[i]); + + for (int i = 0; i < textLines.size() - 1; i++) { + document.insertString(document.getTextLength(), textLines.get(i)); int lastLine = document.getLineCount() - 1; if (lastLine >= 0) { document.deleteString(document.getLineStartOffset(lastLine), document.getTextLength()); } } - document.insertString(document.getTextLength(), strings[strings.length - 1]); + document.insertString(document.getTextLength(), textLines.get(textLines.size() - 1)); } finally { if (preserveCurrentVisualArea) { @@ -599,7 +606,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo } }, null, DocCommandGroupId.noneGroupId(document)); synchronized (LOCK) { - for (int i = myTokens.size() - 1; i >=0 && deferredTokensSize > 0; i--, deferredTokensSize--) { + for (int i = myTokens.size() - 1; i >= 0 && deferredTokensSize > 0; i--, deferredTokensSize--) { TokenInfo token = myTokens.get(i); final HyperlinkInfo info = token.getHyperlinkInfo(); if (info != null) { @@ -610,7 +617,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo myPsiDisposedCheck.performCheck(); final int newLineCount = document.getLineCount(); if (cycleUsed) { - final int lineCount = LineTokenizer.calcLineCount(text, true); + final int lineCount = textLines.size(); for (Iterator it = myHyperlinks.getRanges().keySet().iterator(); it.hasNext();) { if (!it.next().isValid()) { it.remove(); @@ -627,6 +634,30 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo } } + private List splitToLines(StringBuilder[] text) { + final List textLines = Lists.newArrayList(); + StringBuilder line = new StringBuilder(); + for (StringBuilder textItem : text) { + for (int j = 0; j < textItem.length(); j++) { + if (textItem.charAt(j) == '\r') { + textLines.add(new String(line)); + line.setLength(0); + } + else { + line.append(textItem.charAt(j)); + } + } + } + if (line.length() > 0) { + textLines.add(line.toString()); + } + + if (textLines.size() == 0) { + textLines.add(""); + } + return textLines; + } + private void flushDeferredUserInput() { final String textToSend = myBuffer.cutFirstUserInputLine(); if (textToSend == null) { @@ -773,7 +804,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo @Override public Color getDefaultBackground() { final Color color = getColor(ConsoleViewContentType.CONSOLE_BACKGROUND_KEY); - return color == null? super.getDefaultBackground() : color; + return color == null ? super.getDefaultBackground() : color; } }; editor.setColorsScheme(scheme); @@ -1510,7 +1541,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo scrollTo(next.getStartOffset()); } final HyperlinkInfo hyperlinkInfo = myHyperlinks.getRanges().get(next); - return hyperlinkInfo == null ? null : new OccurenceInfo(new Navigatable.Adapter() { + return hyperlinkInfo == null ? null : new OccurenceInfo(new Navigatable.Adapter() { public void navigate(final boolean requestFocus) { hyperlinkInfo.navigate(myProject); linkFollowed(hyperlinkInfo); @@ -1551,7 +1582,7 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo * used soft wraps mode and perform update if we see that the current value differs from the stored. */ private boolean myLastIsSelected; - + @Override protected Editor getEditor(AnActionEvent e) { return myEditor;