Как стать автором
Обновить

Комментарии 54

Про милионы временных Integer-объектов немного не правда — как минимум от -128 до 127 значения Integer кэшируются.
+ все эти временные Integer будут довольно быстро появляться и умирать в эдене.
У вас есть какие-нить замеры перфоманса на тему Integer vs MutableInteger?
Я считал 10M строк, которые раскидывались на 4 группы. MutableInteger более чем вдвое быстрее. Собственно, я написал об этом.
Накидал грубый тест:
pastebin.com/zKYxyiSG
Результат:
Map<String, MutableInteger> быстрее ~ на 51%
Работа через массив (выгодно если у нас малое/фиксированное колличество ключей) ~ на 48% быстрее.
В тривиальной имплементации (Map<String, Integer>) проседание по перфомансу естественно есть, видимо из-за боксинга, GC работает моментально.
Имплементация c Mutable работает быстрее всего, но совсем немного опережает имплементацию через массив (грубый пример можно посмотреть в тесте)
lany, с вами согласен — в данном конктертом случае mutableInteger более быстр и вполне себе гуд решение, однако с ним надо аккуратно
я бы остановился на решении с массивом, но это уже для каждой задачи своё.
Массив, на мой взгляд, тут как раз загрязняет код сильнее. Ваша функция с массивом и так длиннее вышла, а если предположить, что вам надо заботиться о его растягивании, будет ещё хуже. Но да, зависит от задачи. Я, конечно, не предлагаю панацею, я лишь говорю, что если такой инструмент, который иногда может пригодиться. У нас в проекте на 4000 классов Mutable-числа используются от силы в двух местах, а массивы значительно чаще :-)
если -> есть, опечатка.
Вот прямо так и тестировали? Без разогревающих циклов? Без JIT'а? Нда… «Copyright © Luxoft», ничего не скажешь.
Ну я предупредил что это грубый тест, разогревающие циклы просто не были учтены в результатах.
Целью было не получить точные цифры, а увидеть общую картинку.
«Copyright © Luxoft» — посыпаю голову пеплом, что не почистил файл перед копированием… или вы на что-то намекаете
Если вы считаете до 10000000, скажем, то как вам этот кэш поможет? Только накладные расходы на проверку, входит ли новое число в этот диапазон.
А я еще, по сравнению с первым алгоритмом, мы неплохо выигрываем на том, что избавляемся от counts.put(next, val+1), особенно если у нас частые повторы слов. Давно уже пользуюсь вторым подходом как раз, чтобы избегать лишних вставок в map, на счет боксинга тоже подозревал, но никак руки не доходили проверить
А еще можно использовать вот это trove.starlight-systems.com/, особенно если работать с такого рода коллекциями нужно много и не смущает дополнительный джарник.
Ну пор это я тоже написал:
либо использовать сторонние библиотеки, реализующие нестандартный интерфейс.
Я бы не советовал так делать, Изменяемые объекты это ужас-ужас.

Кто то возьмет количество из Map, чтобы потом изпользовать, а его под носом изменят. А что с многопоточными приложениями? Не, ну его нафиг такие оптимизации. Кстати — короткоживущие объекты почто не влияют на производительность
К сожалению, концепция неизменяемых объектов в джаве довольно ущербная — у вас нет стандартных интерфейсов неизменяемых коллекций, у вас нет возможности просто указать _компилятору_, что аргумент/возвращаемое значение — не изменяемое… Нужные интерфейсы можно ввести самому — но это немаленькая работа, и большинство библиотек вас не поддержит. В общем, полноценная реализация разграничения mutable/immutable в яве обойдется крайне дорого. Как правило это разграничение проводится неявно, на основе соглашений. И в этом случае нет никакой разницы, что возвращать изменяемый словарь с неизменяемыми значениями, что возвращать изменяемый словарь с изменяемыми значениями…
В других языках бывает ещё хуже, и ничего — живут и радуются.
Моя мысль была такая: от замены Map[String, Integer] на Map[String,MutableInteger] ничего концептуально не ухудшится. Неизменяемости и до того толком не было, поэтому нет особого смысла жаловаться, что она-де пропадет.

А что с этим жить можно — безусловно. И жить, и даже кое-где подправлять.
На самом деле можно создать интерфейс ImmutableInteger со всеми методами из MutableInteger, кроме set и increment и реализовать его в MutableInteger. и Возвращать соответственно Map[String,ImmutableInteger].
Тогда все станет хорошо.
ну или не плодить свои классы и реализовать в MutableInteger абстрактный Number(как и предлагал автор) и его собственно и возвращать.
«На самом деле» нужно делать интерфейс Counters{ int get(K key); Iterable keys(); } — это если вы в самом деле хотите правильно.

