Выносить вычисление длины выше цикла уже не нужно?
От: sanx  
Дата: 04.07.11 00:58
Оценка:
Правильно ли я понимаю что все современные компиляторы без проблем оптимизируют код типа:

for(int i = 0; i < s.size; ++i) {

и писать типа

int size = s.size();
for(int i = 0; i < size; ++i) {

уже не нужно? или в зависимости от того чем является s (переменная в примерах выше) — могут быть какие-то проблемы?
Ну и понятное дело, я говорю о ситуации когда длина не меняется в теле for
А есть ли отличия для while?

Звучит вопрос глупо, но вдруг есть какие-то интересные, но не очевидные для меня нюансы.
Re: Выносить вычисление длины выше цикла уже не нужно?
От: Tilir Россия http://tilir.livejournal.com
Дата: 04.07.11 03:52
Оценка: 24 (6) +6 -2
Здравствуйте, sanx, Вы писали:

S>Звучит вопрос глупо, но вдруг есть какие-то интересные, но не очевидные для меня нюансы.


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

Берём сферический случай в вакууме.

void test(void)
{
  int i;
  int size = get_size();
  for(i = 0; i < size; ++i) {
    call_smth();
  }
}

void test2(void)
{
  int i;
  for(i = 0; i < get_size(); ++i) {
    call_smth();
  }
}


Компилируем gcc-4.4.3 на уровне O2 до стадии GIMPLE, смотрим дамп

;; Function test (test)

test ()
{
  int size;
  int i;

<bb 2>:
  size = get_size ();
  if (size > 0)
    goto <bb 3>;
  else
    goto <bb 5>;

<bb 3>:
  i = 0;

<bb 4>:
  call_smth ();
  i = i + 1;
  if (size > i)
    goto <bb 4>;
  else
    goto <bb 5>;

<bb 5>:
  return;

}


Обратите внимание -- вычисление происходит один раз.

;; Function test2 (test2)

test2 ()
{
  int i;
  int D.1607;

<bb 2>:
  i = 0;
  goto <bb 4>;

<bb 3>:
  call_smth ();
  i = i + 1;

<bb 4>:
  D.1607 = get_size ();
  if (i < D.1607)
    goto <bb 3>;
  else
    goto <bb 5>;

<bb 5>:
  return;

}


Обратите внимание -- колбасим каждый цикл.

Сейчас я вам коротко объясню почему это так происходит и, я так полагаю, всегда будет время от времени при определённых условиях происходить на любом оптимизирующем компиляторе. В общем случае -- мы не знаем что делает функция get_size. Она где-то в другой единице линковки и мы не представляем что этой единице линковки происходит. Чёрт, она может быть вообще скомпилирована отдельно! Да, компилятор может иногда смухлевать. Есть межпроцедурные оптимизации. Есть инлайнинг. Например в gcc опция combine вообще заставляет трактовать все входные файлы как один. Но в самом общем случае, компилятор *не понимает*, что семантически get_size вычисляет и возращает каждый раз один и тот же размер. Он не может это доказать и поэтому ведёт себя максимально консервативно, а именно -- даже на высоких уровнях оптимизации делает ровно то, что вы ему сказали.

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

---
With best regards, Konstantin
Re: Выносить вычисление длины выше цикла уже не нужно?
От: _nn_ www.nemerleweb.com
Дата: 04.07.11 06:16
Оценка: 1 (1) +1
Здравствуйте, sanx, Вы писали:

S>Правильно ли я понимаю что все современные компиляторы без проблем оптимизируют код типа:


S>for(int i = 0; i < s.size; ++i) {


S>и писать типа


S>int size = s.size();

S>for(int i = 0; i < size; ++i) {

Зачем переменная size за областью for ?
for (size_t i = 0, size = s.size(); i < size; ++i)
  ...


Точно также и в итераторах
for (Container::iterator it = c.begin(), it_end = c.end(); it != it_end; ++it)
  ...


Но тут уже будет проще Boost.Foreach чем это
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re: Выносить вычисление длины выше цикла уже не нужно?
От: Pavel Dvorkin Россия  
Дата: 04.07.11 06:23
Оценка: 1 (1) +1
Здравствуйте, sanx, Вы писали:

S>Ну и понятное дело, я говорю о ситуации когда длина не меняется в теле for


Это тебе совершенно очевидно, что длина не меняется в теле for. Компилятору это может быть совсем не очевидно, и он только в достаточно простых случаях может определить, что она не меняется.


for(int i = 0; i < s.size; ++i) 
  f(&s);


Поди тут пойми, меняется s.size или нет, особенно если f описана в другом файле или вообще в библиотеке какой-то.
With best regards
Pavel Dvorkin
Re: Выносить вычисление длины выше цикла уже не нужно?
От: Centaur Россия  
Дата: 04.07.11 06:33
Оценка:
Здравствуйте, sanx, Вы писали:

S>Правильно ли я понимаю что все современные компиляторы без проблем оптимизируют код типа:


S>for(int i = 0; i < s.size(); ++i) {


S>и писать типа


S>int size = s.size();

S>for(int i = 0; i < size; ++i) {

S>уже не нужно? или в зависимости от того чем является s (переменная в примерах выше) — могут быть какие-то проблемы?


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

Во-вторых, отсутствие выигрыша достигается не тем, что компилятор автоматически выдёргивает вызов size() из цикла, а тем, что функция size(), как правило, инлайновая и очень простая (доступ к полю объекта, или доступ к двум полям и вычитание указателей).
Re[2]: Выносить вычисление длины выше цикла уже не нужно?
От: minorlogic Украина  
Дата: 04.07.11 06:48
Оценка: +1
Возможно автор подразумевал шаблонную функцию, доступ к реализации которой у компилятора есть.
Ищу работу, 3D, SLAM, computer graphics/vision.
Re: Выносить вычисление длины выше цикла уже не нужно?
От: Don Reba Канада https://stackoverflow.com/users/49329/don-reba
Дата: 04.07.11 06:48
Оценка: 13 (2)
Подробно разобрано тут: http://users.livejournal.com/_winnie/20905.html
Ce n'est que pour vous dire ce que je vous dis.
Re[3]: Выносить вычисление длины выше цикла уже не нужно?
От: IROV..  
Дата: 04.07.11 16:00
Оценка:
Здравствуйте, minorlogic, Вы писали:

M>Возможно автор подразумевал шаблонную функцию, доступ к реализации которой у компилятора есть.

Обязан ли компилятор пересчитывать _End — _Begin?
Допустим может, что произойдет если я из другого потока буду менять этот _End и _Begin?

я не волшебник, я только учусь!
Re[2]: Выносить вычисление длины выше цикла уже не нужно?
От: Erop Россия  
Дата: 04.07.11 18:10
Оценка: 8 (1)
Здравствуйте, Tilir, Вы писали:

T>Именно поэтому когда вы пишете код, который потом будет обрабатываться компилятором -- будьте пожалуйста предельно ясны в выражении своих мыслей. Если результат нужен один раз -- вычислите его, пожалуйста, один раз. У компилятора нет встроенных телепатических средств, которыми он угадывает что делает и что не делает ваш код. Вернее есть, но они работают крайне ненадёжно и в частных случаях.


Согласен, с небольшой оговоркой. Если тебе таки надо проитерировать контейнер, то делай это прямо, так, как это принято делать с такими контейнерами.
Тогда есть большие шансы на то, что авторы библиотеки и компилятора предпримут какие-то усилия и всё таки получится. В отличии от ситуации, когда ты делаешь что-то необычное. Но, пир этом всё равно не стоит надеяться на то, что в компиляторе есть блок телепатии
Ну и не надо надеяться на то, что у того, кто будет этот код поддерживать, тоже есть развитые телепатические способности. А то ведь оптимизация может отвалиться не сразу, а попозже...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: А вот и фиг!
От: Wissenschaftler http://rsdn_user.livejournal.com
Дата: 04.07.11 18:35
Оценка: +1
Здравствуйте, Tilir, Вы писали:

T>Здравствуйте, sanx, Вы писали:


S>>Звучит вопрос глупо, но вдруг есть какие-то интересные, но не очевидные для меня нюансы.


T>Нет, вы неправильно думаете. Это общее заблуждение относительно того что по умолчанию могут, а чего нет современные компиляторы.

Элементарные вещи типа get_size() в большинстве библиотек инлайнятся. Соответственно, компилятор все раскрутит и вынесет.
Запретное обсуждение модерирования RSDN:
http://rsdn-user.livejournal.com/652.html
Re[4]: Выносить вычисление длины выше цикла уже не нужно?
От: Centaur Россия  
Дата: 05.07.11 04:00
Оценка:
Здравствуйте, IROV.., Вы писали:

M>>Возможно автор подразумевал шаблонную функцию, доступ к реализации которой у компилятора есть.

IRO>Обязан ли компилятор пересчитывать _End — _Begin?

Если _End или _Begin отмечены как volatile — обязан прочитать новые значения при очередном вызове size() и, соответственно, вернуть актуальный размер. Иначе — имеет право не пересчитывать, по крайней мере до С++11.
Re: Выносить вычисление длины выше цикла уже не нужно?
От: igna Россия  
Дата: 05.07.11 04:18
Оценка:
Здравствуйте, sanx, Вы писали:

S>for(int i = 0; i < s.size; ++i) {


S>int size = s.size();

S>for(int i = 0; i < size; ++i) {

В первом случае у тебя s.size, во втором — s.size().
Re[2]: Выносить вычисление длины выше цикла уже не нужно?
От: jazzer Россия Skype: enerjazzer
Дата: 05.07.11 05:13
Оценка:
Здравствуйте, Tilir, Вы писали:

T>Сейчас я вам коротко объясню почему это так происходит и, я так полагаю, всегда будет время от времени при определённых условиях происходить на любом оптимизирующем компиляторе. В общем случае -- мы не знаем что делает функция get_size. Она где-то в другой единице линковки и мы не представляем что этой единице линковки происходит. Чёрт, она может быть вообще скомпилирована отдельно! Да, компилятор может иногда смухлевать. Есть межпроцедурные оптимизации. Есть инлайнинг. Например в gcc опция combine вообще заставляет трактовать все входные файлы как один. Но в самом общем случае, компилятор *не понимает*, что семантически get_size вычисляет и возращает каждый раз один и тот же размер. Он не может это доказать и поэтому ведёт себя максимально консервативно, а именно -- даже на высоких уровнях оптимизации делает ровно то, что вы ему сказали.


+1.

Более того, даже если у компилятора есть доступ к реализации get_size() и он может ее заинлайнить, у него может не быть доступа к реализации call_smth(), а даже если он есть, то может не быть доступа к реализации любой из маленьких функций, которые из call_smth() зовутся.
А это значит, что размер контейнера внутри этой неизвестной маленькой функции может измениться и кешировать его нельзя.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: Выносить вычисление длины выше цикла уже не нужно?
От: jazzer Россия Skype: enerjazzer
Дата: 05.07.11 05:14
Оценка: -1
Здравствуйте, minorlogic, Вы писали:

M>Возможно автор подразумевал шаблонную функцию, доступ к реализации которой у компилятора есть.


Не поможет.
доступ должен быть ко всему — и к size, и к телу цикла целиком (со всеми вложенными вызовами), иначе кешировать нельзя.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[4]: Выносить вычисление длины выше цикла уже не нужно?
От: minorlogic Украина  
Дата: 05.07.11 06:52
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Не поможет.

J>доступ должен быть ко всему — и к size, и к телу цикла целиком (со всеми вложенными вызовами), иначе кешировать нельзя.

Да , я про такую ситуацию.
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[3]: Выносить вычисление длины выше цикла уже не нужно?
От: Erop Россия  
Дата: 05.07.11 06:53
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Более того, даже если у компилятора есть доступ к реализации get_size() и он может ее заинлайнить, у него может не быть доступа к реализации call_smth(), а даже если он есть, то может не быть доступа к реализации любой из маленьких функций, которые из call_smth() зовутся.

J>А это значит, что размер контейнера внутри этой неизвестной маленькой функции может измениться и кешировать его нельзя.

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

В любом случае, обычно компилятор формальные проверки делает лучше, чем человек. Так что писать for( int i = 0; i < c.size(); i++) { body(); } НАДЁЖНЕЕ!
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: Выносить вычисление длины выше цикла уже не нужно?
От: Erop Россия  
Дата: 05.07.11 06:54
Оценка:
Здравствуйте, Centaur, Вы писали:


C>Если _End или _Begin отмечены как volatile — обязан прочитать новые значения при очередном вызове size()


Если они так помечены не без оснований, то лучше и не мешать компилятору пересчитывать size() на каждой итерации
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: Выносить вычисление длины выше цикла уже не нужно?
От: jazzer Россия Skype: enerjazzer
Дата: 05.07.11 08:38
Оценка:
Здравствуйте, Erop, Вы писали:

E>В большинстве популярных компиляторов (в обеих трёх) есть возможность указать опцию оптимизации, суть которой состоит в том, что на объект нет альтернативных ссылок. То есть если ты получил сонтейнер по ссылке и никуда дальше его не передаёшь, то компилятор может считать, что никто дальше его не меняет.


Если ты о strict aliasing, то это немного не о том. Там опция компиляции говорит только, что на одну и ту же память нет ссылок _разных_ типов. А у тебя замечательно может быть один и тот же тип и все обломится (в сымсле, ничего оптимизироваться не будет).

E>В любом случае, обычно компилятор формальные проверки делает лучше, чем человек. Так что писать for( int i = 0; i < c.size(); i++) { body(); } НАДЁЖНЕЕ!

Если не волнует скорость — конечно. Пусть всё по 100500 раз надежно позовет.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[5]: Выносить вычисление длины выше цикла уже не нужно?
От: jazzer Россия Skype: enerjazzer
Дата: 05.07.11 08:39
Оценка:
Здравствуйте, minorlogic, Вы писали:

M>Здравствуйте, jazzer, Вы писали:


J>>Не поможет.

J>>доступ должен быть ко всему — и к size, и к телу цикла целиком (со всеми вложенными вызовами), иначе кешировать нельзя.

M>Да , я про такую ситуацию.


Ок. Ну и поскольку гарантировать доступность всего дерева крайне трудно, проще и надежнее закешировать самостоятельно.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[5]: Выносить вычисление длины выше цикла уже не нужно?
От: Erop Россия  
Дата: 05.07.11 09:34
Оценка:
Здравствуйте, jazzer, Вы писали:

J>Если ты о strict aliasing, то это немного не о том. Там опция компиляции говорит только, что на одну и ту же память нет ссылок _разных_ типов. А у тебя замечательно может быть один и тот же тип и все обломится (в сымсле, ничего оптимизироваться не будет).


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

J>Если не волнует скорость — конечно. Пусть всё по 100500 раз надежно позовет.


Всё-таки это надо какой-то мегабыстрый цикл написать, чтобы от того, вынесут .end из цикла или нет что-то зависело
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.