LINUX.ORG.RU

Ranged for vs Indexed for

 


1

5

Что в должно быть быстрее на arm для контейнера std::vector<T>?

Ranged for:

std::vector<SomeStruct> vec;
for (const auto& v : vec)
{
  // do something with v
}


Indexed for:
std::vector<SomeStruct> vec;
for (size_t i = 0, size = vec.size(); i < size; i++)
{
  const auto& v = vec[i];
  // do something with v
}


Мои синтетические замеры дают приблизительно одинаковые результаты. Профилирование под xcode показывает странные результаты - indexed for получается иногда сильно быстрее, чем range for.
Самостоятельно выводы сделать не получается, надеюсь на коллективный разум ЛОРа.

★★★★★
Ответ на: комментарий от i-rinat

Этот снипет кода был ответом на вопрос «Зачем вручную выполнять его работу?».

Вы же дали комментарии «Какой замечательный выстрел в ногу.» и «О том, что так код писать не надо.», но так и не пояснили, где там выстрел в ногу. Позже вы уже забыли о «выстреле в ногу» и сказали, что «Он сложный.».
Ваши виляния мне напомнили народную сказку «барин-кузнец».

andreyu ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Я знаю, что работодателям-ламеркам всё равно насколько аккуратно написан код :-)

Тут я с вами соглашусь. Нанимателю зачастую нужен результат. А результатом редко является чистота кода. Чаще показательным для них результатом является «сколько денег принесет этот проект».

andreyu ★★★★★
() автор топика
Ответ на: комментарий от andreyu

Вы же дали комментарии «Какой замечательный выстрел в ногу.»

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

Потом посмотрел внимательнее, вроде корректный. Не отменяет того, что код плохой. Мне что, нужно очевидные вещи писать о том, что код, оказывается, корректен? Тогда жду от тебя признания очевидного факта неспособности читать код. Даже свой.

«О том, что так код писать не надо.»

А этот пункт остаётся. Ты и сам это доказал, запутавшись в собственном коде.

Ваши виляния

Это кто виляет-то? Ты написал код на пяток строк, в котором сам запутался. А гонору-то, гонору.

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

i-rinat ★★★★★
()
Ответ на: комментарий от anonymous

Никаких экзотических условий выполнения такой примитивной функции, как strlen() нет

обычно я советую таким как ты начинать с питона. лол

MyTrooName ★★★★★
()
Ответ на: комментарий от MyTrooName

обычно я советую таким как ты начинать с питона. лол

Очень важное обычное заявление :-) Держи нас в курсе :-)

anonymous
()

Второй пример без оптимизации компилятором(которой может и не быть) вообще медленее

Надо тогда уж

size_t size = vec.size();
for(size_t i = 0; i < size; ++)

Вообще ranged for определен как конструкция с поведением эквивалентным


{
auto && __range = range_expression ; 
for (auto __begin = begin_expr, __end = end_expr; 
__begin != __end; ++__begin) { 
range_declaration = *__begin; 
loop_statement 
} 
} 

По факту обычный фор на итераторах. И разницы с индексированым быть не должно.

Dudraug ★★★★★
()
Ответ на: комментарий от eao197

в некоторых версиях STL различаются реализации

Прям реализации? O_o Я думал там разница просто из-за того что умный компилятор все оптимизирует и выдает асм выхлоп без ненужного оверхэда.

Dudraug ★★★★★
()
Ответ на: комментарий от anonymous

цикл for применяется там, где число итераций заведомо известно :-) Если это не так, то нужен цикл while :-)

Дело говорит.

Dudraug ★★★★★
()
Ответ на: комментарий от i-rinat

Тогда жду от тебя признания очевидного факта неспособности читать код. Даже свой.

Вы о моем косяке vec.clear()? Признаю, мой косяк.

Ты и сам это доказал, запутавшись в собственном коде.

Запутался в вырезке из кода. К плохому или хорошему коду это не имеет никакого отношения.

Это кто виляет-то?

Вы. И в доказательство этого я привел последовательность ваших виляний.

Ты написал код на пяток строк, в котором сам запутался.

