Pull to refresh

Comments 48

Обобщая написанное, у меня все еще остается несколько вопросов.

Неужели этот код накидан на скорую руку и никто не хочет им заниматься?

Тоже возникало достаточно много вопросов при изучении исходников как JDK, так и стандартной библиотеки .NET.


И если в случае .NET есть .NET Core, где старый код переписывается с нуля,
то в случае Java ситуация чуть другая — в какой-то момент она стала быстро развиваться, в разы быстрее обычного, и действительно есть ощущение, что часть фич не успевает продуматься как со стороны контракта, так и со стороны реализации.


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


Например, в .NET, когда был введен оператор "is null", вначале он был реализован "в лоб" — вызовом object.Equals(o, null), что было неоптимально как для ссылочных типов, так и структур (упаковка).
Потом исправили.


Наверное, и в Java будут дорабатывать реализацию JDK.

Разве загрузка класса и инициализация статических полей не происходит всегда лениво и потокобезопасно?
Насчет потокобезопасности вы правы, спасибо, исправил.

Lazy init в случае List0 совершенно не нужен. Вы, видимо, плохо понимаете вообще, когда он нужен. Если не бывает обращений к классу без обращений к инстансу синглтона, то смысла в lazy init никакого. Тут как раз такой случай.

Не поспорю, как-то эта часть про синглтон пришла в голову в последний момент, сейчас понимая, что зря добавил. Убрал.
Неужели этот код накидан на скорую руку и никто не хочет им заниматься?

Нет, этот код писали и переписывали очень долго и обсуждали с кучей людей, и ругались. И в Java 10 ещё допереписывали. Это выверенная и продуманная реализация.


Вот это очень неожиданно. Почему нельзя было просто всегда возвращать false? Зачем тут проверка на null. Это мне остается непонятным.

Открываем контракт класса Collection и читаем:


Throws:
NullPointerException — if the specified element is null and this collection does not permit null elements (optional)

Коллекция поддерживает null-элементы? Нет. Значит, по контракту имеет право кидать NPE. Зачем это сделано? Если вы случайно вызовете на ней contains(null) (что не очень имеет смысл) вы вместо тихого false словите громкое исключение и сразу же поймёте, что в коде ошибка.

UFO just landed and posted this here

Вы измеряли реальный GC pressure от использования time API в реальных сценариях? Или так сорцы почитали и сразу поняли, что всё будет тормозить?

UFO just landed and posted this here

Возражение №1: А что если вам только кажется, что объект выделяется? Если в коде написано new, это ещё не означает, что объект действительно выделится.


Возражение №2: А что если накладные расходы на выделение объекта настолько малы, что составляют 0.0001% от производительности программы, но при этом уберегают вас от возможных ошибок, которые бы вы совершили, если бы API выделяло меньше объектов? Если же вы не готовы пожертвовать даже 0.0001% производительности, тогда вам не следует писать на Java, потому что вы уже жертвуете гораздо большим процентом.

UFO just landed and posted this here
Я не знаю таких оптимизаций в JVM, которые бы удаляли new в момент компиляции.

Ну если вы не знаете, это же не значит, что их нет.


то я бы конечно выбрал реализацию, где объектов выделяется меньше.

Такое чувство, что вам просто не нравятся объекты сами по себе. Не там вы ищете узкое место производительности, совсем не там.


Напоминает мне людей, которые говорили, что IntStream.range(0, 100).forEach(System.out::println) ужасно тормозит по сравнению с for(int i=0; i<100; i++) System.out.println(i); и тоже рассуждали про кучу объектов, про ужасно развесистую реализацию стримов, про то что method-reference разворачивается в класс в рантайме. При этом никого не волнует, что каждый вызов System.out.println на несколько порядков медленнее всего остального (а если он пишет в системную виндовую консоль, то это вообще капец). Просто потому что println — это старый добрый знакомый метод, а стримы — новое, а значит неизвестное и страшное.

UFO just landed and posted this here
Вам бы сперва с JVM поближе познакомиться, прежде чем заниматься «разрешением мифов».
UFO just landed and posted this here
— «A» тормозит.
— Вы уверены?
— Конечно! Когда я писал «B» с использованием «C», а потом перешёл на «D», то стало медленнее. Значит, «A» тормозит.
Ваш комментарий неполный без упоминания что же такого там по-написали…
UFO just landed and posted this here
Каких именно? Циклов по сокетам или циклов по байтам в буфере?
UFO just landed and posted this here
Просто в моем понимании если приложение — «просто прокси» и перекладывает байтики из сокета в сокет, то именно это перекладывание и становится critical path. И его же очень трудно испортить новыми API, просто потому что не лезет оно в эти API :-)

Потому и удивляюсь проседанию производительности…
UFO just landed and posted this here
Зачем вносить изменения, не дающие никаких преимуществ?
Изменения ради изменений?
UFO just landed and posted this here

