exponenta event banner

Дорогостоящая копия в диапазоне для итерации цикла

Переменная цикла на основе диапазона for цикл копируется из элементов диапазона вместо ссылки, что приводит к неэффективному коду

Описание

Этот дефект возникает, когда переменная цикла основана на диапазоне for цикл копируется из элементов диапазона вместо считывания элементов диапазона по ссылке. Скопируйте элементы диапазона только тогда, когда это необходимо, поскольку их копирование может привести к неэффективному коду. Этот дефект возникает, когда переменная цикла не изменяется и выполняется любое из следующих условий:

  • Скопированная переменная цикла является большой тривиально копируемой переменной типа. Копирование тривиально копируемого объекта дороже, чем ссылка на него, когда объект большой.

  • Скопированная переменная цикла является нетривиально копируемым типом. Копирование такого объекта может потребовать вызова внешней функции, что дороже, чем ссылка на него. Чтобы проверить, является ли объект нетривиально копируемым, используйте функцию std::is_trivially_copyable. Дополнительные сведения об этой функции см. в разделе std::is_trivially_copyable в ссылке C++.

Риск

На основе диапазона for циклы могут стать неэффективными, когда в каждой итерации цикла создается дорогостоящая копия переменной цикла. Рассмотрим этот код:

void foo( std::map<std::string, std::string> const& property_map )
{
    for( std::pair< const std::string, std::string > const property: property_map) 
    {}
}

Переменная цикла property объявлен как const вместо const&. В каждой итерации for цикл, std::pair объект копируется с карты property_maps к переменной цикла property. Из-за пропавшего & в декларации propertдорогостоящая операция копирования выполняется в каждой итерации вместо операции ссылки, что приводит к неэффективному коду. Поскольку этот код компилируется и функционирует правильно, неэффективное for петли могут быть не замечены. Аналогичный источник неэффективности см. в разделе Expensive pass by value и Expensive return by value.

Зафиксировать

Чтобы устранить этот дефект, объявите переменную цикла на основе диапазона for цикл как const&. Рассмотрим этот код:

void foo( std::map<std::string, std::string> const& property_map )
{
    for( std::pair< const std::string, std::string > const& property: property_map) 
    {}
}
Потому что переменная цикла property объявлен как const&, переменная ссылается на другой элемент карты property_map в каждой итерации цикла без копирования какого-либо ресурса. Предотвращая дорогостоящую копию в каждой итерации, код становится более эффективным.

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

Примеры

развернуть все

#include <initializer_list>
#include <unordered_map>
#include <vector>
struct Small_Trivial_Type
{
	unsigned char values[ sizeof( void* ) ];
};

struct Large_Trivial_Type
{
	unsigned char values[ 4u * sizeof( void* ) ];
};

class Nontrivial_Type
{
	Nontrivial_Type() noexcept;
	Nontrivial_Type( Nontrivial_Type const& );
	Nontrivial_Type& operator=( Nontrivial_Type const& );
	~Nontrivial_Type() noexcept;
	int read() const;
	void modify( int );
};
extern std::vector< Nontrivial_Type > getNtts();

void foo( std::vector< Nontrivial_Type > const& ntts )
{
	for( Nontrivial_Type ntt: ntts )
	{}
}

void foo_auto( std::vector< Nontrivial_Type > const& ntts )
{
	for( auto ntt: ntts ) 
	{}
}
void foo_c_array( Nontrivial_Type const ( & ntts )[ 10 ] )
{
	for( Nontrivial_Type ntt: ntts )
	{}
}
void foo_large( std::vector< Large_Trivial_Type > const& ltts )
{
    for( Large_Trivial_Type ltt: ltts ) 
    {}
}
void foo_small( std::vector< Small_Trivial_Type > const& stts )
{
    for( Small_Trivial_Type const stt: stts ) 
    {}
}
void modify_elem( std::vector< Nontrivial_Type > const& ntts )
{
	for( Nontrivial_Type ntt: ntts ) 
	{
		ntt.modify( 42 );//Modification
	}
}

В этом примере диапазон for показаны контуры, имеющие различные типы переменных цикла.

  • Polyspace ® помечает нетривиально копируемую переменную циклаntt в foo() из-за дорогостоящей операции копирования, которая не нужна, поскольку переменная цикла не изменена. По той же причине переменные цикла в foo_auto() и foo_c_array() помечены.

  • Polyspace помечает переменную большого цикла ltt в foo_large() потому что более дорого копировать элементы ltts в ltt чем к ссылочным элементам ltts, хотя ltt является тривиально копируемым типом.

  • Polyspace не помечает переменную цикла stt в foo_small() потому что копирование элементов stts в stt не дороже ссылки на элементы stts.

  • Polyspace не помечает переменную цикла ntt в modify_elem() поскольку переменная цикла изменяется в цикле.

Исправление

Чтобы устранить эту проблему, используйте постоянные ссылки (const&) в качестве переменных цикла в зависимости от диапазона for петли. Используя const& переменные цикла предотвращают дорогостоящее копирование и создают эффективный код.

#include <initializer_list>
#include <unordered_map>
#include <vector>
struct Small_Trivial_Type
{
	unsigned char values[ sizeof( void* ) ];
};

struct Large_Trivial_Type
{
	unsigned char values[ 4u * sizeof( void* ) ];
};

class Nontrivial_Type
{
	Nontrivial_Type() noexcept;
	Nontrivial_Type( Nontrivial_Type const& );
	Nontrivial_Type& operator=( Nontrivial_Type const& );
	~Nontrivial_Type() noexcept;
	int read() const;
	void modify( int );
};
extern std::vector< Nontrivial_Type > getNtts();
// Test iterating over a const vector.
void foo( std::vector< Nontrivial_Type > const& ntts )
{
	for( Nontrivial_Type const& ntt: ntts ) // NC2C
	{}
}

void foo_auto( std::vector< Nontrivial_Type > const& ntts )
{
	for( auto const& ntt: ntts ) //NC2C
	{}
}
void foo_c_array( Nontrivial_Type const ( & ntts )[ 10 ] )
{
	for( Nontrivial_Type const& ntt: ntts ) // NC2C
	{}
}
void foo_large( std::vector< Large_Trivial_Type > const& ltts )
{
	for( Large_Trivial_Type const& ltt: ltts ) 
	{}
}

Информация о результатах

Группа: Производительность
Язык: C++
По умолчанию: Откл.
Синтаксис командной строки: EXPENSIVE_RANGE_BASED_FOR_LOOP_ITERATION
Воздействие: среднее
Представлен в R2020b