Я дал ответ и на этот вопрос.

А гонору-то, гонору.

Да, ваше утверждение о плохом коде говорит о вашем гоноре.

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

Вы как и тот аноним, не умеющий читать тему топика? Я в своем сообщении четко написал, что меня интересует и в чем я просил разум ЛОРа помочь разобраться.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от Dudraug

Надо тогда уж

size_t size = vec.size();
for(size_t i = 0; i < size; ++)



Зачем нужно повышать видимость size без необходимости?

Вообще ranged for определен как конструкция с поведением эквивалентным

Да, только operator!= и префиксный operator++ имеют свои накладные расходы в сравнении со сравнением и инкрементом size_t. На это указывает и профайлер из xcode.

По факту обычный фор на итераторах. И разницы с индексированым быть не должно.

Вчера я провел еще тесты и indexed for получился быстрее ranged for. Но я все равно не уверен, что это правильно. Возможно это стечение обстоятельств или некая «особенность процессора» трестируемого девайса.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от Dudraug

цикл for применяется там, где число итераций заведомо известно :-) Если это не так, то нужен цикл while :-)

Дело говорит.

Нет, рассказывает о своем любимом стиле, выдавая его за истину.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от a1batross

Одинаково. Так что тут дело вкуса.

Тут приводили примеры, результаты которых показывали и одинаковые результаты, и разные.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от andreyu

Да, только operator!= и префиксный operator++ имеют свои накладные расходы в сравнении со сравнением и инкрементом size_t. На это указывает и профайлер из xcode.

Вот только size_t требует дополнительно вычислять адрес и оперировать двумя сущностями - индекс + указатель, когда для итератора всегда на руках один только указатель.

anonymous
()
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от anonymous

Вот только size_t требует дополнительно вычислять адрес и оперировать двумя сущностями - индекс + указатель, когда для итератора всегда на руках один только указатель.

Нет, size_t этого не требует. Этого требует доступ к элементу вектора.

Практика показывает, что не все так однозначно.

Ranged for: operator++ (или адресная арифметика), operator!=.
Indexed for: адресная арифметика.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от Dudraug
for (size_t i = 0, size = vec.size(); i < size; i++)

Второй пример без оптимизации компилятором(которой может и не быть) вообще медленее

Надо тогда уж

size_t size = vec.size();
for(size_t i = 0; i < size; ++)

А какая разница, вынесена инициализация size из цикла или нет? Ведь в for по-любому все инициализации до первой точки с запятой происходят один раз?

aureliano15 ★★
()
Ответ на: комментарий от andreyu

Нет, рассказывает о своем любимом стиле, выдавая его за истину.

Так поступают все опытные специалисты :-) Неопытные это слушают и получают хороший опыт :-)

Вы же лишь занимаете оборонительную позицию :-) Весь трэд вы пытаетесь оправдать свой for, но с вами никто не согласен :-) Поэтому кто и «рассказывает о своём любимом стиле, выдавая его за истину», так это вы :-) Только ваши позиции слабее :-) Лол :-)

anonymous
()
Ответ на: комментарий от anonymous

И по факту, если компилятор адекватный, то разницы не будет вообще,

А что такое адекватный компилятор? В природе такой существует?

разница может быть из-за отсутствия оптимизации

Этот вариант нет смысла рассматривать, т.к. речь о релизной сборке.

или слишком малого времени для замеров.

Речь не о длительности замера, а количестве итераций.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от andreyu

Нет, size_t этого не требует. Этого требует доступ к элементу вектора.

Это уже придирка к словам, мы же про итерацию по элементам говорим.

Практика показывает, что не все так однозначно.

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

anonymous
()
Ответ на: комментарий от andreyu

Речь не о длительности замера, а количестве итераций.

На таком примере одного без другого не будет. Кстати, я сейчас как раз под маком - прогоню у себя.

anonymous
()
Ответ на: комментарий от aureliano15

А какая разница, вынесена инициализация size из цикла или нет? Ведь в for по-любому все инициализации до первой точки с запятой происходят один раз?

