Подтвердить что ты не робот

Почему string.intern() так медленно?

Прежде чем кто-либо задаст вопрос об использовании string.intern() вообще, позвольте мне сказать, что мне это нужно в моем конкретном приложении по соображениям памяти и производительности. [1]

Итак, до сих пор я использовал string.intern() и считал, что это самый эффективный способ сделать это. Тем не менее, я заметил, что с возрастом это узкое место в программном обеспечении. [2]

Затем, совсем недавно, я попытался заменить string.intern() на огромную карту, где я помещал/получал строки, чтобы каждый раз получать уникальный экземпляр. Я ожидал, что это будет медленнее... но это было как раз наоборот! Это было намного быстрее! Замена intern() путем нажатия/опроса карты (которая достигает точно такой же) привела к более чем на порядок быстрее.

Вопрос: почему intern() так медленно?!? Почему же это не просто подкрепляется картой (или фактически, просто настроенным набором) и будет намного быстрее? Я озадачен.

[1]: для неубежденных: он обрабатывается на естественном языке и должен обрабатывать гигабайты текста, поэтому необходимо избегать многих экземпляров одной и той же строки, чтобы избежать раздувания памяти и сравнения ссылочных строк достаточно быстро.

[2]: без него (нормальные строки) это невозможно, при этом этот конкретный шаг остается наиболее интенсивным для вычисления

EDIT:

Из-за удивительного интереса к этому сообщению, вот какой-то код для его проверки:

http://pastebin.com/4CD8ac69

И результаты интернирования бит более 1 миллиона строк:

  • HashMap: 4 секунды
  • string.intern(): 54 секунды

Из-за того, что вы не используете кеширование разморозки /OS IO и прочее подобное, эксперимент повторялся путем инвертирования порядка обоих эталонных тестов:

  • string.intern(): 69 секунд
  • HashMap: 3 секунды

Как вы видите, разница очень заметна, более десяти раз. (Используя OpenJDK 1.6.0_22 64 бит... но с помощью солнца один привел к аналогичным результатам, я думаю)

4b9b3361

Ответ 1

Скорее всего, причина разницы в производительности: String.intern() - это собственный метод, и вызов собственного метода приводит к массивным накладным расходам.

Так почему же это родной метод? Вероятно, потому что он использует постоянный пул, который представляет собой низкоуровневую конструкцию VM.

Ответ 2

В этой статье обсуждается реализация String.intern(). В Java 6 и 7 реализация использовала хэш-таблицу фиксированного размера (1009), так как число записей увеличилось, производительность стала O (n). Фиксированный размер можно изменить с помощью -XX:StringTableSize=N. По-видимому, в Java8 размер по умолчанию больше, но проблема остается.

Ответ 3

@Michael Borgwardt сказал это в комментарии:

intern() не синхронизируется, по крайней мере, на уровне языка Java.

Я думаю, что вы имеете в виду, что метод String.intern() не объявляется как synchronized в исходном коде класса String. И действительно, это истинное утверждение.

Однако:

  • Объявление intern() как synchronized блокирует текущий экземпляр String, потому что это метод экземпляра, а не статический метод. Таким образом, они не могли реализовать синхронизацию пула строк таким образом.

  • Если вы отступите и подумаете об этом, пул строк должен выполнить некоторую внутреннюю синхронизацию. Если бы это было не так, это было бы непригодным для использования в многопоточном приложении, потому что для всего кода, использующего метод intern() для внешней синхронизации, просто нет практического пути.

Таким образом, внутренняя синхронизация, которую выполняет пул строк, может быть узким местом в многопоточном приложении, которое сильно использует intern().

Ответ 4

Я не могу говорить с большим опытом с ним, но из документов String:

"Когда вызывается метод intern, если пул уже содержит строку, равную этому объекту String, как определено методом {@link #equals (Object)}, тогда возвращается строка из пула. В противном случае, этот объект String добавляется в пул и возвращается ссылка на этот объект String."

При работе с большим количеством объектов любое решение, связанное с хешированием, превосходит тот, который этого не делает. Я думаю, что вы просто видите результат неправильного использования функции языка Java. Interning не существует, чтобы действовать как карта строк для вашего использования. Вы должны использовать карту для этого (или, если необходимо, Set). Таблица строк предназначена для оптимизации на уровне языка, а не уровня приложения.

Ответ 5

Принятый ответ неверен. String.intern становится медленным из-за двух причин:
1. ограничение -XX: StringTableSize.
В java он использует внутреннюю хэш-таблицу для управления строковым кешем, в java 6 значение по умолчанию StringTableSize по умолчанию равно 1009, что означает, что string.intern is O (число строкового объекта /1009), когда все больше и больше строковых объектов было создано, он становится медленнее.

\ openjdk7\точка доступа\SRC\доля\ут\файлов классов \symbolTable.cpp

oop StringTable::intern(Handle string_or_null, jchar* name,  
                        int len, TRAPS) {  
  unsigned int hashValue = java_lang_String::hash_string(name, len);  
  int index = the_table()->hash_to_index(hashValue);  
  oop string = the_table()->lookup(index, name, len, hashValue);  
  // Found  
  if (string != NULL) return string;  
  // Otherwise, add to symbol to table  
  return the_table()->basic_add(index, string_or_null, name, len,  
                                hashValue, CHECK_NULL);  
}

2. В java 6 пул кэша строк находится в области perm, а не в куче. В большинстве случаев мы настраиваем размер perm относительно небольшим.