8

我最近注意到,自从我更新到 Java 7 后,我的代码的特定部分运行速度明显变慢。令人惊讶的是,Java 7 在全球范围内都比 Java 6 快。

该程序非常大,但我成功提取了可重现的代码来演示 java 7 如何比 Java 6 慢。

如果您使用 Java 6 和 Java 7 运行完全相同的代码,您会发现Java 7 比 Java 6 慢大约 7-8 倍!!!

这是代码(不要费心去理解它的作用,它只是真实代码的抽象,因此没有任何意义)。它基本上是一个解析字符串以格式化它的某些部分的代码:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
public class CPerfJava7 extends JFrame {
    private static final long serialVersionUID = 1L;

    // +==============================================================+
    // | Attributes                                                   |
    // +==============================================================+
    JPanel mainPanel;

    public CPseudoHtmlDocument docu;
    private static long totalTime = 0;

    // +==============================================================+
    // | Constructors                                                 |
    // +==============================================================+

    public CPerfJava7() {
        super("PerfJava7");
        docu = new CPseudoHtmlDocument();
        mainPanel = new JPanel(new BorderLayout());
        JTextPane textPane = new JTextPane();
        textPane.setDocument(docu);
        textPane.setText("testcase one\ndo program breakfast meal\ndo program time\ndo program portion size - 1/4 of the cup\nIt was easy to set up the time and portion size as directed in the manual\n\ndo collect kibbles from the bowl and measure the breakfast portion\ncheck the portion dispensed 1/4 cup of kibbles \nActual result - it was less than 1/4 cup. The wheel that rotates and dispenses\n the kibbles might haven't rotated completely.\n\ntestcase two\ndo program lunch meal\ndo program time\ndo leave the portion size the same - 1/4 of the cup\n\ncheck the portion dispensed 1/4 cup of kibbles\n do collect kibbles from the bowl and measure the lunch portion\nActual result was 1/20 of the cup, the wheel dispenser hardly released any kibbles\n\ntestcase three\n\ndo program dinner meal\ndo program time\ndo leave the portion size the same - 1/4 of the cup\n\ncheck the portion dispensed 1/4 cup of kibbles\n do collect kibbles from the bowl and measure the dinner portion\nActual result was  less than 1/4 cup\n\nWhy was the middle meal the smallest? Is lunch always the smallest?\nTo prove this hypothesis, repeat first three test cases again.\n\ntestcase four\ndo repeat test case one\nActual result - it was less 1/4 and the portion was dispensed in two increments.\n\ntestcase five\ndo repeat test case two\nActual result - 1/8 of the cup.\n\ntestcase six\ndo repeat test case three\nActual result - 1/2 cup! The wheel rotated almost twice. It looked like all\n kibbles which were not dispensed previously, got released.\n Lunch was still the smallest meal, but the dinner portion was twice of the size\n.All three tests must be repeated again.\n\ntestcase seven\ndo repeat test case one\nActual result - 1/4 of the cup. Great!\n\ntestcase eight\ndo repeat test case two\nActual result - 1/4 of the cup! It works perfectly! Why?\n\ntestcase nine\ndo repeat test case three\nActual result - 1/6, so the third meal was less than it should be. Is this size of the portion hard to dispense correctly? Let's try 1/2 of the cup\n\ntestcase ten\ndo program breakfast meal\ndo program time\ndo program 1/2 of the cup\ncheck the amount of kibbles dispensed is 1/2 of the cup\nActual result - 1/2 of the cup. Excellent. The wheel rotated twice as designed. In a minute it added 1/4 of the cup.\n\ntestcase eleven\ndo program lunch meal\ndo program time\ndo leave the portion size the same\ncheck the amount of kibbles dispensed is 1/2 of the cup\nActual result - 1/3 of the cup. Lunch is smaller again.\n\testcase twelve\ndo program dinner meal\ndo program time\ndo leave the portion size the same - 1/2 of the cup\ncheck the amount of kibbles dispensed is 1/2 of the cup\nActual result - 1/2, the portion was dispensed in 2 increments with the interval. As there is no consistency again, let test this portion size one more time.\n \ntestcase thirteen\ndo repeat test case ten\ncheck the amount of kibbles is 1/2 of the cup\nActual result - a bit less than 1/2\n\n\ntestcase fourteen\ndo repeat test case eleven\ncheck the portion size is 1/2 of the cup\nActual result - a bit more than 1/2. This portion size looks more stable. The lunch isn't the smallest anymore.\n\ntestcase fiftee\ndo repeat test case twelve\ncheck portion size is 1/2 of the cup\nActual result - is l/2, a bit less of the exact amount. All three meals were served with almost correct portion size.\n\n\n- \n\n\n");
        mainPanel.add(new JScrollPane(textPane), BorderLayout.CENTER);
        setContentPane(mainPanel);

    }

    public static void main(String[] args) {
        CPerfJava7 frame;

        WindowListener exitListener = new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                Window window = e.getWindow();
                window.setVisible(false);
                window.dispose();
                System.exit(0);
            }
        };

        frame = new CPerfJava7();
        frame.addWindowListener(exitListener);
        frame.setPreferredSize(new Dimension(600, 400));
        frame.pack();
        frame.setVisible(true);

        try {
            // run it to warm it up
            frame.docu.doParse();
            frame.docu.doParse();
            frame.docu.doParse();
            frame.docu.doParse();
            frame.docu.doParse();
            frame.docu.doParse();
            // then do real measures
            totalTime = 0;
            for (int k=0; k<50; k++) {
                frame.docu.doParse();
            }
            System.err.println(" AVERAGE = " + (totalTime/50));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /*
    +----------------------------------------------------------------------+
    |                    Class: CPseudoHtmlDocument                        |
    +----------------------------------------------------------------------+
    */

    @SuppressWarnings("serial")
    public class CPseudoHtmlDocument extends DefaultStyledDocument {

        // +==============================================================+
        // | Attribute(s)                                                 |
        // +==============================================================+

        private final SimpleAttributeSet attrNormal  = new SimpleAttributeSet();
        private final SimpleAttributeSet attrSpecial = new SimpleAttributeSet();
        private final SimpleAttributeSet attrDefault = new SimpleAttributeSet();

        private int currentPos = 0;

        // +==============================================================+
        // | Constructor(s)                                               |
        // +==============================================================+

        public CPseudoHtmlDocument() {
            StyleConstants.setForeground(attrSpecial, Color.decode("0xC71D15"));StyleConstants.setBold(attrSpecial, true);
            StyleConstants.setForeground(attrDefault, new Color(150, 150, 0));
            StyleConstants.setBold(attrDefault, true);
        }

        // +==============================================================+
        // | Method(s)                                                    |
        // +==============================================================+

        public long getCurrentTimeMillis() {
            Calendar now = Calendar.getInstance();
            return now.getTimeInMillis();
        }
        public String getCurrentLogTime() {
            Calendar now = Calendar.getInstance();
            return calendarToPreciseTimeOfTheDay(now);
        }
        public String calendarToPreciseTimeOfTheDay (Calendar calendar) {
            SimpleDateFormat localSimpleDateFormat = new SimpleDateFormat("HH:mm:ss.S");
            return localSimpleDateFormat.format(calendar.getTime());
        }

        private void doParse() {
            long before = getCurrentTimeMillis();
            setCharacterAttributes(0, 3260, new SimpleAttributeSet(), true); // reset all the text to review (including the rewinded part) with normal attribute
            for (int i = 0; i <= 3260; i++) {
                currentPos = i;
                checkForKeyword(true);
            }
            long after = getCurrentTimeMillis();
            totalTime += (after-before);
            System.err.println("   -> time to parse: " + (after-before) + " ms");
        }

        private void checkForKeyword(boolean insert) {
            int preceedingStartTag, nextStopTag, preceedingPercentTag, nextPercentTag, preceedingSpace, nextSpace;
            char currentChar = ' ';
            try {
                currentChar = this.getText(currentPos, 1).charAt(0);
            } catch (BadLocationException e1) {
            }

            if (currentChar == '[') {
                nextStopTag = getNextStop(currentPos+1, true);
                setStyle(currentPos, nextStopTag); // check if this is a [xxx] we need to highlight

            } else if (currentChar == ']') {
                preceedingStartTag = getPreceedingStart(currentPos-1, true); // returns -1 if not found or another [, ], \n or space found first
                setStyle(preceedingStartTag, currentPos); // check if this is a [xxx] we need to highlight

            } else if (currentChar == '%') {
                nextPercentTag = getNextPercent(currentPos+1, true); // returns -1 if not found or \n or space found first
                if (nextPercentTag >= 0) {
                    // removed code
                }

            } else {
                // [xxx]
                preceedingStartTag = getPreceedingStart(currentPos-1, true); // returns -1 if not found or another ], \n or space found first
                nextStopTag = getNextStop(currentPos, true); // returns -1 if not found or another [, \n or space found first
                if (preceedingStartTag >=0 && nextStopTag >=0) {
                    setStyle(preceedingStartTag, nextStopTag);
                }

                // PARAMS
                preceedingPercentTag = getPreceedingPercent(currentPos-1, true); // returns -1 if not found or another [, ], \n or space found first
                nextPercentTag = getNextPercent(currentPos, true); // returns -1 if not found or another [, ], \n or space found first
                if (preceedingPercentTag >=0 && nextPercentTag >=0) {
                }

                // PDNL
                preceedingSpace = getPreceedingSpace(currentPos-1, true); // returns -1 if another [, ] or % found first, 0 if not found 
                nextSpace = getNextSpace(currentPos, true); // returns -1 if not found or another [, ] or % found first, length-1 if not found 
                if (preceedingSpace >=0 && nextSpace >=0) {
                    if (preceedingSpace == 0) preceedingSpace--;      // keyword at the very beginning of the text
                    setStyle(preceedingSpace+1, nextSpace-1);
                }
            }
        }

        private int getPreceedingStart(int offset, boolean strict) {
            while (offset >= 0) {
                try {
                    char charAt = this.getText(offset, 1).charAt(0);
                    if (charAt == '[') {
                        return offset;
                    } else if (charAt == ']' || charAt == '%' || charAt == ' ' || charAt == '\n' || charAt == '\r' || charAt == '\t') {
                        if (strict)
                            return -1;
                    }
                } catch (BadLocationException e) {
                    e.printStackTrace();
                }
                offset--;
            }
            if (strict)
                return -1;
            else
                return 0;
        }
        private int getNextStop(int offset, boolean strict) {
            while (true) {
                try {
                    char charAt = this.getText(offset, 1).charAt(0);
                    if (charAt == ']') {
                        return offset;
                    } else if (charAt == '[' || charAt == '%' || charAt == ' ' || charAt == '\n' || charAt == '\r' || charAt == '\t') {
                        if (strict)
                            return -1;
                    }
                } catch (BadLocationException e) {
                    if (strict)
                        return -1;
                    else
                        return offset-1;
                }
                offset++;
            }
        }
        private int getPreceedingPercent(int offset, boolean strict) {
            while (offset >= 0) {
                try {
                    char charAt = this.getText(offset, 1).charAt(0);
                    if (charAt == '%') {
                        return offset;
                    } else if (charAt == ' ' || charAt == '\n' || charAt == '\r' || charAt == '\t') {
                        if (strict)
                            return -1;
                    }
                } catch (BadLocationException e) {
                    e.printStackTrace();
                }
                offset--;
            }
            if (strict)
            return -1;
            else 
                return 0;
        }
        private int getNextPercent(int offset, boolean strict) {
            while (true) {
                try {
                    char charAt = this.getText(offset, 1).charAt(0);
                    if (charAt == '%') {
                        return offset;
                    } else if (charAt == ' ' || charAt == '\n' || charAt == '\r' || charAt == '\t') {
                        if (strict)
                            return -1;
                    }
                } catch (BadLocationException e) {
                    if (strict)
                        return -1;
                    else
                        return offset;
                }
                offset++;
            }
        }
        private int getPreceedingSpace(int offset, boolean strict) {
            while (offset >= 0) {
                try {
                    char charAt = this.getText(offset, 1).charAt(0);
                    if (charAt == ' ' || charAt == '\n' || charAt == '\r' || charAt == '\t') {
                        return offset;
                    } else if (charAt == '%' || charAt == '[' || charAt == ']') {
                        if (strict)
                            return -1;
                    }
                } catch (BadLocationException e) {
                    e.printStackTrace();
                }
                offset--;
            }
            return 0;
        }   
        private int getNextSpace(int offset, boolean strict) {
            while (true) {
                try {
                    char charAt = this.getText(offset, 1).charAt(0);
                    if (charAt == ' ' || charAt == '\n' || charAt == '\r' || charAt == '\t') {
                        return offset;
                    } else if (charAt == '%' || charAt == '[' || charAt == ']') {
                        if (strict)
                            return -1;
                    }
                } catch (BadLocationException e) {
                    return offset-1;
                }
                offset++;
            }
        }
        private void setStyle(int startTag, int stopTag) {
            if (startTag >= 0 && stopTag > 0 && stopTag > startTag) {
                int tagLength = stopTag-startTag+1;
                try {
                    String tag = this.getText(startTag, tagLength);
                    String s = tag.trim()/*.toLowerCase()*/;

                    if (s.equals("testcase") || s.equals("do") || s.equals("check") || s.equals("and")) {
                        setCharacterAttributes(startTag, tagLength, attrSpecial, true);

                    } else {
                        setCharacterAttributes(startTag, tagLength, attrNormal, true);
                    }
                } catch (BadLocationException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

在我的计算机上,如果我使用 Java 6 运行它,平均运行时间为60 毫秒

如果我使用 Java 7 运行它,则需要460 毫秒

我是唯一注意到这一点的人吗?谢谢,

编辑:这是证明错误方法是 Java 7 中的 DefaultStyledDocument.setCharacateAttributes()的新程序:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.Calendar;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

public class CPerfJava7_justSetCharacterAttributes extends JFrame {
    private static final long serialVersionUID = 1L;

    // +==============================================================+
    // | Attributes                                                   |
    // +==============================================================+
    JPanel mainPanel;
    public CPseudoHtmlDocument docu;

    // +==============================================================+
    // | Constructors                                                 |
    // +==============================================================+

    public CPerfJava7_justSetCharacterAttributes() {
        super("PerfJava7");
        docu = new CPseudoHtmlDocument();
        mainPanel = new JPanel(new BorderLayout());
        JTextPane textPane = new JTextPane();
        textPane.setDocument(docu);
        textPane.setText("testcase one\ndo program breakfast meal\ndo program time\ndo program portion size - 1/4 of the cup\nIt was easy to set up the time and portion size as directed in the manual\n\ndo collect kibbles from the bowl and measure the breakfast portion\ncheck the portion dispensed 1/4 cup of kibbles \nActual result - it was less than 1/4 cup. The wheel that rotates and dispenses\n the kibbles might haven't rotated completely.\n\ntestcase two\ndo program lunch meal\ndo program time\ndo leave the portion size the same - 1/4 of the cup\n\ncheck the portion dispensed 1/4 cup of kibbles\n do collect kibbles from the bowl and measure the lunch portion\nActual result was 1/20 of the cup, the wheel dispenser hardly released any kibbles\n\ntestcase three\n\ndo program dinner meal\ndo program time\ndo leave the portion size the same - 1/4 of the cup\n\ncheck the portion dispensed 1/4 cup of kibbles\n do collect kibbles from the bowl and measure the dinner portion\nActual result was  less than 1/4 cup\n\nWhy was the middle meal the smallest? Is lunch always the smallest?\nTo prove this hypothesis, repeat first three test cases again.\n\ntestcase four\ndo repeat test case one\nActual result - it was less 1/4 and the portion was dispensed in two increments.\n\ntestcase five\ndo repeat test case two\nActual result - 1/8 of the cup.\n\ntestcase six\ndo repeat test case three\nActual result - 1/2 cup! The wheel rotated almost twice. It looked like all\n kibbles which were not dispensed previously, got released.\n Lunch was still the smallest meal, but the dinner portion was twice of the size\n.All three tests must be repeated again.\n\ntestcase seven\ndo repeat test case one\nActual result - 1/4 of the cup. Great!\n\ntestcase eight\ndo repeat test case two\nActual result - 1/4 of the cup! It works perfectly! Why?\n\ntestcase nine\ndo repeat test case three\nActual result - 1/6, so the third meal was less than it should be. Is this size of the portion hard to dispense correctly? Let's try 1/2 of the cup\n\ntestcase ten\ndo program breakfast meal\ndo program time\ndo program 1/2 of the cup\ncheck the amount of kibbles dispensed is 1/2 of the cup\nActual result - 1/2 of the cup. Excellent. The wheel rotated twice as designed. In a minute it added 1/4 of the cup.\n\ntestcase eleven\ndo program lunch meal\ndo program time\ndo leave the portion size the same\ncheck the amount of kibbles dispensed is 1/2 of the cup\nActual result - 1/3 of the cup. Lunch is smaller again.\n\testcase twelve\ndo program dinner meal\ndo program time\ndo leave the portion size the same - 1/2 of the cup\ncheck the amount of kibbles dispensed is 1/2 of the cup\nActual result - 1/2, the portion was dispensed in 2 increments with the interval. As there is no consistency again, let test this portion size one more time.\n \ntestcase thirteen\ndo repeat test case ten\ncheck the amount of kibbles is 1/2 of the cup\nActual result - a bit less than 1/2\n\n\ntestcase fourteen\ndo repeat test case eleven\ncheck the portion size is 1/2 of the cup\nActual result - a bit more than 1/2. This portion size looks more stable. The lunch isn't the smallest anymore.\n\ntestcase fiftee\ndo repeat test case twelve\ncheck portion size is 1/2 of the cup\nActual result - is l/2, a bit less of the exact amount. All three meals were served with almost correct portion size.\n\n\n- \n\n\n");
        mainPanel.add(new JScrollPane(textPane), BorderLayout.CENTER);
        setContentPane(mainPanel);

    }

    public static void main(String[] args) {
        CPerfJava7_justSetCharacterAttributes frame;

        WindowListener exitListener = new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                Window window = e.getWindow();
                window.setVisible(false);
                window.dispose();
                System.exit(0);
            }
        };

        frame = new CPerfJava7_justSetCharacterAttributes();
        frame.addWindowListener(exitListener);
        frame.setPreferredSize(new Dimension(600, 400));
        frame.pack();
        frame.setVisible(true);

        try {
            // run it to warm it up
            frame.docu.doParse();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /*
    +----------------------------------------------------------------------+
    |                    Class: CPseudoHtmlDocument                        |
    +----------------------------------------------------------------------+
    */

    @SuppressWarnings("serial")
    public class CPseudoHtmlDocument extends DefaultStyledDocument {

        private final SimpleAttributeSet attrDefault = new SimpleAttributeSet();

        public CPseudoHtmlDocument() {
            StyleConstants.setForeground(attrDefault, new Color(150, 150, 0));
            StyleConstants.setBold(attrDefault, true);
        }

        public long getCurrentTimeMillis() {
            Calendar now = Calendar.getInstance();
            return now.getTimeInMillis();
        }

        private void doParse() {
            long before = getCurrentTimeMillis();
            for (int k=0; k<5; k++) {
                for (int i = 0; i <= 3260; i+=1) {
                    setStyle(i, i+1);
                }
            }
            long after = getCurrentTimeMillis();
            System.err.println("   -> time to parse: " + (after-before) + " ms");
        }

        private void setStyle(int startTag, int stopTag) {
            int tagLength = stopTag-startTag+1;
            setCharacterAttributes(startTag, tagLength, attrDefault, true);
        }
    }

}

在我的电脑上:

Java 6:~1 秒

Java 7:~4.5 秒

有什么方法可以让它在 Java 7 中像在 Java 6 中一样快地工作?谢谢,

编辑:这是修复性能的解决方法。Java 7 的问题(我在insertString()remove()上做了):

public void remove(int offset, int length) throws BadLocationException {
    // start by removing normally the string using the default styled document
    try {
        super.remove(offset, length);
    } catch (Exception e) {
        e.printStackTrace();
    }

    int caretPosition = textPane.getCaretPosition();
    textPane.setDocument(new DefaultStyledDocument());

    // DO HERE THE TIME-CONSUMING TASKS THAT APPLY SOME STYLES ON THE CURRENT DOCUMENT

    textPane.setDocument(this);
    textPane.setCaretPosition(caretPosition);
}
4

3 回答 3

4

我认为问题不是文档,而是视图结构布局的差异。

尝试创建一个没有 JTextPane 的 Document 并应用所有样式。

在 java7 中引入了断点计算来定义可以包装视图的位置。布局中有一个错误,请参阅使用 Java 7 在 JTextPane 中使用样式文本进行奇怪的文本换行

猜猜简单的布局也会发生同样的情况。

您可以尝试使用 ParagraphView 和 LabelView 视图,看看布局的不同变化如何极大地改变性能。

于 2013-11-10T08:09:56.630 回答
2

正如此链接所建议的那样,将 setCharacterAttributes 替换为 getHighlighter().addHighlight() 可能会很有趣。它可能会更快,也许 Java 版本之间的差异会减少很多。我无法测试它,因为它需要对代码进行太多更改。

于 2013-11-09T22:02:26.560 回答
0

我看到给定的代码片段正在使用 SimpleDateFormat。Calendar.GetInstance 和 SimpleDateFormat 是 Java 7 中的已知性能问题,特别是在运行多个线程时。尝试将 SimpleDateFormat 更改为 JodaTime 或更改代码以使用 TimeStamp(长格式)并测试一次。

于 2014-08-28T06:19:52.373 回答