Comments 45
Какие есть виды GC и в чем их отличие
очень важный вопрос для разработчика, особенно уровня senior.
лично Вы сколько раз за свою карьеру задумывались, какой же gc используется в Вашем проекте?
нисколько?
Наверное как и большинство разработчиков, пока не будет с ними проблем - никто не задумывается)))
Можно узнать, какой примерно процент ваканский, где нужно модифицировать gc? Ну или юзкейс хотя бы. На мой взгляд, если до этого дошло, не лучше ли использовать другие языки?
Примерно 0 в моей практике)) обычно ты приходишь и за тебя уже все настроено, а именно выбор GC стоит очень редко, обычно дефолтный покрывает все, что нужно, за редким исключением
Опции gc модифицировать часто приходилось, иногда менять тип gc.
Пока не покажут пальцем на GC паузы :). Сейчас то хорошо, а раньше CMS (и что было до него) иногда давал прикурить, порождая такие артефакты в кодовой базе, как самописный пул объектов.
Спасибо за материал, кратко и со вкусом!
На мой взгляд в шпаргалках (и видимо вопросах на собесах) не хватает чего-то наподобие "Как вы будете разбираться с приложением, которое упало по OOM", "Как вы будете выяснять, почему приложение иногда перестаёт отвечать в рамках SLA (а потом опять начинает, само да...).
Я про то, что JMC, VisualVM, Profiler в IDEA и это вот наше всё :)
Спасибо!
HAVING- работает с результатами агрегатных функций (SUM,COUNT,AVGи т.д.)
Это утверждение не корректно. Оператор SELECT , содержащий функции SUM, COUNT, AVG и т.д., отрабатывает ПОСЛЕ оператораHAVING , т.е. HAVING никак не может работать с РЕЗУЛЬТАТАМИ агрегатных функций. Как раз, наоборот, агрегатные функции работают с результатами работы оператора HAVING.
HAWING - это WHERE для результатов агрегатов. Т.е. отобрать всех клиентов, которые делали больше, чем три покупки за последнюю неделю.
HAVING - это аналог WHERE именно для результата группировки (GROUP BY), а не агрегатов. Да, в HAVING возможно использование результатов агрегатов, но не только их. Но в HAVING могут использоваться не только результаты агрегатов, но и значения столбцов, использованных в группировке (не результаты агрегатов)
Часть про бинарные деревья не очень хорошая. Если нужно только искать и вставлять бинарные деревья никому не нужны — просто используйте хешмап.
Бинарные деревья нужны, когда нужны всякие нестандартные запросы: lower_bound, range, агрегат на отрезке и прочее.
Ну и про сложность операций не очень удачно выразились: нет никакого лучшего и худшего случая. Либо Вы работаете со сбалансированным деревом и тогда сложностьв худшем случае. Либо Вы делаете что-то сильно нетривиальное и сложность операций зависит от контекста.
Спасибо, да, вы правы, я не совсем корректно выразился, спасибо)
Если нужно только искать и вставлять бинарные деревья никому не нужны — просто используйте хешмап.
HashMap имеет сложность вставки O(N) в худшем случае (ресайз).
Так что бинарные деревья могут быть нужны просто ради более-менее предсказуемой скорости вставки. Хотя, если честно, не сказать что это распространенная проблема - HashMap обычно более чем достаточно )
Тоже об этом подумал, но потом понял что это всё ещё недостаточно причина. Можно сделать хешмап со сложностью вставки за— нужно просто делать ресайз постепенно. Да и в целом, для большинства структур с аммортизацией можно сделать структуру без аммортизации с сохранением сложности операций: нужно просто размазать аммортизированную работу по всем операциям.
Собственно, например, в го именно так и устроен хешмап: они в момент ресайза просто создают новый массив, но не удаляют старый. Далее, при поиске они проверяют нет ли двух массивов. Если есть два массива, они мигрируют сколько-то элементов из старого в новый, после чего делают лукап в обоих. Таким образом получается честный
Такая схема сильно замедляет операции с хешмапами, конечно. Но деревья поиска прям очень медленные на самом деле. Поэтому стоит их избегать пока есть возможность.
Нюансов хватает в алгоритмах. И O(1) не всегда быстрее O(log N) или O(N) - вопрос алгоритма и размеров данных.
Та же сортировка - да, сортировка Хоара (quicksort) быстра (O(N * log N)). Но на небольших массивах тот же "пузырек" быстрее (O(N*N)).
А в некоторых случаях сортировать можно и за O(N) - например, подсчитав количество разных элементов (если память позволяет) - хотя и не факт, что это будет быстрее более традиционных алгоритмов )
Так что выбор TreeMap может быть оправданным. Как минимум - когда важен порядок ключей )
Хотя HashMap - крайне универсальное решение, спору нет.
Про константы замечание понятное. Но как раз суть в том, что у хешмапа (даже с учётом онлайн ресайзинга) константы ниже на любых размерах. Разве что может быть проблема с потреблением памяти — но кого оно волнует в жвм :)
"Когда важен порядок ключей" — непонятно что значит. Либо у нас есть какой-то дополнительный запрос, который учитывает порядок ключей (например lower bound) — об этом писал в самом начале.
Либо же нас интересует порядок итерации (например при выводе). Здесь есть странный хак: можно использовать хешмап, а при выводе сначала копировать в массив, а потом массив сортировать. Что удивительно, такой подход чаще будет быстрее чем TreeMap.
Поэтому действительно оправданым TreeMap становится тогда, когда мы на нём начинаем считать всякую статистику, или начинаем строить кастомные структуры данных. А это уже какая-то экзотика для типичного бэка.
Хак не странный, а вполне популярный )
Хотя лично для меня, контр-интуитивно, что отдельная сортировка будет быстрее. Хотя замеров на этот счет я не делал, да и не интересовался в целом этим нюансом с TreeMap. Надо будет как-нибудь "поиграться" с TreeMap и HashMap )
Тут вся фишка в том, что массивы — это архитектурный костыль с точки зрения CS, но при этом очень хорошо оптимизированный. Поэтому, у нас срабатывает два эффекта:
Сортировка массивом на порядок быстрее сортировки через дерево
Хешмапы на порядок быстрее деревьев, т.к. хешмапы в среднем находят ключ за 2-3 шага, а деревья вполне себе могут и 20 шагов потратить.
В оптимизации констант массивы, кеши и прочее — гораздо больше срабатывает.
просто создают новый массив
Это O(n), если массив не какой-то фиксированной длины.
Если использовать calloc и аналоги, будет. И емнип, в jvm будет именно calloc, а не ручное зануление.
Как оно может быть O(1), если новый массив размера N все равно кто-то должен занулить?
Ну, да, но опять же нет острой необходимости занулять всё и сразу при аллокации. Можно по мере работы с массивом постепенно его занулять.
На практике, плюс-минус везде используются всякие хаки с page fault-ами для того, чтобы лениво занулять страницы данных.
Кажется, на пейдж фолты можно полагаться только при первом выделении памяти, далее же если рантайм не отказывается от переиспользования памяти или не переизобретает виртуальную память внутри себя(ни того, ни другого голанг не делает, насколько я знаю), то переиспользуемую память придется занулять.
Как это сделать без слоя виртульной памяти, лениво, по мере работы, я, признаться, не очень предствляю, потому что состояние занятости каждой ячейки все равно надо где-то трекать, если мы не можем положиться на то, что там будет либо ноль либо указатель.
В общем, я могу что-то упускать, но у меня есть большие сомнения в том, что в голанге есть гарантированные O(1) вместо O(тоже-N-но-не-такое-большое-как-с-полным-разовым-перехешированием).
Хотя вот сейас подумал, технически же память можно инициализировать не в момент выделения, а "на всякий случай" занулять в момент удаления/сборки мусора.
Хотя это кажется контр-продуктивным, поскольку это лишние операции записи, но, кажется, конкретно в данном случае это можно бы сработать.
Если говорить конкретно про го, то логика у них примерно такая (только большие аллокации):
Если при аллокации памяти кусок ещё не занулён — зануляем его ручками, целиком
Но, там, вроде бы, есть фоновое зануление памяти, если есть лишние вычислительные ресурсы.
Если же говорить про "а как это вообще можно реализовать", то дело тоже не шибко хитрое. Опишу принцип, без какой либо оптимизации:
В каждый момент времени у нас есть два массива: текущий и следующий
В момент, когда наш текущий массив достиг заполненности r, мы аллоцируем новый массив, но не зануляем его
Дальше, при каждой выставке в текущий массив, мы зануляем R элементов в новом массиве (просто помним сколько мы уже занулили, сколько ещё нужно занулить)
Когда мы достигнет заполненности m, нам нужно переместить элементы в новый массив. Делать мы это будем лениво.
Соответственно теперь при каждой вставке мы будем перемещать по M элементов в новый массив
Когда мы достигнем заполненности x, мы просто сделаем новый массив текущим. Ожидается, что к этому моменту все элементы уже были перемещены.
В целом, нетрудно подобрать такие r, m и такие R, M, что каждый из этапов гарантированно будет успевать закончиться. Например: r=4/8, m=5/8, x=6/8, R=16, M=6, фактор роста массива в 2 раза. В такой схеме на каждой операции будет выполняться O(1) действий (по модулю коллизий и стоимости malloc).
Но, честно говоря, в тот момент, когда паузы от реллокаций станут действительно существенными, я бы просто перешёл на запрос сырой памяти от ОС (т.к. это будет проще и быстрее).
Подскажите пожалуйста, здесь
Затем выполняется конструктор
Second()
this.speed = 5→ speed становится 5
вместо speed имелось ввиду count или в java есть предопределеные именованные переменные?
1-4 выкинуть
5 и 6 заменить на вопрос/задачу по решению наиболее распространённых проблем с памятью в приложении
7 и 8 заменить на вопрос/задачу по решению наиболее распространённых проблем с многопоточностью в приложении
9 и 10 заменить на что-то, что не решит LLM за 5 секунд (напр. рассказать историю про опыт сложной работы с БД)
11 заменить на реальный пример
12 выкинуть (ну или заменить на пример из жизни)
в 14 поменять "самый любимый паттерн и как вы его применяли" => "самый сложный паттерн и как вы его применяли"
15 заменить на вопрос, с какими типами БД работали и как
во имя богов, добавить хоть что-нибудь на умение проектировать на уровне приложения, помимо "любимого паттерна"
добавить что-то на проверку, умеет ли человек реализовывать сложные бизнес-задачи, рефакторить, пользоваться инструментами, делать ревью, работать с изменениями и конфликтами, писать тесты, документацию, делать интеграцию, конфигурирование, логирование, обработку ошибок, знает ли нужные библиотеки, может ли поддерживать свою развёрнутую систему, хорошо ли разбирается в своей предметной области, ..., в общем, умеет ли он вообще работать или только знает контракт между Equals и HashCode.
Если это был собес там, куда устроились - передайте, что могут меня не благодарить. Если нет - ну и хурма с ними.
Все собесы разные, у меня был такой, бывают, конечно, собесы гораздо сложнее.
Зачастую все основные вопросы и понимание у собеседуего приходит на систем дизайне, как по мне, там и доп вопросы и почему так и зачем, вот там и раскрывается человек)
Я же рассказал свой реальный опыт)
Систем дизайн для сеньора считаю не нужным, хотя везде и спрашивают его сейчас зачем-то.
За опыт спасибо, но было бы полезней, если бы у вас не было всё обезличенным: "какой-то банк", "где-то был собес".
Берите пример с https://habr.com/ru/articles/926214/.
Я читал все "шпаргалки", и, конечно же, никаким Senior developer в банке автор не является, тянет на студента или недавнего студента.
По этой статье:
содержимое раздела "Знание контракта между Equals и HashCode" совершенно не относится к контракту между equals() и hashCode().
SQL-задачка. Почему product - это заказ клиента? Нафига group by содержит c.id ?
Задачка на System design - кошмарный overengineering.
Ваше мнение
Если будут одинаковые имена, то группировка по айди не даст 1 запись, а несколько, по каждому клиенту)
Мне доказывать нечего, да и не нужно, я просто делюсь своим опытом))
Вам же спасибо, что следите за мной, мне очень приятно)))
никаким Senior developer в банке автор не является, тянет на студента или недавнего студента.
А это вы с точки зрения как должно бы быть или с точки зрения текущих (увы) реалий?
Без обид :) Bucket != Basket.
Что-то я не могу найти где это в статье, вроде и прошелся еще раз))
HashMap — это массив корзин.
HashMap — это массив "вёдер". :)
"Ведро" - это коряво, конечно, но очень часто слышу, когда их называют "баскетами".
Возможно, это субъективный мой "пунктик" и я зря цепляюсь.
А у нас всегда называли так, у всех свое, видимо))
В русскоязычном сообществе устоявшийся перевод - "корзин". Это не имеет отношения к bucket vs basket
К устоявшемуся переводу претензий нет, но слово "bucket" - официальное.
(Просто поясняю свою позицию, не хочу пускаться в споры)
По system design я бы первым делом подумал бы про кеширование и его прогрев (или репликации внешней бд на своей стороне) . А если так нельзя, то уже в сторону асинхронного взаимодействия и т д.
Как я проходил собеседование на Senior Java