Об этом я и говорю: предлагаемое автором решение уже заметно неправославно, от замены Integer на MutableInteger ничего не ухудшится. Для приватной реализации сойдет, для public API по-любому плохо.
Языковая поддержка неизменяемых коллекций, конечно никакая, но это не знит, что надо делать еще хуже. Если я возвращаю не коллекцию, а счетчик, то лучше чтобы он был неизменяемым
Да на здоровье же. Я ж написал:
Ну или в крайнем случае после подсчёта скопировать всё в новую Map.
Что не так?
А то, что проблема не в Map, а в самих неизменяемых числах. Вам нужно будет еще и их клонировать. Т.е. если на основе этого примера сторить реальное приложение, то надо будет добавить столько всего, что все ваши оптимизации сойдут на нет. Дайте компилятору и виртуальной машине оптимизировать, ради бога. Вы не слышали фразу «Преждевременная оптимизация — корень всего зла»?
Ну где я написал, что я рекомендую оптимизировать преждевременно? Что ж вы мне приписываете то, чего я не говорил? Надо в начало каждой статьи вставлять слова, что перед оптимизацией сперва используйте профайлер? По-моему, всем давно очевидно.

У вас очень жизненная заметка «Про релевантность опыта», спасибо.
Надо в начало каждой статьи вставлять слова, что перед оптимизацией сперва используйте профайлер?

Увы, но надо. Как ни печально, но тех, кто понимает, что в статье описывается достаточно экзотическая ситуация* и решения, которые тут применяются, тоже достаточно экзотичны (хотя и, безусловно, интересны) — на Хабре меньшинство. И это меньшинство довольно хорошо совпадает с другим меньшинством — тех, кто в случае просадок производительности сначала исследуют ситуацию (например, с помощью профайлера), а уже потом начинают что-то исправлять.

* — ситуация, когда java используется для data mining-а экзотична по определению, так как она не очень для этого подходит, ни как язык, ни как среда исполнения.
Любой примитивный int — изменяемый объект. Вы их вообще не используете? А если используете, то как же многопоточность? Или вы решили, что я предлагаю использовать MutableInteger всегда? Но я же ясно написал:
Однако в некоторых случаях вам помогут изменяемые (mutable) числа.

Внутренние детали вычислительно нетривиальной операции могут обойтись без высокоуровневых концепций, вам так не кажется?
Кстати — короткоживущие объекты почто не влияют на производительность

Кроме тех случаев, когда влияют.
int передается по значению, а объект по ссылке. Если я куда то передал примитивный счетчик, мне уже не надо волноваться, что кто то его изменит.

Расскажите мне в каких случаях короткоживущие объекты влияют на производительность
Вы можете вернуть наружу из алгоритма любую структуру данных. Если вы боитесь за изменчивость, скопируйте результат в неизменяемый тип (я это упомянул в статье). В рассмотренном примере это вообще проблем не несёт. И, надо полагать, вы из тех людей, которые обычные Java-массивы не используют вообще? Ведь их там могут изменить.

Короткоживущие объекты влияют на производительность в примере, рассмотренном в статье.
Вот пример из жизни — habrahabr.ru/post/147552/ о котором я недавно писал. В этом случае коротко живущие объекты не только влияли на производительность, но и приводили к постоянному срабатыванию сборщика что просто вешало машину. Я уже не говорю про десятки таких мест в высоконагруженных системах.
Присоединюсь к критикам. Конкретно задачу из примера намного лучше решить с использованием AtomicInteger и его атомарного инкремента. Мало того что тривиально добавляется многопоточность, так ещё и никаких левых костылей, затрудняющих чтение кода.

К тому же, если пока вы считали распределение строк, ваши изменяемые числа не дай бог успели постареть и попасть в OldGen — прощай всякая производительность.
Конкретно задачу из примера намного лучше решить с использованием AtomicInteger и его атомарного инкремента.
Вы проверяли, насколько это быстро? Атомарный инкремент ужасно медленный. Просто ужасно. Попробуйте эту задачу распараллелить, вы больше потеряете, чем приобретёте.

И почему вы считаете, что MutableInteger и increment больше затрудняют чтение кода, чем AtomicInteger и incrementAndGet? Код абсолютно одинаковый.

К тому же, если пока вы считали распределение строк, ваши изменяемые числа не дай бог успели постареть и попасть в OldGen
Нестрашно, если их мало.
Руки оторвать:
— Тем кто придумал Java generic не как first class.
— Тем кто использует Integer вообще в коллекциях.
— Тем кто из-за недостатков подхода из второго пункта придумывает гибриды костылей с велосипедами.

