Почему повторное распределение памяти наблюдается медленнее при использовании Epsilon против G1?

Если вы действительно заботитесь о правильном анализе CSV, это будет делать это относительно медленно, поскольку он работает по одному символу за раз.

 void ParseCSV(const string& csvSource, vector<vector<string> >& lines)
    {
       bool inQuote(false);
       bool newLine(false);
       string field;
       lines.clear();
       vector<string> line;

       string::const_iterator aChar = csvSource.begin();
       while (aChar != csvSource.end())
       {
          switch (*aChar)
          {
          case '"':
             newLine = false;
             inQuote = !inQuote;
             break;

          case ',':
             newLine = false;
             if (inQuote == true)
             {
                field += *aChar;
             }
             else
             {
                line.push_back(field);
                field.clear();
             }
             break;

          case '\n':
          case '\r':
             if (inQuote == true)
             {
                field += *aChar;
             }
             else
             {
                if (newLine == false)
                {
                   line.push_back(field);
                   lines.push_back(line);
                   field.clear();
                   line.clear();
                   newLine = true;
                }
             }
             break;

          default:
             newLine = false;
             field.push_back(*aChar);
             break;
          }

          aChar++;
       }

       if (field.size())
          line.push_back(field);

       if (line.size())
          lines.push_back(line);
    }
16
задан kaan 25 September 2019 в 02:14
поделиться

2 ответа

комментарий @Holger выше объясняет часть, которую я пропускал в исходном тесте †“получение новой памяти от ОС, является более дорогим, чем переработка памяти в JVM. Комментарий @the8472 указал, что код приложения не сохранял ссылки ни на один из выделенных массивов, таким образом, тест не тестировал то, что я хотел. Путем изменения теста для хранения ссылки на каждый новый массив результаты теперь показывают Эпсилон, превосходящий G1 по характеристикам.

Вот то, что я сделал в коде для сохранения ссылок. Определите это как членскую переменную:

static ArrayList<byte[]> savedArrays = new ArrayList<>(1024);

затем добавляют это после каждого выделения:

savedArrays.add(array);

выделения Эпсилона подобны прежде, который ожидается:

$ time java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC Scratch repeatedAllocations
real    0m0.587s
user    0m0.312s
sys     0m0.296s

$ time java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC Scratch repeatedAllocations
real    0m0.589s
user    0m0.313s
sys     0m0.297s

$ time java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC Scratch repeatedAllocations
real    0m0.605s
user    0m0.316s
sys     0m0.313s

времена G1 теперь намного медленнее, чем прежде и также медленнее, чем Эпсилон:

$ time java -XX:+UseG1GC Scratch repeatedAllocations
real    0m0.884s
user    0m1.265s
sys     0m0.538s

$ time java -XX:+UseG1GC Scratch repeatedAllocations
real    0m0.884s
user    0m1.251s
sys     0m0.533s

$ time java -XX:+UseG1GC Scratch repeatedAllocations
real    0m0.864s
user    0m1.214s
sys     0m0.528s

Повторное выполнение времен на выделение с помощью repeatedAllocationsWithTimingAndOutput(), средние числа теперь соответствуют Эпсилону, являющемуся быстрее.

average time (in nanos) for 1,024 consecutive 1MB array allocations
Epsilon 491,665
G1      883,981
3
ответ дан 30 November 2019 в 17:17
поделиться

Я полагаю, что Вы видите затраты на обеспечение электричеством памяти на первом доступе.

В случае Эпсилона, выделения всегда достигают новой памяти, что означает, что сама ОС должна обеспечить электричеством физические страницы к процессу JVM. В случае G1 происходит то же самое, но после первого цикла GC, это выделило бы объекты в уже обеспеченной электричеством памяти. G1 испытал бы случайные переходы задержки, коррелируемые с паузами GC.

, Но существуют особенности ОС. По крайней мере, на Linux, когда JVM (или действительно, любой другой процесс) "резервы" и память "фиксаций" память не на самом деле обеспечена электричеством: это , физические страницы еще не присвоены ему. Как оптимизация, Linux приводит в порядок этот провод на первом доступе для записи к странице. То действие ОС проявило бы как sys%, между прочим, который является, почему Вы видите его в синхронизациях.

И это - возможно правильная вещь для ОС, чтобы сделать при оптимизации места, например, много процессов, работающих на машине, (пред-) выделяющий большую память, но едва использующий ее. Это произошло бы с, скажем, -Xms4g -Xmx4g: ОС счастливо сообщила бы, что вся 4G "фиксируется", но ничего еще не произошло бы, пока JVM не начнет писать там.

Все это - подход к этому странному приему: предварительное касание всей памяти "кучи" в JVM запускается с -XX:+AlwaysPreTouch (уведомление head, это самые первые образцы):

$ java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xms4g -Xmx4g \
       Scratch repeatedAllocationsWithTimingAndOutput | head
491988
507983
495899
492679
485147

$ java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+AlwaysPreTouch -Xms4g -Xmx4g \
       Scratch repeatedAllocationsWithTimingAndOutput | head
45186
42242
42966
49323
42093

И здесь, выполнение из поля действительно заставляет Эпсилон выглядеть хуже, чем G1 (уведомление tail, это самые последние образцы):

$ java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xms4g -Xmx4g \
       Scratch repeatedAllocationsWithTimingAndOutput | tail
389255
386474
392593
387604
391383

$ java -XX:+UseG1GC -Xms4g -Xmx4g \
  Scratch repeatedAllocationsWithTimingAndOutput | tail
72150
74065
73582
73371
71889

..., но это изменяется, как только обеспечение электричеством памяти вне изображения (уведомление tail, это самые последние образцы):

$ java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+AlwaysPreTouch -Xms4g -Xmx4g \
       Scratch repeatedAllocationsWithTimingAndOutput | tail
42636
44798
42065
44948
42297

$ java -XX:+UseG1GC -XX:+AlwaysPreTouch -Xms4g -Xmx4g \
        Scratch repeatedAllocationsWithTimingAndOutput | tail
52158
51490
45602
46724
43752

G1 улучшается также, потому что он касается небольшого количества новой памяти после каждого цикла. Эпсилон немного быстрее, потому что он имеет меньше материала, чтобы сделать.

В целом, это - то, почему -XX:+AlwaysPreTouch рекомендуемая опция для low-latency/high-throughput рабочих нагрузок, которые могут принять оплачиваемую авансом стоимость запуска и оплачиваемую авансом оплату места RSS.

UPD: Задумайтесь об этом, это - Эпсилон ошибка UX, и простые особенности должны произвести предупреждение пользователям .

29
ответ дан 30 November 2019 в 17:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: