Всем привет. Последние события в Украине как-то отбросили меня от хабра, но вот, все, более менее, наладилось и я, вернувшись к привычному ритму работы, вспомнил о парочке своих постов в черновиках. В связи с выходом 8-й версии явы, пост, возможно, уже несколько устарел, но не пропадать же добру.
Итак, как-то вечером, оптимизируя очередной кусочек кода — случайно заглянул в String и обнаружил, что класс строки уже не тот. Так как строка, пожалуй, один из самых распространенных типов, думаю многим будет интересно узнать об изменениях.
Метод split строки стал быстрее работать для односимвольного параметра. Теперь в методе вообще не будет использоваться регексп и будет применен indexOf в цикле.
Было:
Стало:
Начиная с 6-го апдейта 7-й явы из класса строки были удалены 2 поля:
Как вы, наверное, помните эти поля использовались при вызове метода substring. Назначение полей — уменьшение сложности метода и попытка избежать создания нового массива символов строки используя ссылку на уже существующий массив. Что, в свою очередь, в некоторых ситуациях могло порождать известную утечку памяти. Теперь же размер строки на 8 байт меньше и проблема утечки навсегда решена.
Вместо 2-х удаленных полей, появилось 1 новое целочисельное — hash32. Предназначенное для хранение нового хеша строки. Новый хеш используется, например, в hashmap:
Новый алгоритм хеширования должен улучшить распределение хешей для строк (я не смог найти, чем он конкретно лучше существующего, может кто-то в комментариях подскажет). Новая хеш-функция по умолчанию отключена и чтобы ее включить вам понадобится опция «jdk.map.althashing.threshold». Правда, вскоре после релиза 6-го апдейта оказалось, что в высококонкурентной многопоточной среде из-за метода sun.misc.Hashing.randomHashSeed() создание множества hashmap происходит гораздо медленней чем до апдейта, так как метод randomHashSeed использует внутри Random, который в свою очередь базируется на AtomicLong, который и вызвал проблемы с производительностью.
Начиная с апдейта 40 баг уже исправлен.
Как мне подсказали, в 8-й яве новый алгоритм хеширования был удален.
Так уж сложилось, что большинство разработчиков редко заглядывают во внутренние методы стандартных классов. Тем не менее внутри можно найти много простора для оптимизации. Так же и получилось с методом split строки. Для критических участков кода, вместо:
можно сделать:
И получить приблизительно двукратный прирост производительности метода split.
Итак, как-то вечером, оптимизируя очередной кусочек кода — случайно заглянул в String и обнаружил, что класс строки уже не тот. Так как строка, пожалуй, один из самых распространенных типов, думаю многим будет интересно узнать об изменениях.
Оптимизирован метод String.split()
Метод split строки стал быстрее работать для односимвольного параметра. Теперь в методе вообще не будет использоваться регексп и будет применен indexOf в цикле.
Было:
public String[] split(String regex, int limit) {
return Pattern.compile(regex).split(this, limit);
}
Стало:
public String[] split(String regex, int limit) {
if (((regex.value.length == 1 &&
".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) || ...)) {
...
while ((next = indexOf(ch, off)) != -1) {
...
}
...
return result;
}
return Pattern.compile(regex).split(this, limit);
}
2 поля удалены
Начиная с 6-го апдейта 7-й явы из класса строки были удалены 2 поля:
private int offset;
private int count;
Как вы, наверное, помните эти поля использовались при вызове метода substring. Назначение полей — уменьшение сложности метода и попытка избежать создания нового массива символов строки используя ссылку на уже существующий массив. Что, в свою очередь, в некоторых ситуациях могло порождать известную утечку памяти. Теперь же размер строки на 8 байт меньше и проблема утечки навсегда решена.
Новый хеш-алгоритм
private transient int hash32 = 0;
int hash32() {
int h = hash32;
if (0 == h) {
// harmless data race on hash32 here.
h = sun.misc.Hashing.murmur3_32(HASHING_SEED, value, 0, value.length);
// ensure result is not zero to avoid recalcing
h = (0 != h) ? h : 1;
hash32 = h;
}
return h;
}
Вместо 2-х удаленных полей, появилось 1 новое целочисельное — hash32. Предназначенное для хранение нового хеша строки. Новый хеш используется, например, в hashmap:
transient int hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0;
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
...
}
Новый алгоритм хеширования должен улучшить распределение хешей для строк (я не смог найти, чем он конкретно лучше существующего, может кто-то в комментариях подскажет). Новая хеш-функция по умолчанию отключена и чтобы ее включить вам понадобится опция «jdk.map.althashing.threshold». Правда, вскоре после релиза 6-го апдейта оказалось, что в высококонкурентной многопоточной среде из-за метода sun.misc.Hashing.randomHashSeed() создание множества hashmap происходит гораздо медленней чем до апдейта, так как метод randomHashSeed использует внутри Random, который в свою очередь базируется на AtomicLong, который и вызвал проблемы с производительностью.
Начиная с апдейта 40 баг уже исправлен.
Update по Java 8
Как мне подсказали, в 8-й яве новый алгоритм хеширования был удален.
Еще оптимизация по split
Так уж сложилось, что большинство разработчиков редко заглядывают во внутренние методы стандартных классов. Тем не менее внутри можно найти много простора для оптимизации. Так же и получилось с методом split строки. Для критических участков кода, вместо:
someString.split("[_,;,-]");
можно сделать:
private static final Pattern PATTERN = Pattern.compile("[_,;,-]");
PATTERN.split(someString);
И получить приблизительно двукратный прирост производительности метода split.