Разная область видимости. Вы предлагаете расширить ее без необходимости - это какой-то си-стайл. Я же предпочитаю сокращать область видимости, если в ее повышении нет необходимости.

И я вполне допускаю увеличение области видимости переменной в подобных случаях:

const size_t size = vec.size();
if (size == whatever_size)
{
}

for (size_t i = 0; i < size; i++)
{
}

andreyu ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Так поступают все опытные специалисты :-) Неопытные это слушают и получают хороший опыт :-)

Вы каждый раз занимаетесь самохвальством. На работе вас не ценят?

Вы же лишь занимаете оборонительную позицию :-)

Странно, правда?

Весь трэд вы пытаетесь оправдать свой for, но с вами никто не согласен :-)

Весь тред я пытаюсь донести до вас, что тема топика совсем о другом.

Поэтому кто и «рассказывает о своём любимом стиле, выдавая его за истину», так это вы :-) Только ваши позиции слабее :-) Лол :-)

Где вы прочли подобное утверждение?

andreyu ★★★★★
() автор топика
Ответ на: комментарий от andreyu

А какая разница, вынесена инициализация size из цикла или нет? Ведь в for по-любому все инициализации до первой точки с запятой происходят один раз?

Разная область видимости.

Это понятно. Я спрашивал, какая разница в плане производительности, отвечая на цитату:

Второй пример без оптимизации компилятором(которой может и не быть) вообще медленее Надо тогда уж

Вы предлагаете расширить ее без необходимости

Нет, это не я предложил. :-)

aureliano15 ★★
()
Ответ на: комментарий от anonymous

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

Спасибо, Кэп. Попробуйте перечитать мое исходное сообщение.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от anonymous

На таком примере одного без другого не будет. Кстати, я сейчас как раз под маком - прогоню у себя.

Спасибо, но речь об arm, а не x86_64. Так что замеры делайте на каком-либо apple tv или iphone/ipad.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от aureliano15

Я спрашивал, какая разница в плане производительности, отвечая на цитату:

В плане производительности, думаю, никакой.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от andreyu

Вы каждый раз занимаетесь самохвальством.

Если кто-то заявляет о своём реальном опыте, то он самохвал? :-) Нет, он просто констатирует факт :-)

На работе вас не ценят?

У меня нет комплексов :-) Заявить что я - опытный программист, мне ничего не стоит, и я этого не стесняюсь, в отличии от :-) Лол :-)

Странно, правда?

Правда странно :-) Вам несколько программистов указало, что вы пишете трудновоспринимаемую тарабарщину :-) Но вы предпочитаете оправдываться, вместо того, чтобы просто принять к сведению мнение коллег :-) Странно, правда :-)

Весь тред я пытаюсь донести до вас, что тема топика совсем о другом.

Тема топика бессмысленна :-) Вы ничего не выиграете, даже если профайлер Xcode замеряет вам выигрыш for по индексам :-) Это пустая трата времени :-)

Где вы прочли подобное утверждение?

А кто защищает свой for? :-) Не я же :-)

anonymous
()
Ответ на: комментарий от andreyu

Спасибо, Кэп. Попробуйте перечитать мое исходное сообщение.

#include <chrono>
#include <iostream>
#include <vector>
using namespace std;
using namespace std::chrono;

struct my_struct {
    long a_;
    long b_;
    string c;
};

int main()
{
    std::vector<my_struct> v { 16384 };

    for( size_t i = 0 ; i < 16 * 1024 * 1024 ; ++i )
        v.push_back( { random(), random(), "" } );

    {
        auto t = high_resolution_clock::now();

        int s = 0;
        for( const auto& e : v )
            s += e.a_ + e.b_;

        auto duration = duration_cast<microseconds>( high_resolution_clock::now() - t ).count();
        cout << s << ' ' << duration << endl;
    }

    {
        auto t = high_resolution_clock::now();

        const size_t count = v.size();
        int s = 0;
        for( size_t i = 0 ; i < count ; ++i )
        {
            const auto& e = v[ i ];
            s += e.a_ + e.b_;
        }

        auto duration = duration_cast<microseconds>( high_resolution_clock::now() - t ).count();
        cout << s << ' ' << duration << endl;
    }
}