Извините, наболело.
* — Естественно не только Integer, но и Byte, Short, Long, Float, Double.
В чём проблема с использованием Integer в коллекциях?
А уж чем Byte не угодил — вообще неясно. Байты в стандартной реализации закэшированы абсолютно все, новых объектов при боксинге вообще не создаётся. Почитайте исходник java.lang.Byte.

Любопытно, что мнения комментаторов опять же противоположны, о чём я и сказал в начале статьи: от «накладные расходы невелики, молодые объекты удаляются быстро, используйте Integer и не парьтесь» до «Integer использовать ни в коем случае нельзя, это ужасно» :-)
Да, простите, сонный был. :)
Byte — исключение.
Голову надо отрывать тем критикам, кто мечтает оторвать руки инженерам, решавшим (и довольно успешно решившим) проблемы, о которых оный критик даже не думал толком :)
Мне кажется, вы оторваны от реального мира и его задач. В книжках-то конечно всё идеально прекрасно, одновременно и быстро, и абстрактно, и концептуально правильно.
НЛО прилетело и опубликовало эту надпись здесь
Вы прекрасно меня поняли.

Собственно крик души у меня был в том, что в C# эти (и далеко не только) проблемы решены, однако, по некоторым причинам .NET использовать не представляется возможности.
Может, MutableInteger и полезен, но пример не совсем удачный. Для указанной задачи идеально подходит TObjectIntHashMap из библиотеки Trove с его методом adjustOrPutValue.
Вставлю свои 5 копеек. Если забыть о недостатках и сконцентрироваться на достоинствах подхода с классом оберткой, то он вполне пригоден для использования с оговоркой not thread-safe. Надеюсь автор сам написал класс и еще просто не знаком с commons-lang и классами из пакета org.apache.commons.lang.mutable.
По поводу перформанса. Немного быстрее будет если заменить класс-обертку одноэлементным массивов. И то, и другое HotSpot успешно компилирует в быстрый код, но доступ к элементам массива происходит немного быстрее. Ну и для удобства можно написать вспомогательный класс:
class MutableInt {
	static Object create() { return new int[1]; }
	static int get(Object ref) { return ((int[])ref)[0]; }
	static void set(Object ref, int value) { ((int[])ref)[0] = value; }
	static int incAndGet(Object ref) { return ++((int[])ref)[0]; }
	static int getAndInc(Object ref) { return ((int[])ref)[0]++; }
}
Надеюсь автор сам написал класс и еще просто не знаком с commons-lang и классами из пакета org.apache.commons.lang.mutable.
Автор знаком и сослался на commons.lang. Но здесь класс написал сам, чтобы понятнее было.
Действительно, ссылка есть, прошу прощения за невнимательность.
Немного быстрее будет если заменить класс-обертку одноэлементным массивов. И то, и другое HotSpot успешно компилирует в быстрый код, но доступ к элементам массива происходит немного быстрее.

Можно с этого места поподробнее? Почему доступ к элементам массива быстрее?
Думаю, тут тормоза не из-за создания новых объектов, а из-за двукратного лазания в мапу — при гете и при путе. Если бы можно было получить Map.Entry и модифицировать уже его…
Объясните пожалуйста, а почему нельзя использовать < String, int >?
Потому что так устроены генерики в Java: в них не может быть примитивного типа. Сами попробуйте.
Потому что generic'и требуют объекта в качестве параметра.
А упирается это в генерацию кода, совместимого с Java 1.4.
Реально в runtime вместо типа T подставляется ближайший тип, прописанный в extends, и используются приведения типов. Они, естественно, будут безопасными, т.к. компилятор уже все проверил.
Кстати, это одна из причин, почему нельзя сделать так:

    public class SuperClass<T> {
        public void setT(T obj) {
           //...
        }
    }
    
    public class SubClass<Number> extends SuperClass<Number> {
        // Здесь будет ошибка компиляции, т.к. метод с такой сигнатуров уже есть
        public void setT(Object obj) {
            //...
        }

        public void setT(Number obj) {
            //...
        }
    }
Интересный пример. Правильно ли я понимаю, что в байткоде SubClass будет метод с сигнатурой public void setT (Object obj), в котором будет приведение входного параметра к Number? Почему-то всегда был уверен, что компилятор меняет сигнатуру для конечного типа.
IMHO, решать проблему надо в корне.
generic'и нужно заменить на template'ы с поддержкой примитивных типов.
А если следовать парадигме обратной совместимости, то добавить рядом.
Синтаксис различимый вполне можно сделать.
Преобразование между ними — тоже.
Зато приседаний станет гораздо меньше.
Вот, кажется, для оптимизации целых вычислений в JIT именно MutableInteger может быть очень полезен.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории