Как зайти в Даркнет?!
25th January, 01:11
6
0
Как в tkinter из поля ввода Entry получить значение в одну переменную и обновить строку кнопкой, затем получить ещё одно введённое значение и затем сложить их. Ниже пример кода
21st July, 19:00
895
0
Программа, которая создает фейковые сервера в поиске игровых серверов CS 1.6 Steam
21st March, 17:43
948
0
Очень долго работает Update запрос Oracle
27th January, 09:58
914
0
не могу запустить сервер на tomcat HTTP Status 404 – Not Found
21st January, 18:02
906
0
Где можно найти фрилансера для выполнения поступающих задач, на постоянной основе?
2nd December, 09:48
938
0
Разработка мобильной кроссплатформенной военной игры
16th July, 17:57
1724
0
период по дням
25th October, 10:44
3955
0
Пишу скрипты для BAS только на запросах
16th September, 02:42
3720
0
Некорректный скрипт для закрытия блока
14th April, 18:33
4613
0
прокидывать exception в блоках try-catch JAVA
11th March, 21:11
4381
0
Помогите пожалуйста решить задачи
24th November, 23:53
6086
0
Не понимаю почему не открывается детальное описание продукта
11th November, 11:51
4351
0
Нужно решить задачу по программированию на массивы
27th October, 18:01
4396
0
Метода Крамера С++
23rd October, 11:55
4309
0
помогите решить задачу на C++
22nd October, 17:31
4002
0
Помогите решить задачу на python с codeforces
22nd October, 11:11
4492
0
Python с нуля: полное руководство для начинающих
18th June, 13:58
2599
0
Есть ли разница в производительности между i++ и ++i в C++?
[Резюме: используйте ++i , если у вас нет конкретной причины использовать i++ .]
Для C++ ответ немного сложнее.
Если i является простым типом (а не экземпляром класса C++), то ответ, данный для C ("нет, нет разницы в производительности") , имеет место, так как компилятор генерирует код.
Однако если i является экземпляром класса C++, то i++ и ++i вызывают одну из функций operator++ . Вот стандартная пара этих функций:
Foo& Foo::operator++() // called for ++i
{
this->data += 1;
return *this;
}
Foo Foo::operator++(int ignored_dummy_value) // called for i++
{
Foo tmp(*this); // variable "tmp" cannot be optimized away by the compiler
++(*this);
return tmp;
}
Поскольку компилятор не генерирует код, а просто вызывает функцию operator++ , нет никакого способа оптимизировать переменную tmp и связанный с ней конструктор копирования. Если конструктор копирования является дорогостоящим, то это может оказать значительное влияние на производительность.
Да. Есть.
Оператор ++ может быть определен или не определен как функция. Для примитивных типов (int, double,...) операторы встроены, поэтому компилятор, вероятно, сможет оптимизировать ваш код. Но в случае объекта, который определяет оператор++, все обстоит иначе.
Функция operator++(int) должна создать копию. Это происходит потому, что postfix ++ должен возвращать другое значение, чем то, что он содержит: он должен удерживать свое значение в переменной temp, увеличивать его значение и возвращать temp. В случае operator++ (), prefix ++, нет необходимости создавать копию: объект может увеличивать себя, а затем просто возвращать себя.
Вот иллюстрация этого момента:
struct C
{
C& operator++(); // prefix
C operator++(int); // postfix
private:
int i_;
};
C& C::operator++()
{
++i_;
return *this; // self, no copy created
}
C C::operator++(int ignored_dummy_value)
{
C t(*this);
++(*this);
return t; // return a copy
}
Каждый раз, когда вы вызываете operator++(int), вы должны создать копию, и компилятор ничего не может с этим поделать. Когда вам предоставляется выбор, используйте operator++(); таким образом, вы не сохраняете копию. Это может быть существенно в случае многих приращений (большой цикл?) и / или крупные объекты.
Вот эталонный пример для случая, когда операторы инкремента находятся в разных единицах перевода. Компилятор с g++ 4.5.
Пока игнорируйте вопросы стиля
// a.cc
#include <ctime>
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
int main () {
Something s;
for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
std::clock_t a = clock();
for (int i=0; i<1024*1024*30; ++i) ++s;
a = clock() - a;
for (int i=0; i<1024*1024*30; ++i) s++; // warm up
std::clock_t b = clock();
for (int i=0; i<1024*1024*30; ++i) s++;
b = clock() - b;
std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
<< ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
return 0;
}
Приращение O(n)
Тест
// b.cc
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
Something& Something::operator++()
{
for (auto it=data.begin(), end=data.end(); it!=end; ++it)
++*it;
return *this;
}
Something Something::operator++(int)
{
Something ret = *this;
++*this;
return ret;
}
Результаты
Результаты (тайминги в секундах) с g++ 4.5 на виртуальной машине:
Flags (--std=c++0x) ++i i++
-DPACKET_SIZE=50 -O1 1.70 2.39
-DPACKET_SIZE=50 -O3 0.59 1.00
-DPACKET_SIZE=500 -O1 10.51 13.28
-DPACKET_SIZE=500 -O3 4.28 6.82
O (1) приращение
Тест
Давайте теперь возьмем следующий файл:
// c.cc
#include <array>
class Something {
public:
Something& operator++();
Something operator++(int);
private:
std::array<int,PACKET_SIZE> data;
};
Something& Something::operator++()
{
return *this;
}
Something Something::operator++(int)
{
Something ret = *this;
++*this;
return ret;
}
Он ничего не делает в приращении. Это имитирует случай, когда инкрементация имеет постоянную сложность.
Результаты
Результаты сейчас сильно разнятся:
Flags (--std=c++0x) ++i i++
-DPACKET_SIZE=50 -O1 0.05 0.74
-DPACKET_SIZE=50 -O3 0.08 0.97
-DPACKET_SIZE=500 -O1 0.05 2.79
-DPACKET_SIZE=500 -O3 0.08 2.18
-DPACKET_SIZE=5000 -O3 0.07 21.90
Вывод
С точки зрения производительности
Если вам не нужно Предыдущее значение, сделайте привычкой использовать предварительное приращение. Будьте последовательны даже со встроенными типами, вы привыкнете к этому и не рискуете пострадать от ненужной потери производительности, если вы когда-нибудь замените встроенный тип пользовательским типом.
С семантической точки зрения
i++говоритincrement i, I am interested in the previous value, though.++iговоритincrement i, I am interested in the current valueилиincrement i, no interest in the previous value. Опять же, вы привыкнете к этому, даже если сейчас не совсем так.
Кнут.
Преждевременная оптимизация - это корень всех зол. Как и преждевременная пессимизация.
Не совсем правильно говорить, что компилятор не может оптимизировать копию временной переменной в постфиксном случае. Быстрый тест с VC показывает, что он, по крайней мере, может сделать это в определенных случаях.
В следующем примере генерируемый код идентичен для префикса и постфикса, например:
#include <stdio.h>
class Foo
{
public:
Foo() { myData=0; }
Foo(const Foo &rhs) { myData=rhs.myData; }
const Foo& operator++()
{
this->myData++;
return *this;
}
const Foo operator++(int)
{
Foo tmp(*this);
this->myData++;
return tmp;
}
int GetData() { return myData; }
private:
int myData;
};
int main(int argc, char* argv[])
{
Foo testFoo;
int count;
printf("Enter loop count: ");
scanf("%d", &count);
for(int i=0; i<count; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
}
Независимо от того, используете ли вы ++testFoo или testFoo++, вы все равно получите один и тот же результирующий код. На самом деле, не считывая счетчик от пользователя, оптимизатор получил все это до константы. Так вот это:
for(int i=0; i<10; i++)
{
testFoo++;
}
printf("Value: %d\n", testFoo.GetData());
В результате были получены следующие результаты:
00401000 push 0Ah
00401002 push offset string "Value: %d\n" (402104h)
00401007 call dword ptr [__imp__printf (4020A0h)]
Поэтому, хотя постфиксная версия, безусловно, может быть медленнее, вполне возможно, что оптимизатор будет достаточно хорош, чтобы избавиться от временной копии, если вы ее не используете.
В руководстве по стилю Google C++ говорится::
Преинкремент и Прекрементация
Используйте префиксную форму (++i) операторов инкремента и декремента с итераторы и другие объекты шаблона.
Определение: когда переменная увеличивается (++i или i++) или уменьшается (--i или я--) и значение выражения не используется, нужно решить будь то преинкремент (декремент) или постинкремент (декремент).
Плюсы: когда возвращаемое значение игнорируется, форма "pre" (++i) никогда не бывает меньше эффективнее, чем форма "post" (i++), и часто более эффективна. Это происходит потому, что после инкремента (или декремента) требуется копия i, чтобы быть сделано, что является значением выражения. Если i-итератор или другой тип не scalar, копирование которого я мог бы быть дорогим. С тех пор как эти двое типы инкремента ведут себя одинаково, когда значение игнорируется, почему бы и нет просто всегда заранее приращивать?
Минусы: традиция, разработанная в C году, использовать пост-инкремент, когда значение выражения не используется, особенно в циклах for. Некоторые находят пост-инкремент легче читать, так как "subject" (i) предшествует "verb" ( ++ ), как и в английском языке.
Решение: для простых scalar (не объектных) значений нет причин отдавать предпочтение одному из них форма и мы допускаем то и другое. Для итераторов и других типов шаблонов используйте предварительное приращение.
Я хотел бы отметить отличную статью Эндрю Кенига о Code Talk совсем недавно.
В нашей компании также мы используем конвенцию ++iter для обеспечения согласованности и производительности, где это применимо. Но Эндрю поднимает чрезмерно продуманную деталь относительно намерения против производительности. Бывают случаи, когда мы хотим использовать iter++ вместо ++iter.
Итак, сначала решите свое намерение, и если pre или post не имеет значения, то идите с pre, поскольку это будет иметь некоторое преимущество в производительности, избегая создания дополнительного объекта и бросая его.
@Ketan ..
.поднимает излишне просматриваемые детали относительно намерения против производительности. Бывают случаи, когда мы хотим использовать iter++ вместо ++iter.
Очевидно, что post и pre-increment имеют различную семантику, и я уверен, что все согласны с тем, что при использовании результата вы должны использовать соответствующий оператор. Я думаю, что вопрос заключается в том, что нужно делать, когда результат отбрасывается (как в циклах for ). Ответ на этот вопрос (IMHO) состоит в том, что, поскольку соображения производительности в лучшем случае незначительны, вы должны делать то, что более естественно. Для меня ++i более естественно, но мой опыт говорит мне, что я в меньшинстве, и использование i++ вызовет меньше металлических накладных расходов для большинства людей, читающих ваш код.
Ведь именно по этой причине язык не называется " ++C ".[*]
[*] Вставить обязательное обсуждение того, что ++C является более логичным именем.
Марк: просто хотел отметить, что операторы++являются хорошими кандидатами для вставки, и если компилятор решит сделать это, то избыточная копия будет устранена в большинстве случаев. (например, POD типов, которыми обычно являются итераторы.)
Тем не менее, это все еще лучший стиль, чтобы использовать ++iter в большинстве случаев. :-)
Разница в производительности между ++i и i++ будет более очевидной, если рассматривать операторы как функции, возвращающие значения, и как они реализуются. Чтобы было легче понять, что происходит, в следующих примерах кода будет использоваться int , как если бы это было struct .
++i увеличивает переменную, а затем возвращает результат. Это можно сделать на месте и с минимальным временем CPU, требуя во многих случаях только одной строки кода:
int& int::operator++() {
return *this += 1;
}
Но то же самое нельзя сказать о i++ .
Постинкрементирование, i++, часто рассматривается как возвращение исходного значения до инкрементирования. Однако функция может возвращать результат только после его завершения . В результате возникает необходимость создать копию переменной, содержащей исходное значение, увеличить переменную, а затем вернуть копию, содержащую исходное значение:
int int::operator++(int& _Val) {
int _Original = _Val;
_Val += 1;
return _Original;
}
Если нет функциональной разницы между предварительным приращением и последующим приращением, компилятор может выполнить оптимизацию таким образом, чтобы между ними не было разницы в производительности. Однако, если используется составной тип данных, такой как struct или class , конструктор копирования будет вызван при последующем приращении, и при необходимости глубокой копии выполнить эту оптимизацию будет невозможно. Таким образом, предварительное приращение обычно происходит быстрее и требует меньше памяти, чем последующее приращение.
- ++i -быстрее не использовать возвращаемое значение
- i++ - быстрее использовать возвращаемое значение
Если возвращаемое значение не используется, компилятор гарантированно не использует временное значение в случае ++i . Не гарантируется, что он будет быстрее, но гарантируется, что он не будет медленнее.
При использовании возвращаемого значения i++ позволяет процессору протолкнуть оба приращение и левая сторона в трубопроводе, так как они не зависят друг от друга. ++я могу остановить конвейер, потому что процессор не может запустить левую сторону до тех пор, пока операция предварительного инкремента не пройдет весь путь до конца. Опять же, остановка конвейера не гарантируется, так как процессор может найти другие полезные вещи, чтобы вставить в него.
@Mark: я удалил свой предыдущий ответ, потому что он был немного флип, и заслужил понижение только за это. Я действительно думаю, что это хороший вопрос в том смысле, что он спрашивает, что на уме у многих людей.
Обычный ответ заключается в том, что ++i быстрее, чем i++, и, несомненно, это так, но более важный вопрос-"when should you care?"
Если доля времени CPU, затраченного на инкрементирующие итераторы, меньше, чем 10%,, то вам может быть все равно.
Если доля времени CPU, затраченного на инкрементирование итераторов, больше, чем 10%,, вы можете посмотреть, какие операторы выполняют эту итерацию. Посмотрите, можно ли просто увеличивать целые числа, а не использовать итераторы. Скорее всего, вы могли бы, и хотя это может быть в некотором смысле менее желательно, шансы довольно велики, что вы сэкономите практически все время, проведенное в этих итераторах.
Я видел пример, когда приращение итератора потребляло значительно больше 90% времени. В этом случае переход к целочисленному приращению сокращает время выполнения по существу на эту сумму. (т. е. лучше, чем 10-кратное ускорение)
@wilhelmtell
Компилятор может выделить временное значение. Дословно из другого потока:
Компилятору C++ разрешено устранять временные модули на основе стека, даже если это изменяет поведение программы. MSDN ссылка для VC 8:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx
Предполагаемый вопрос был о том, когда результат не используется (это ясно из вопроса для C). Может ли кто-нибудь исправить это, так как вопрос "сообщество wiki"?
О преждевременной оптимизации часто цитируют кнута. - Вот именно. но Дональд Кнут никогда не стал бы защищать этим ужасный кодекс, который вы можете видеть в наши дни. Когда-нибудь видели a = b + c среди Java целых чисел (не int)? Это составляет 3 конверсии бокса/распаковки. Очень важно избегать подобных вещей. И бесполезно писать i++ вместо ++i - это та же самая ошибка. EDIT: как прекрасно выразился фреснель в комментарии, Это можно подвести под "premature optimization is evil, as is premature pessimization".
Даже тот факт, что люди больше привыкли к i++, является неудачным наследием C, вызванным концептуальной ошибкой K&R (если вы следуете аргументу намерения, это логическое заключение; и защита K&R, потому что они K&R, бессмысленна, они велики, но они не велики как разработчики языка; бесчисленные ошибки в дизайне C существуют, начиная от gets() до strcpy(), до strncpy() API (он должен был иметь strlcpy() API с первого дня)).
Кстати, я один из тех, кто недостаточно привык к C++, чтобы найти++, который мне надоедает читать. Тем не менее, я использую это, поскольку признаю, что это правильно.
Время, чтобы предоставить людям драгоценные камни мудрости ;) - есть простой трюк, чтобы заставить C++ postfix increment вести себя примерно так же, как и prefix increment (придумал это для себя, но видел это также и в коде других людей, так что я не одинок).
В принципе, трюк состоит в том, чтобы использовать вспомогательный класс, чтобы отложить инкремент после возврата, и RAII приходит на помощь
#include <iostream>
class Data {
private: class DataIncrementer {
private: Data& _dref;
public: DataIncrementer(Data& d) : _dref(d) {}
public: ~DataIncrementer() {
++_dref;
}
};
private: int _data;
public: Data() : _data{0} {}
public: Data(int d) : _data{d} {}
public: Data(const Data& d) : _data{ d._data } {}
public: Data& operator=(const Data& d) {
_data = d._data;
return *this;
}
public: ~Data() {}
public: Data& operator++() { // prefix
++_data;
return *this;
}
public: Data operator++(int) { // postfix
DataIncrementer t(*this);
return *this;
}
public: operator int() {
return _data;
}
};
int
main() {
Data d(1);
std::cout << d << '\n';
std::cout << ++d << '\n';
std::cout << d++ << '\n';
std::cout << d << '\n';
return 0;
}
Изобретено для некоторых тяжелых пользовательских итераторов кода, и это сокращает время выполнения. Стоимость prefix vs postfix теперь одна ссылка, и если это пользовательский оператор, делающий тяжелые перемещения, префикс и постфикс дали мне одинаковое время выполнения.
Как быстро ;) Если вы хотите, чтобы это был один и тот же расчет для процессора, то просто порядок, в котором это делается, отличается.
Например, следующий код :
#include <stdio.h>
int main()
{
int a = 0;
a++;
int b = 0;
++b;
return 0;
}
Произведите следующее assembly :
0x0000000100000f24 <main+0>: push %rbp 0x0000000100000f25 <main+1>: mov %rsp,%rbp 0x0000000100000f28 <main+4>: movl $0x0,-0x4(%rbp) 0x0000000100000f2f <main+11>: incl -0x4(%rbp) 0x0000000100000f32 <main+14>: movl $0x0,-0x8(%rbp) 0x0000000100000f39 <main+21>: incl -0x8(%rbp) 0x0000000100000f3c <main+24>: mov $0x0,%eax 0x0000000100000f41 <main+29>: leaveq 0x0000000100000f42 <main+30>: retq
Вы видите, что для a++ и b++ это ВКЛ мнемоника, так что это одна и та же операция ;)
++i быстрее, чем i++ , потому что он не возвращает старую копию значения.
Это также более интуитивно понятно:
x = i++; // x contains the old value of i
y = ++i; // y contains the new value of i
Этот пример C печатает "02" вместо "12", который вы могли бы ожидать:
#include <stdio.h>
int main(){
int a = 0;
printf("%d", a++);
printf("%d", ++a);
return 0;
}
#include <iostream>
using namespace std;
int main(){
int a = 0;
cout << a++;
cout << ++a;
return 0;
}