Если вы работаете в IDEA и она начинает "желтить" с предложением перевести куски кода циклов в Stream API, то вы не будете на это обращать внимание? Я потихоньку за полгода, год почти все циклы конвертнул.

Если IDEA начинает надоедать инспекцией, НЕ указывающей на проблему в коде, я всё взвешу, и, возможно, отключу эту инспекцию для _этого_ проекта. Неиспользование Stream API, очевидно, проблемой не является.

Если инспекция потенциально указывает на баг, то для начала разберусь, баг ли это в самой инспекции или баг в проекте. Затем — либо багрепорт в JB, либо исправление бага в проекте.

IDEA так не делает, не надо инсинуаций. Эта инспекция по умолчанию в режиме information-only (то есть код не жёлтый, но по Alt+Enter фикс доступен). Если вы её сами включили в своём проекте, IDEA не виновата. Если вы не включали и она вам подсвечивает на только что созданном проекте, то, пожалуйста, сообщите нам.

Аллокация объекта в TLAB осуществляется десятком инструкций процессора, почти бесплатно, а в случае скаляризации память в куче вообще не используется. Так что ваш стандартный ответ — это экономия на спичках.
Это с одной стороны. А с другой при большом наплыве даже короткоживущих объектов потребуется более частая аллокация новых TLAB'ов и в итоге более частые вызовы minor GC (пусть и работы им не прибавится). В общем, нет простых ответов, всё надо измерять.
UFO just landed and posted this here
UFO just landed and posted this here
Оптимизация происходит в runtime.

А что, Java какие-то оптимизации делает не в рантайме?

UFO just landed and posted this here

Ну StringBuilder — это не оптимизация, а необходимость, потому что специального байткода для конкатенации строк нет. Раз ее знаете, то я вам говорю: оптимизации делает оптимизирующий JIT-компилятор, который работает в рантайме. Javac ничего не оптимизирует, если не считать минимального фолдинга констант и удаления недостижимых веток в соответствии с JLS.

И к чему нужна была такая вот реализация? Этот вопрос все еще для меня открыт.

Лучше сразу в публичном API сделать оверлоады, а потом дооптимизировать реализацию, если необходимо. Это можно сделать даже в минорных апдейтах, и ваша программа автоматически станет быстрее (даже без перекомпиляции). Ну и байткод короче будет у вас, что иногда полезно.

Как всегда ответы на некоторые из вопросов лежат не в Java а в теории категорий. null не хранится в List потому что null не принадлежит T. А List должен быть функтором. В качестве альтернативы, при необходимости, может быть использовано List<Option>.
Если минусанувшим не лень, объясните за что.
Мне кажется, что я понятно ответил на вопрос: «почему List<T> не может содержать null?».
К сожалению при наборе потерял <T>, возможно это послужило причиной недопонимания.

Вы программируете на Java? В вашем комментарии совсем не та проблематика что на самом деле.

Не думаю, что авторы этой фичи руководствовались теорией категорий. Скорее практическими соображениями, что где null'ы, там баги. Но три минуса вам действительно многовато, разбавил.

По поводу ListN и varargs: если вызывать перегрузки List.of() без varargs, ListN использоваться не будет. Он как раз для случая с заранее неизвестным количеством элементов.


машина (речь больше о компиляторе) глупее человека

Не компилятор дурак, а Java не позволяет выразить, что метод не возвращает управление нормальным образом. В Kotlin, например, это пофиксили типом Nothing.


Почему у меня в списке не может храниться null?

Потому, что null где попало лучше избегать, это известный подход. И конечно же, такое поведение не сюрприз, а написано в документации


Зачем было создавать столько методов?

Чтобы не плодить массивы из varargs на ровном месте. Я не нашёл подтверждения в статье, что это не работает.

static <E> List<E> of(E e1, E e2, E e3) {  
    return new ImmutableCollections.ListN<>(e1, e2, e3);  
}  
Главное, что в пользовательском коде не будет vararg. Добавить классы List4-List10 хоть в следующем обновлении JDK — не проблема. Если бы не было этих методов, то от использования vararg можно было бы избавиться только перекомпиляцией пользовательского кода.
Если это ещё не сделали — значит, это не так уж приоритетно и влияет на производительность.
что метод не возвращает управление нормальным образом.

В данном случае это не применимо. Метод checkIndex может вернуть управление нормальным образом в зависимости от параметров. Если ему передавать вторым параметром 0, тогда не может.

А что я тут ещё заметил: для списков разной длины возвращается свой класс. Это плохо, потом будет какой-то код, написанный для List<T> получать всё это многообразие разных реализаций и нарвёмся мы на мегаморфные вызовы, что сразу убьёт все полезные оптимизации.

Sign up to leave a comment.

Articles