Производительность Java CLI-приложения с помощью StringBuilder - PullRequest
3 голосов
/ 05 августа 2010

Общие сведения:
Я пишу сокет-клиент, который постоянно получает данные / котировки "Market" (бесконечный цикл) со стороны сервера (удаленной).
Я делю данные на куски, чтобы я мог их использовать.
каждый блок содержит около 200 символов и должен быть преобразован в массив.
После разделения фрагмента он анализируется в списке (здесь нет проблем).

Проблема:
Загрузка процессора достигает 40% после 10 минут работы.
Мне удалось выделить проблему.
Каждый кусок должен быть преобразован в JSON.
Итак, я даю вам настоящий код, который решает проблемы.
этот код выполняется каждые 300-400 мс.
Пропуск этого кода приведет к тому, что вся система будет загружена на 1% -2%.

Примечание:
Я прочитал эту ветку, но не вижу там никакого решения.
Лучше ли повторно использовать StringBuilder в цикле?

код:

private static StringBuffer jsonVal = new StringBuffer();

    public static String toJson(List<QuotesData> quotesData) {
        // Empty variable
        jsonVal.delete(0, jsonVal.length());
        jsonVal.append("{");
        synchronized (quotesData) {
            for (QuotesData quote : quotesData) {

                jsonVal.append("\"").append(quote.getSymbol()).append("\":[{");
                jsonVal.append("\"ask\":\"").append(quote.getAsk()).append(
                        "\",");
                jsonVal.append("\"bid\":\"").append(quote.getBid()).append(
                        "\",");
                jsonVal.append("\"time\":\"").append(quote.getDateTime())
                        .append("\"}],");

            }
            jsonVal.append("}");
            String returnString = jsonVal.toString();
            return returnString.toString().replace("}],}", "}]}");
        }
    }

Ответы [ 4 ]

2 голосов
/ 05 августа 2010

Сначала я бы предложил использовать JProfiler или JConsole, оба из которых включены в JDK6, чтобы точно определить , где наблюдается снижение производительности.

Не зная, где используется процессор, я бы избежал synchronized. Я сомневаюсь, что append это проблема. Очистите его, избавившись от static local jsonVal тоже.

public static String toJson(final List<QuotesData> quotesData) {
    final List<QuotesData> theData = new ArrayList<QuotesData>(quotesData);
    StringBuffer jsonVal = new StringBuffer();
    jsonVal.append("{");
    for (QuotesData quote : quotesData) {
        jsonVal.append("\"").append(quote.getSymbol()).append("\":[{");
        jsonVal.append("\"ask\":\"").append(quote.getAsk()).append(
                "\",");
        jsonVal.append("\"bid\":\"").append(quote.getBid()).append(
                "\",");
        jsonVal.append("\"time\":\"").append(quote.getDateTime())
               .append("\"}],");

    }
    jsonVal.append("}");
    String returnString = jsonVal.toString();
    return returnString.toString().replace("}],}", "}]}");
}

Рассмотрите возможность использования библиотеки JSON, например Gson . Код становится намного проще. Вы можете настроить выход при необходимости:

private static final Gson gson = new Gson();
public static String toJson(final List<QuotesData> quotesData) {
    return gson.toJson(new ArrayList<QuoteData>(quotesData));
}
0 голосов
/ 05 августа 2010

ОК, так что это похоже на классический случай чрезмерной оптимизации. Создание объекта не так дорого, что вам нужно переписать один и тот же строковый буфер, особенно если это вызывается каждые 300-400 мс.

Я постараюсь рассмотреть все возможные сценарии: Экспоненциальный рост Приведенный выше код присваивается новому потоку каждые 300 мс, но список огромен, и для его сериализации требуется более 300 мс. Если это так, то вы в основном душите свои ресурсы, и это только вопрос времени, когда приложение аварийно завершает работу. Если это так, вы должны видеть, что процессор постоянно растет. Решение было бы:

  1. Ограничить количество потоков, которые могут работать одновременно, чтобы не убивать приложение
  2. Одновременно создайте объект json и объедините результат, поэтому создание одного json занимает менее 300 мс.

ускорения Итак, список не является клонируемым, что, как я предполагаю, означает, что на самом деле это не список, а какая-то очередь, реализованная в виде интерфейса списка. Итак, оставив синхронизацию, как я сделал бы это:

public static final int JSON_LENGTH = 250; //you probably know this

public static String toJson(final List<QuotesData> quotesData) {
    jsonVal = new StringBuilder(JSON_LENGTH * quotesData.size());
    jsonVal.append("{");
    synchronized (quotesData) {
        for (QuotesData quote : quotesData) {

            jsonVal.append("\"").append(quote.getSymbol()).append("\":[{")
            .append("\"ask\":\"").append(quote.getAsk()).append("\",")
            .append("\"bid\":\"").append(quote.getBid()).append("\",")
            .append("\"time\":\"").append(quote.getDateTime()).append("\"}],");

        }
        // much much faster than replace
        jsonVal.setCharAt(jsonVal.length()-1, '}');
        return jsonVal.toString();
    }
}

Большинство изменений косметические, и я уверен, что JIT уже оптимизирует их. Единственное отличие, которое я хотел бы сделать, - это использовать StringBuilder и каждый раз создавать новый и не использовать .replace () . Но чтобы подчеркнуть мою точку зрения, если вы не подходите под первое описание (экспоненциальный рост), я сомневаюсь, что проблема здесь. Я бы сначала посмотрел на реализацию списка.

0 голосов
/ 05 августа 2010

Мой гость в том, что размер StringBuilder постоянно изменяется. Сколько цитат есть данных? Я предлагаю вам создать StringBuilder с размером до цикла for:

StringBuffer jsonVal = new StringBuffer(quotesData.size()*200); //the 200 is on top of my head. Do a few loop to see what is the average length of a QuotesData.

Кстати, вы рассматривали возможность использования StringBuilder вместо этого? Это то же самое, что и StringBuffer, за исключением того, что он является поточно-ориентированным (StringBuffer синхронизирован, а StringBuild - нет).

0 голосов
/ 05 августа 2010

Несколько предложений:

  • Профилируйте код, он должен показать вам горячие точки.
  • Используйте StringBuilder вместо StringBuffer.StringBuffer синхронизируется, StringBuilder - нет.
  • Действительно ли нужен оператор synchronized?Если нет, попробуйте удалить его.
  • toString() не требуется в операторе возврата.Вы можете удалить его.
  • Измените код, чтобы в конце вам не понадобился метод replace(), который может быть дорогостоящим, если returnString длинный.
  • Попробуйте создатьновый StringBuffer объект перед циклом вместо очистки старого.
  • Попробуйте вернуть interned значение строки, т.е. return returnString.intern()
...