В среднем по времени - практически одно и тоже. Проверял clang 6 на макоси с -O2.

anonymous
()
Ответ на: комментарий от andreyu

Спасибо, но речь об arm, а не x86_64. Так что замеры делайте на каком-либо apple tv или iphone/ipad.

(осторожно) А ты уверен, что ты профилировал именно arm выхлоп?

anonymous
()
Ответ на: комментарий от aureliano15

Ну это дело вкуса, я просто предложил не вызывать .size() на каждой итерации. Остальное уже стилистика, не более.

Dudraug ★★★★★
()
Ответ на: комментарий от anonymous

Если кто-то заявляет о своём реальном опыте, то он самохвал? :-) Нет, он просто констатирует факт :-)

Если он заявляет о своем опыте без доказательств, да еще мимо темы, то он самохвал.

У меня нет комплексов :-)

Ваши комментарии говорят об обратном.

Заявить что я - опытный программист, мне ничего не стоит,

Да, это я вижу.

и я этого не стесняюсь, в отличии от :-) Лол :-)

На то вы и аноним, что бы не стесняться.

Вам несколько программистов указало, что вы пишете трудновоспринимаемую тарабарщину

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

Тема топика бессмысленна :-)

Очевидно, что она бессмысленна для вас.

Вы ничего не выиграете, даже если профайлер Xcode замеряет вам выигрыш for по индексам :-) Это пустая трата времени :-)

Ну как скажете.

А кто защищает свой for?

Перечитайте стартовое сообщение еще раз.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Попробуйте перечитать мое исходное сообщение.
Проверял clang 6 на макоси с -O2.

Спасибо, но в моем исходном сообщении первой же строкой написано «Что в должно быть быстрее на arm для контейнера std::vector<T>?». Обратите внимание на слово arm.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от anonymous

А ты уверен, что ты профилировал именно arm выхлоп?

Я еще не научился запускать x86_64 код на arm-девайсе.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от Dudraug

Ну это дело вкуса, я просто предложил не вызывать .size() на каждой итерации.

Нет, вы предложили повысить область видимости size.

В примере ниже, std::vector<T>.size() вызывается только один раз, а область видимости ограничена циклом:

for (size_t i = 0, size = vec.size(); i < size; i++)
{
}

andreyu ★★★★★
() автор топика
Ответ на: комментарий от andreyu

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

Тарабарщиной воспринимается изменение инварианта цикла for в нескольких местах :-) Вы же знаете что такое инвариант цикла? :-) Вот и подумайте, как можно лучше переписать вашу тарабарщину :-)

anonymous
()
Ответ на: комментарий от andreyu

Я еще не научился запускать x86_64 код на arm-девайсе.

А под arm-девайсом ты понимаешь реальный девайс или симулятор? Что-то мне не верится, что ты заливал бинарник на какой-нибудь iPhone и реально там запускал.

anonymous
()
Ответ на: комментарий от andreyu

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

f1u77y ★★★★
()
Ответ на: комментарий от andreyu

Обратите внимание на слово arm.

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

А можно самому попробовать замерить время. На архитектуре Intel для этого есть ассемблерная команда rdtsc. Сейчас посмотрел, есть ли что-то аналогичное на arm, и если верить этой статье, то есть:

ARM. В целом наличие средств измерения времени сильно зависит от выбранного семейства. На архитектуре ARM11 регистр CCNT может быть использован для чтения текущего номера такта; однако ширина его всего 32 бита, что означает переполнение примерно каждые 10 секунд на системе с частотой в 400 МГц. На системах Cortex M3 присутствует устройство Systick шириной 24 бита, а скорость его изменения специфицируется значением из регистра TENMS.

Преимущество использования подобных команд для подсчёта производительности в том, что

  1. накладные расходы, связанные с подсчётом, минимальны и практически не влияют на результат;
  2. по идее результат такой команды не должен зависеть от случайных обстоятельств, как то переключение между процессами, засыпание и т. д., в отличие от простого измерения временных промежутков через чтение системного времени.
