понедельник, 23 октября 2023 г.

Простенький тест скорости работы shared_ptr vs raw ptr.

 Всегда было интересно, насколько дорого обходятся умные указатели, потому решил написать простенький тест, чтобы проверить насколько все хорошо или плохо. Для сравнение взял три разные структуры: классическиую структуру, структуру с raw-pointers и структуру с shared_ptr.  

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

struct StudentId
{
	char str[20];
};
 
struct StudentMarks
{
	int math;
	int it;
	int lit;
	int pe;
};
 
struct RecommendLetters
{
	int amount;
};
 
// plain struct
struct StudentInfo 
{
	StudentId id;
	StudentMarks marks;
	RecommendLetters letters;
};
 
// raw-pointer struct
struct StudentInfoRP
{
	StudentInfoRP()
	{
		id = new StudentId();
		marks = new StudentMarks();
		letters = new RecommendLetters();
	}
 
	~StudentInfoRP()
	{
		delete id;
		delete marks;
		delete letters;
	}
 
	StudentId* id;
	StudentMarks* marks;
	RecommendLetters* letters;
};
 
// shared pointers struct
struct StudentInfoSP
{
	std::shared_ptr<StudentId> id = std::make_shared<StudentId>();
	std::shared_ptr<StudentMarks> marks = std::make_shared<StudentMarks>();
	std::shared_ptr<RecommendLetters> letters = std::make_shared<RecommendLetters>();
};
Инициализация выглядит так:

std::vector<StudentInfo> studs(amount);
for (int i = 0; i < amount; ++i)
{
        StudentInfo& stud = studs[i];
        sprintf_s(stud.id.str, sizeof(stud.id) / sizeof(char), "%03d-%014d", rand() % 100, i);
        stud.marks.math = rand() % 10;
	stud.marks.it = rand() % 10;
	stud.marks.lit = rand() % 10;
	stud.marks.pe = rand() % 10;
	stud.letters.amount = (rand() % 20 == 5) ? rand() % 4 : 0;
}
Сортировка:

std::sort(studs.begin(), studs.end(), [](const StudentInfo& a, const StudentInfo& b)
{
	if (a.marks.math != b.marks.math)
	{
        	return a.marks.math < b.marks.math;
	}
	else if (a.marks.it != b.marks.it)
	{
		return a.marks.it < b.marks.it;
	}
	else if (a.marks.lit != b.marks.lit)
	{
		return a.marks.lit < b.marks.lit;
	}
	else if (a.marks.pe != b.marks.pe)
	{
		return a.marks.pe < b.marks.pe;
	}
	else if (a.letters.amount != b.letters.amount)
	{
		return a.letters.amount < b.letters.amount;
	}
	else if (strcmp(a.id.str, b.id.str) != 0)
	{
		return strcmp(a.id.str, b.id.str) < 0;
    	}
});
В общем, ничего необычного. Аналогично все и для других двух структур, только там , посколько это указатели, обращение идет через стрелку. Время засекал очень просто:

auto start = std::chrono::steady_clock::now();
std::vector<StudentInfo> studs(amount);
 
// do some..
 
auto end = std::chrono::steady_clock::now();
const std::chrono::duration<double> delta = end - start;
Теперь самое интересное - результаты. Вот они:


Как видно, вот всех дисциплинах убедительную победу одержал вариант с простыми структурами, значительно опередив вариант с raw-pointer и shared-pointer, причем сортировка была быстрее многократно, чтение значений быстрее на порядок, а удаление обьектов вообще в тысячи раз быстрее. Если ваши данные скомпонованы подряд, компактно в памяти и вы обращаетесь к ним напрямую, а не не через ссылку/указатель - то это путь к высокой скорости! Но нас интересует разница между сырыми указателями и умными указателями - она на самом деле оказалась не так и значительна: сортировка 10 миллионов элементов raw-pointer оказалась всего в 2 раза быстрее, чем сортировка shared-pointer. Создание и удаление и тех и других почти не отличается. 

Вывод: отказ от умных указателей не даст вам значительного прироста производительности, ну кроме каких-то очень-очень специальных случаев.

P.S.
Код находится здесь. Тестовая платформа: Ryzen 7 3700X, 16GB RAM

Комментариев нет:

Отправить комментарий