Улучшения производительности в C ++ 0x - PullRequest
9 голосов
/ 10 июня 2009

Одним из улучшений C ++ 0x, которые позволят писать более эффективный код C ++, является умный указатель unique_ptr (очень плохо, что он не позволит перемещаться по операциям, подобным memmove (): предложение не было включено в проект).

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

vector<char *> v(10,"astring");
string concat = accumulate(v.begin(),v.end(), string(""));

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

Предоставляет ли C ++ 0x инструменты для решения проблемы и, возможно, другие?

Ответы [ 4 ]

13 голосов
/ 10 июня 2009

Да, C ++ решает проблему с помощью так называемой семантики перемещения .

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

Это сделано путем введения конструктора перемещения . Это конструктор, в котором вы знаете, что объект src является временным и исчезает. Поэтому для получателя приемлемо принять внутреннее представление объекта src.

То же самое верно для операторов присваивания перемещения .

Чтобы отличить конструктор копирования от конструктора перемещения, язык ввел ссылки на значения . Класс определяет свой конструктор перемещения для получения ссылки на rvalue , которая будет привязана только к rvalue (временным значениям). Так что мой класс определил бы что-то вроде:

 class CMyString
 {
 private:
     char* rawStr;
 public:

     // move constructor bound to rvalues
     CMyString(CMyString&& srcStr) 
     {
         rawStr = srcStr.rawStr
         srcStr.rawStr = NULL;             
     }

     // move assignment operator 
     CMyString& operator=(CMyString&& srcStr) 
     {
         if(rawStr != srcStr.rawStr) // protect against self assignment
         {
             delete[] rawStr;
             rawStr = srcStr.rawStr
             srcStr.rawStr = NULL;
         }
         return *this;
     }

     ~CMyString()
     {
         delete [] rawStr;
     }
 }

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

7 голосов
/ 10 июня 2009

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

constexpr int returnSomething() {return 40;}

int avalue[returnSomething() + 2]; 

Это недопустимый код C ++, потому что returnSomething () + 2 не является константным выражением.

Но используя ключевое слово constexpr, C ++ 0x может сообщить компилятору, что выражение является константой времени компиляции.

1 голос
/ 10 июня 2009

Извините - вы не можете утверждать, что string concat = accumulate(v.begin(),v.end(), string("")); должен перераспределить. Прямая реализация будет, конечно. Но компиляторам очень здесь разрешено делать правильные вещи.

Это уже имеет место в C ++ 98, и C ++ 0x продолжает разрешать как умные, так и глупые реализации. Тем не менее, семантика перемещения упростит интеллектуальные реализации.

0 голосов
/ 09 сентября 2013
vector<string> v(10, "foo");
string concat = accumulate(v.begin(), v.end(), string(""));

Этот пример - просто плохое программирование в любом стандарте C ++. Это эквивалентно этому:

string tmp;
tmp = tmp + "foo"; //copy tmp, append "foo", then copy the result back into tmp
tmp = tmp + "foo"; //copy tmp, append "foo", then copy the result back into tmp
tmp = tmp + "foo"; //copy tmp, append "foo", then copy the result back into tmp
tmp = tmp + "foo"; //copy tmp, append "foo", then copy the result back into tmp
tmp = tmp + "foo"; //copy tmp, append "foo", then copy the result back into tmp
tmp = tmp + "foo"; //copy tmp, append "foo", then copy the result back into tmp
tmp = tmp + "foo"; //copy tmp, append "foo", then copy the result back into tmp
tmp = tmp + "foo"; //copy tmp, append "foo", then copy the result back into tmp
tmp = tmp + "foo"; //copy tmp, append "foo", then copy the result back into tmp
tmp = tmp + "foo"; //copy tmp, append "foo", then copy the result back into tmp

Семантика перемещения C ++ 11 будет заботиться только о части «скопировать результат обратно в tmp» уравнения. Начальные копии из tmp по-прежнему будут копиями. Это классический алгоритм Шлемеля-живописца , но даже хуже, чем обычный пример, использующий strcat в C.

Если бы accumulate просто использовал += вместо + и =, он бы избежал всех этих копий.

Но C ++ 11 дает нам способ добиться большего успеха, оставаясь лаконичным, используя лямбда-функцию:

string concat;
for_each(v.begin(), v.end(), [&](const string &s){ concat += s; });

РЕДАКТИРОВАТЬ: Я полагаю, что разработчик стандартной библиотеки мог бы реализовать accumulate с перемещением операнда на +, поэтому tmp = tmp + "foo" станет tmp = move(tmp) + "foo", и это в значительной степени решит эту проблему. Я не уверен, будет ли такая реализация строго соответствовать. Ни GCC, ни MSVC, ни LLVM в настоящее время не делают этого. И поскольку accumulate определено в <numeric>, можно предположить, что оно предназначено только для использования с числовыми типами.

...