Скорость работы на разных типах

#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> or double , но ни в int одном экземпляре.