aureliano15 ★★
()
Ответ на: комментарий от f1u77y

Значит вызов std::vector.size() происходит каждую итерацию.

Насколько мне известно, этот вызов в итоге инлайнится в обращение к полю вектора

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

/usr/include/c++/7.3.0/bits/stl_vector.h :

size() const _GLIBCXX_NOEXCEPT
{ return size_type(this->_M_impl._M_finish - this->_M_impl._M_start); }
aureliano15 ★★
()
Ответ на: комментарий от andreyu

Вы о моем косяке vec.clear()?

Нет, я о коде, эквивалентом которому был заявлен vec.clear().

Я дал ответ и на этот вопрос.

Это не вопрос.

Вы. И в доказательство этого я привел последовательность ваших виляний.

Только в твоей голове.

Да, ваше утверждение о плохом коде говорит о вашем гоноре.

Наоборот. В отличие от тебя, я не пытаюсь доказать свою элитарность. Я допускаю ошибки, иногда очень глупые. Поэтому неодобрительно смотрю на подобные трюкачества в коде, о чём и сообщаю.

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

Вы как и тот аноним, не умеющий читать тему топика?

Не виляй. :-)

i-rinat ★★★★★
()
Ответ на: комментарий от andreyu

Это не то, на что стоит полагаться.

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

a1batross ★★★★★
()
Последнее исправление: a1batross (всего исправлений: 1)
Ответ на: комментарий от anonymous

Тарабарщиной воспринимается изменение инварианта цикла for в нескольких местах :-)

Инвариант цикла, с точки зрения цикла, остается неизменным внутри for.

Вот и подумайте, как можно лучше переписать вашу тарабарщину :-)

Ну так предложите свой правильный вариант.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от anonymous

А под arm-девайсом ты понимаешь реальный девайс или симулятор?

Очевидно, что под девайсом я подразумеваю реальный девайс.

Что-то мне не верится, что ты заливал бинарник на какой-нибудь iPhone и реально там запускал.

И что же мне могло помешать запустить приложение на реальном девайсе?

andreyu ★★★★★
() автор топика
Ответ на: комментарий от f1u77y

Насколько мне известно, этот вызов в итоге инлайнится в обращение к полю вектора,

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

что не медленнее, чем обращение к переменной на стеке.

Переменная запросто может оказаться в регистре.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от aureliano15

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

К сожалению, периодически профилировщик странно себя ведет. От обновления к обновлению он то ломается, то его снова чинят. Я не готов утверждать, что в данный момент профилировщик неправ. Но так же я не готов утверждать, что он прав.
Поэтому я провел синтетические тесты. Но, как я сказал в топике, однозначные выводы сделать не смог.

А можно самому попробовать замерить время.

Делал.

На архитектуре Intel для этого есть ассемблерная команда rdtsc.

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

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

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

andreyu ★★★★★
() автор топика
Ответ на: комментарий от a1batross

Если ты хочешь скорости, то наверное есть смысл отказаться от STL.

Чем std::vector<T> хуже T[]?

И велосипеды не нужны. Вон EA имеют свой STL, который как раз старается быть быстрым. (хоть что-то EA хорошее сделали)

Для того были причины.
В моем случае std::vector<T> сам по себе не является узким горлышком.

andreyu ★★★★★
() автор топика
Ответ на: комментарий от i-rinat

Только в твоей голове.

Окей.

Поэтому неодобрительно смотрю на подобные трюкачества в коде, о чём и сообщаю.

Так код ядра линукс плохой или хороший?

andreyu ★★★★★
() автор топика
Ответ на: комментарий от andreyu

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

Ну, в данном случае речь не об Intel, а об arm, как я понял. Разумеется, я не готов утверждать, что arm-аналог не врёт. Но если бы передо мной встала такая проблема, то я загуглил бы этот вопрос и, убедившись, что он не врёт, использовал бы его. А если бы убедился в обратном, то продолжил бы чесать репу. :-)

aureliano15 ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.