Есть ли какие-либо преимущества использования этой дополнительной переменной в аннотации цикла for?

11

Я нашел следующую аннотацию цикла в большом проекте, над которым я работаю (псевдокод):

var someOtherArray = [];
for (var i = 0, n = array.length; i < n; i++) {
    someOtherArray[i] = modifyObjetFromArray(array[i]);
}

Что привлекло мое внимание, так это дополнительная «n» переменная. Я никогда раньше не видел написанного таким образом.

Очевидно, что в этом сценарии нет причин, по которым этот код не может быть написан следующим образом (к которому я очень привык):

var someOtherArray = [];
for (var i = 0; i < array.length; i++) {
    someOtherArray[i] = modifyObjetFromArray(array[i]);
}

Но это заставило меня задуматься.

Есть ли сценарий, когда написание такого цикла for имело бы смысл? Идея приходит в голову, что длина «массива» может измениться во время выполнения цикла for, но мы не хотим зацикливаться дальше, чем исходный размер, но я не могу представить себе такой сценарий.

Сокращение массива внутри цикла также не имеет особого смысла, потому что мы, вероятно, получим исключение OutOfBoundsException.

Есть ли известный шаблон проектирования, где эта аннотация полезна?

Редактировать Как отмечает @ Jerry101, причина в производительности. Вот ссылка на созданный мной тест производительности: http://jsperf.com/ninforloop . На мой взгляд, разница не достаточно велика, если вы не выполняете итерацию, хотя очень большой массив. Код, из которого я скопировал его, содержал до 20 элементов, так что я думаю, что читаемость в этом случае перевешивает оценку производительности.

Влад Спрейс
источник
В ответ на ваше редактирование: это объектно-ориентированное программирование. Стандартный массив. Длина быстрая и дешевая. Но по какой-то причине реализация .length может измениться на что-то дорогое. Если значение не меняется, не получайте его несколько раз.
Питер Б
Согласен. Полезно знать об этой опции, но я, вероятно, не буду использовать ее очень часто, если не получу аналогичный пример для вашего SQL-запроса. Спасибо »
Влад Спрейс
Дело в том, что если ваш массив состоит из миллиона элементов, вы будете вызывать array.length миллион раз, а не 1 раз, пока значение не изменится. Просить что-то миллион раз и знать, что каждый последующий ответ будет таким же, как и первый ответ ... для меня это звучит немного бессмысленно.
Питер Б
1
Я не считаю, что это значительно затрудняет чтение кода. (Серьезно, если у кого-то есть проблемы с таким простым циклом, они должны быть обеспокоены.) Но: 1) Я был бы ужасно удивлен, если бы после 4 десятилетий языков C и C-потомков, каждый серьезный компилятор уже не делал этого для вас; и 2) это не может быть горячей точкой. Кажется, не стоит заставлять кого-то тратить лишние 5 секунд на его разбор, если только он не внутри библиотеки (например, реализация структуры данных).
Doval,
1
В C # /. NET использование варианта nможет быть медленнее, чем использование варианта, array.Lengthпоскольку JITter может не заметить, что он может устранить проверки границ массива.
CodesInChaos

Ответы:

27

Переменная nгарантирует, что сгенерированный код не извлекает длину массива для каждой итерации.

Это оптимизация, которая может иметь значение во время выполнения в зависимости от используемого языка, является ли массив фактически объектом коллекции или «массивом» JavaScript, и другими деталями оптимизации.

Jerry101
источник
3
Нет, это не так. Выбор длины массива для каждой итерации зависит не только от языка, но и от параметров компилятора и компилятора, а также от того, что делается в цикле (я говорю о C и C ++)
BЈовић
.. И даже в этом случае, я бы лично поставил n-присваивание чуть выше цикла, если бы я действительно этого хотел, так как - как отметил OP - это редкая (YMMV) конструкция для использования, и ее легко пропустить / не понять другим читателям разработки код.
Свап
20
Как написано, он ограничен nциклом, что, как правило, хорошо. Я бы определил это до цикла, только если бы я хотел использовать его позже.
Jerry101
4
@ Jerry101: По-видимому, фрагментом примера является JavaScript, где нет такой вещи как область видимости цикла ...
Берги
1
@ BЈовић: на практике компилятору редко удается доказать, что размер массива не меняется (как правило, это тривиально доказуемо, только если вектор, по которому вы выполняете цикл, является локальным для функции и вы вызываете только встроенные функции в цикле).
Matteo Italia
2

Выборка длины массива может быть «более дорогой», чем фактическое действие, которое вы повторяете.

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

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

Питер Б
источник
Если количество записей меняется, то что? Скажем, было 100 элементов, и пока вы обрабатываете элемент 50, вставляется элемент после # 25. Поэтому, когда вы обрабатываете элемент № 51, он совпадает с № 50, который вы уже обработали, и все идет не так. Так что проблема не в том, что длина меняется, а в том, что она гораздо более распространена.
gnasher729
@ gnasher729 как только вы попадаете в асимметричное поведение, многое может пойти не так. Но есть вещи, которые вы можете сделать против этого, такие как запуск и транзакция sql.
Питер Б
2

Идея приходит в голову, что длина «массива» может измениться во время выполнения цикла for, но мы не хотим зацикливаться дальше, чем исходный размер, но я не могу представить себе такой сценарий.

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

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

guests = [];
for (i = 0; i < invitations.length; ++i) {
    if (! invitations[i].is_declined()) {
        guests.append(invitations[i].recipient())
    }
}
// maybe some other stuff to modify the guestlist
for (i = 0, n = guests.length; i < n; ++i) {
    if (guests[i].has_plus_one()) {
        guests.append(guests[i].get_plus_one());
    }
}

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

На самом деле, я думаю, что самое неправильное в этом коде состоит в том, что членство в guestsмассиве означает разные вещи в разных точках кода. Поэтому я, конечно, не назвал бы это «шаблоном», но я не знаю, что достаточно дефекта, чтобы исключить возможность что-либо делать в этом роде :-)

Стив Джессоп
источник
Массив также может быть изменен другим потоком. Это не позволяет компилятору оптимизировать поиск длины. Таким образом, программист явно говорит, что массив не изменяется даже в других потоках.
Basilevs
0

Если это вообще возможно, вы должны использовать итератор, который пересекает все элементы массива. Вы используете массив просто как последовательность, а не как хранилище с произвольным доступом, поэтому было бы очевидно, что итератор, который просто выполняет последовательный доступ, будет быстрее. Представьте, что вы думаете, что массив на самом деле является двоичным деревом, но кто-то написал код, который определяет «длину» путем подсчета элементов, и код, который обращается к i-му элементу, также путем обхода элемента, каждый в O (n ). Итератор может быть очень быстрым, ваш цикл будет замедленным.

gnasher729
источник