Выбирает ли правильную оптимизацию преждевременной оптимизации типа коллекции?

В последнее время я все чаще сталкиваюсь с этим типом кода:

List<Something> list = new LinkedList<Something>();
// add a bunch of constants to the list
// keep in mind that the number of items added is known on list creation
return list.toArray(new Something[list.size()]);

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

Я попытался поговорить с автором, используя что-то вроде: "Эй, почему бы вам не использовать ArrayList вместо этого, так как это именно то, что вам нужно, потому что его операция toArray - постоянная скорость". Однако, по мнению авторов, это преждевременная оптимизация, и я не должен заботиться об этом.

Я полностью согласен с тем, что оптимизация должна выполняться только при необходимости (и даже редко):), но, на мой взгляд, выбор правильной коллекции для использования не является преждевременной оптимизацией (и это не требует много времени или трудно... используя LinkedList везде просто "плохой код" ИМХО).

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

Ответ 1

В общем, выбор подходящей коллекции - важная часть алгоритмического дизайна. Разница между O(N) и O(NlogN) по сравнению с O(N^2) может определять, будет ли основной алгоритм (и, следовательно, все приложение) жизнеспособным.

Однако выбор коллекции не всегда важен.

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

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

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

Однако. Если бы это был мой код, я бы использовал ArrayList (возможно, с начальной загрузкой) на том основании, что он более изящный (и чуть более читаемый) способ сделать работу.

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

  • критичность проблем с производительностью (когда значительна) - да,
  • ... правильность - да,
  • ... читаемость/ремонтопригодность - да,
  • ... соответствие согласованному/утвержденному стилю - да,
  • ... плохое написание и грамматика - может быть,
  • ... элегантность - нет.

Ответ 2

По-моему, это не преждевременная оптимизация. Выбор правильной коллекции занимает не более 5 секунд, если разработчик знаком с доступными коллекциями. И писать неэффективный код просто плохо. Если случай более сложный, можно легко перейти к одному списку (по умолчанию ArrayList), не задумываясь о всех возможных сценариях. Но для простых таких, как это, выберите нужную коллекцию. Эти 5 секунд сэкономят вас целый день спустя, когда выяснится, что этот метод называется миллионы раз и является узким местом.

Итак, я хочу сказать: "не оптимизируйте преждевременно"!= "пишите дерьмовый код, пока проблема не ударит вас"

Ответ 3

На самом деле, если вы хотите вернуть массив, зачем вообще создавать список посредников? Я нахожу инициализаторы массивов довольно читабельными:

return new Something[] {
    Something.RED,
    Something.BLUE,
    Something.GREEN,
    Something.PURPLE,
    Something.YELLOW
};

Обратите внимание, что мой аргумент касается читаемости, а не производительности. Количество констант конечно, и если предположить, что их тысячи не будут выполнены, этот метод будет выполняться в течение микросекунды, поэтому, если он не будет называться миллионы раз в секунду, он не будет значительно платить за процессор - и даже если этот метод был критическая производительность не исправляется, заменив LinkedList на ArrayList, но сохраняя копию массива вокруг или даже переопределяя контракт метода, поэтому метод может возвращать один и тот же неизменяемый экземпляр каждый раз, а не создавать новый по одному для каждого вызова. Итог: замена LinkedList на ArrayList для этого шаблона использования только улучшит этот метод выполнения с помощью небольшого постоянного коэффициента, который почти никогда не имеет значения.

В качестве альтернативы, если вам нужен Список, вы можете сделать:

return Arrays.asList(
    Something.RED,
    Something.BLUE,
    Something.GREEN,
    Something.PURPLE,
    Something.YELLOW
);

Ответ 4

Существует разница между выбором алгоритмов подбора и структурами данных и попыткой выжать несколько циклов процессора из (суб) программы. Последнее, как правило, нахмуривается, за исключением редких обстоятельств, главным образом потому, что оно поддерживает ремонтопригодность для (как правило) незначительных улучшений производительности. Первые, наоборот, часто дают немного более чистый и более неуверенный код, а также (!) Значительно улучшают производительность, по крайней мере, для большего ввода. Чтобы выбрать более наглядный пример, выполняется много линейных поисков ( "кто-то уже добавил это?" ) Над массивом, а не с помощью набора ok ( "нет преждевременной оптимизации!" ) Или просто глупо? Надеюсь, мы все можем согласиться с тем, что существует гораздо более подходящая структура данных. И, как было сказано ранее, это не только улучшает производительность, но и поддерживает ремонтопригодность: в этом примере использование набора сразу сообщает читателю, что заказ не имеет значения, но членство делает. В вашем коде это меньше проблем, но использование ArrayList уверенности не ухудшает ремонтопригодность. У вас есть фиксированное количество элементов для добавления, и вам в конечном итоге нужно будет превратить его в массив - ArrayList просто является очевидным выбором здесь, и разработчику не придется задаваться вопросом, почему это список.

Ответ 5

Это вопрос вкуса, на самом деле.

В голове этого разработчика я думаю, что идея довольно проста: бросьте на этот интерфейс коллекции любую реализацию коллекции, какую мне нравится, и измените ее только в том случае, если из-за этого кода обнаружено узкое место.

Однако в вашей голове идея больше: поскольку я могу использовать естественно эффективную реализацию этой коллекции, зачем использовать другую?

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

Кроме того, ссылка на "преждевременную оптимизацию" в этом контексте после того, как код был полностью написан, - это ерунда (или, если говорить более вежливо, пассивная агрессивная шутка): время оптимизации теперь, когда код но не один раз, когда QA жалуется на вялое применение.

Ответ 6

В общем случае аргумент о том, лучше ли ArrayList, чем LinkedList, не отвечает, не зная точно, что еще происходит с коллекцией. Пока вы не закончите писать код, который неизвестен.

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

Ответ 7

Вы оба ошибаетесь.

  • Выбор правильного алгоритма заблаговременно является важной частью разработки программы, а не "оптимизацией".

  • Он уже выбрал LinkedList. Он должен что-то выбрать. Принятие этого решения не является "оптимизацией", это является существенным предварительным условием для компиляции программы.

  • ArrayList.toArray(T []) не является и не может быть операцией O (1) с постоянным временем.

Ответ 8

Поскольку мы говорим об оптимизации, есть еще одна оптимизация, которую вы можете сделать:

Вы упомянули в вопросе, что знаете количество элементов, которые будут добавлены в список. Я бы предложил использовать перегруженный конструктор ArrayList и предоставить параметр initialCapacity. Обратите внимание: LinkedList не имеет такого конструктора.

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

Примечание. К сожалению, это, вероятно, относится к комментариям, но у меня еще нет комментариев для комментариев.

Ответ 9

Если вы знаете, что вам нужен массив в конце, и вы заранее знаете количество элементов, почему бы просто не создать массив? Я не вижу, что получается, создавая некоторый промежуточный контейнерный объект. Это даже не вопрос оптимизации, а просто ясность кода.