#c #performance
#c #Производительность
Вопрос:
У меня довольно глупая задача класса: измерить, какие типы работают быстрее на C . Сначала я подумал: «очевидно, что самый быстрый тип — это тип, соответствующий размеру слова процессора, поэтому на моем AMD Ryzen 7 2700x это будет int64_t) и, конечно, double и float будут намного медленнее, чем целые числа. Я написал простую программу, но не могу объяснить ее вывод для себя… Возможно, я что-то упускаю. (скомпилирован в 64-разрядной версии msvc142).
#include <vector>
#include <chrono>
#include <array>
template <typename T>
T** newMatr1(size_t n)
{
T** res = new T * [n];
for (size_t i = 0; i < n; i )
{
res[i] = new T[n];
}
return res;
}
template <typename T>
void delMatr1(T** matr, size_t n)
{
for (size_t i = 0; i < n; i )
{
delete[] matr[i];
}
delete matr;
}
template <typename T>
void initMatr(T** matr, size_t n)
{
for (size_t i = 0; i < n; i )
{
for (size_t j = 0; j < n; j )
{
matr[i][j] = rand();
}
}
}
template <typename T>
void multiplyMatr(T** matr1, T** matr2, size_t n)
{
T** res = newMatr1<T>(n);
for (size_t i = 0; i < n; i)
{
for (size_t j = 0; j < n; j)
{
for (size_t k = 0; k < n; k)
{
res[i][j] = matr1[i][k] * matr2[k][j];
}
}
}
delMatr1(res, n);
}
template <typename T>
std::vector<std::vector<T>> initVec(size_t n)
{
std::vector<std::vector<T>> matr(n, std::vector<T>(n));
for (size_t i = 0; i < n; i )
{
for (size_t j = 0; j < n; j )
{
matr[i][j] = rand();
}
}
return matr;
}
template <typename T>
void multiplyVect(const std::vector<std::vector<T>>amp; matr1, const std::vector<std::vector<T>>amp; matr2, size_t n)
{
std::vector<std::vector<T>> res(n, std::vector<T>(n));
for (size_t i = 0; i < n; i)
{
for (size_t j = 0; j < n; j)
{
for (size_t k = 0; k < n; k)
{
res[i][j] = matr1[i][k] * matr2[k][j];
}
}
}
}
template <typename Func, typename T>
std::chrono::high_resolution_clock::duration getMinTime(Func function, const Tamp; matr1, const Tamp; matr2, int n, int count)
{
auto min = std::chrono::high_resolution_clock::duration::max();
for (size_t i = 0; i < count; i)
{
auto start = std::chrono::high_resolution_clock::now();
function(matr1, matr2, n);
auto end = std::chrono::high_resolution_clock::now();
auto diff = end - start;
if(diff < min)
min = diff;
}
return min;
}
template <typename T>
void getTime()
{
std::vector<size_t> sizes{ 512 };// , 1024, 2048
for (int i = 0; i < sizes.size(); i)
{
std::cout << sizes[i] << ":n";
std::cout << "std::vector: ";
std::vector<std::vector<T>> vec1 = initVec<T>(sizes[i]);
std::vector<std::vector<T>> vec2 = initVec<T>(sizes[i]);
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(getMinTime(multiplyVect<T>, vec1, vec2, sizes[i], 10)).count() << "ms";
std::cout << " arr(raw ptr): ";
T** matr1 = newMatr1<T>(sizes[i]);
T** matr2 = newMatr1<T>(sizes[i]);
initMatr(matr1, sizes[i]);
initMatr(matr2, sizes[i]);
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(getMinTime(multiplyMatr<T>, matr1, matr2, sizes[i], 10)).count() << "msn";
delMatr1(matr1, sizes[i]);
delMatr1(matr2, sizes[i]);
}
}
int main()
{
std::vector<size_t> sizes{ 512, 1024 }; //, 2048
std::cout << "int8_tn";
getTime<int8_t>();
std::cout << "int16_tn";
getTime<int16_t>();
std::cout << "int32n";
getTime<int>();
std::cout << "int64_tn";
getTime<int64_t>();
std::cout << "floatn";
getTime<float>();
std::cout << "doublen";
getTime<double>();
system("pause");
}
результат, который я получил:
int8_t
512:
std :: vector: 233 мс arr (необработанный ptr): 228
мс int16_t
512:
std :: vector: 233 мс arr (необработанный ptr): 230 мс
int32
512:
std :: vector: 237 мс arr (необработанный ptr): 235 мс
int64_t
512:
std:: vector: 250 мс arr (необработанный ptrptr): 236
мс с плавающей
точкой 512:
std ::vector: 104 мс arr (необработанный ptr): 104 мс
double
512:
std :: vector: 106 мс arr (необработанный ptr): 106 мс
что не так? Почему плавающие и двойные значения намного быстрее, чем целые числа? И почему double не медленнее, чем float?
Флаги:
/permissive- /GS / GL /W3 /Gy /Zc: wchar_t / Zi /Gm- /O2 /sdl /Fd»x64 Release vc142.pdb» / Zc: встроенный /fp: точный / D «NDEBUG» / D «_CONSOLE» /D «_UNICODE»/ D «UNICODE» / Отчет об ошибке: подсказка / WX- / Zc: forScope / Gd / Oi / MD / FC / Fa «x64 Release» / EHsc / nologo / Fo»x64 Release» / Ot / Fp»x64 Release tmp.pch» / диагностика: столбец
Комментарии:
1. вы включили оптимизацию компилятора? Пожалуйста, опубликуйте флаги, которые вы использовали для вызова компилятора
2. как вы думаете, что не так в результатах? Чего еще вы ожидали?
3.
std::vector<size_t> sizes{ 512 };
создает вектор с одним элементом — числом 512. Таким образом, ваш цикл выполняется только один раз, что делает ваши измерения совершенно неточными4. На самом деле, ваше предположение о том, что размер слова является самым быстрым типом, как правило, неточно.
int
предполагается, что это самый быстрый целочисленный тип для доступа в архитектуре, а на 64-разрядных архитектурах он обычно остается 32-разрядным.5. Я быстро вставил ваш код в godbolt и скомпилировал его с помощью «/O2 /arch:AVX512». Я ни в коем случае не эксперт в языке ассемблера, но я вижу много векторных (SSE?) Инструкций, Например, в
multiplyVector<float>
ordouble
, но ни вint
одном экземпляре.