【问题标题】:Why are size_t and unsigned int slower than int?为什么 size_t 和 unsigned int 比 int 慢?
【发布时间】:2018-08-25 20:37:21
【问题描述】:

我正在使用下面的简单交换排序算法在 Windows 的 Visual Studio 项目中试验不同的整数类型。处理器是英特尔。代码在版本 x64 中编译。优化设置为“最大化速度 (/O2)”。编译设置对应的命令行是

/permissive- /GS /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"x64\Release\vc141.pdb" /Zc:inline /fp:precise /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /Gd /Oi /MD /Fa"x64\Release\" /EHsc /nologo /Fo"x64\Release\" /Fp"x64\Release\SpeedTestForIntegerTypes.pch" /diagnostics:classic 

代码本身:

#include <ctime>
#include <vector>
#include <iostream>

void sort(int N, int A[], int WorkArray[]) // exchange sort
{
    int i, j, index, val_min;
    for (j = 0; j < N; j++)
    {
        val_min = 500000;
        for (i = j; i < N; i++)
        {
            if (A[i] < val_min)
            {
                val_min = A[i];
                index = i;
            }
        }
        WorkArray[j] = A[j];
        A[j] = val_min;
        A[index] = WorkArray[j];
    }
}

int main()
{
    std::vector<int> A(400000), WorkArray(400000);
    for(size_t k = 0; k < 400000; k++)
        A[k] = 400000 - (k+1);

    clock_t begin = clock();

    sort(400000, &A[0], &WorkArray[0]);

    clock_t end = clock();
    double sortTime = double(end - begin) / CLOCKS_PER_SEC;
    std::cout << "Sort time: " << sortTime << std::endl;
    return 0;
}

WorkArray 只需要在排序前保存向量。 关键是,这个排序花了我 22.3 秒来完成。有趣的部分是,如果我将数组AWorkArray 的类型int 更改为size_t(在std::vector 和函数sort 的参数列表中)以及val_min ,时间增加到67.4!这是 三倍 慢!新代码如下:

#include <ctime>
#include <vector>
#include <iostream>

void sort(int N, size_t A[], size_t WorkArray[]) // exchange sort
{
    int i, j, index;
    size_t val_min;
    for (j = 0; j < N; j++)
    {
        val_min = 500000U;
        for (i = j; i < N; i++)
        {
            if (A[i] < val_min)
            {
                val_min = A[i];
                index = i;
            }
        }
        WorkArray[j] = A[j];
        A[j] = val_min;
        A[index] = WorkArray[j];
    }
}

int main()
{
    std::vector<size_t> A(400000), WorkArray(400000);
    for(size_t k = 0; k < 400000; k++)
        A[k] = 400000 - (k+1);

    clock_t begin = clock();

    sort(400000, &A[0], &WorkArray[0]);

    clock_t end = clock();
    double sortTime = double(end - begin) / CLOCKS_PER_SEC;
    std::cout << "Sort time: " << sortTime << std::endl;
    return 0;
}

请注意,对于函数局部变量 ijindexN,我仍然保留类型 int,因此只有 i++j++ 这两个算术运算应该采用在这两种情况下执行相同的时间。因此,这种放缓与其他原因有关。它与内存对齐问题或寄存器大小或其他有关吗?

最离谱的部分是当我将int 更改为unsigned int 时。 unsigned intint 都占用相同的字节数,即 4(sizeof 表明了这一点)。但是unsigned int 的运行时间是 65.8 秒!虽然第一个结果可以接受,但第二个结果完全让我感到困惑!为什么运行这样一个甚至不涉及符号检查的简单算法所需的时间会有如此显着的差异?

感谢所有解决这两个问题的人。我可以从哪里开始阅读有关这些硬件级优化特性的更多信息?我不关心排序算法本身,这里只是为了说明问题。

更新:我再次强调 在所有三种情况下我都使用整数作为数组索引

【问题讨论】:

  • 当你改变类型时,你是在所有地方都改变还是只在 main 改变?您能否发布 2 个行为不同的代码版本来回答我的问题?
  • 您的优化设置是什么?一般来说,基准测试应该只应用于已发布的、非调试的构建。
  • 没有通过 Coliru 对 GCC 进行复制。另外,还有一个警告:main.cpp:23:16: warning: 'index' may be used uninitialized in this function [-Wmaybe-uninitialized] A[index] = WorkArray[j]; 不幸的是,我面前没有 MSVC 来测试这个 atm。
  • 在我的机器上测试了代码。 int 为 42s,无符号 int 为 41s。当您测试这两种情况时,您的机器是否处于闲置状态? (我对 size_t 不感兴趣,因为这对我来说有点像“呃,它的数据量是两倍”。)
  • @StephenNewell 这很能说明问题。 MSVC 使用size_t 进行矢量化,但不是int。但是intunsigned 几乎相同。

标签: c++ performance int size-t


【解决方案1】:

检查生成的所有 3 个变体(intunsignedsize_t)的程序集,最大的区别是在int 情况下,sort 函数中的循环被展开并使用 SSE 指令(一次处理 8 个整数),而在其他 2 种情况下两者都没有。有趣的是,sort 函数在 int 案例中被调用,而在其他两个案例中它被内联到 main 中(可能是由于循环展开导致函数大小增加)。

我使用cl /nologo /W4 /MD /EHsc /Zi /Ox从命令行编译,使用dumpbin进行反汇编,使用工具集Microsoft (R) C/C++ Optimizing Compiler Version 19.12.25830.2 for x64

int 的执行时间约为 30 秒,其他两个为 100 秒。

【讨论】:

  • 谢谢!考虑到它与int 的大小相同,您是否知道为什么它不为unsigned int 展开?
  • 这就是闭源编译器的可悲之处,除非您编写它并违反您雇主的保密协议,否则其他任何人都无法知道编译器为什么要进行特定优化.
  • 可能是因为有一条 SSE 指令 (pcmpgtd) 可以进行有符号比较,但没有用于无符号比较。
  • @1201ProgramAlarm - 有趣。我的从不生成此指令或类似的指令,尽管它使用其他 AVX/SSE 指令。
  • @1201ProgramAlarm - 与 VS 2015 和 2017 完全不同的程序集(2015 年 25 秒与 40 秒)。
【解决方案2】:

我在 VS2017 中试过这个代码。我成功复制了。

我将代码修改如下,这样时间就差不多了。

原因似乎是由于数组索引的隐式转换。

#include <ctime>
#include <vector>
#include <iostream>

using namespace std;

// exchange sort
template<typename elem_t, typename index_t>
void sort(index_t size, elem_t* a, elem_t* b)
{
    index_t index = 0, i, j;
    elem_t min;

    for (j = 0; j < size; j++)
    {
        min = 500000;
        for (i = j; i < size; i++)
        {
            if (a[i] < min)
            {
                min = a[i];
                index = i;
            }
        }
        b[j] = a[j];
        a[j] = min;
        a[index] = b[j];
    }
}

template<typename elem_t, typename index_t, index_t size>
void test() {
    //vector<elem_t> a(size);
    //vector<elem_t> b(size);

    elem_t a[size];
    elem_t b[size];

    for (index_t k = 0; k < size; k++)
        a[k] = (elem_t)(size - (k + 1));

    clock_t begin = clock();
    sort(size, &a[0], &b[0]);
    clock_t end = clock();

    double sortTime = double(end - begin) / CLOCKS_PER_SEC;
    cout << "Sort time: " << sortTime << endl;
}

int main()
{
    const int size = 40000;

    cout << "<size_t, int>" << endl;
    test<size_t, int, size>();
    cout << endl;

    cout << "<size_t, size_t>" << endl;
    test<size_t, size_t, size>();
    cout << endl;

    cout << "<int, int>" << endl;
    test<int, int, size>();
    cout << endl;

    cout << "<int, size_t>" << endl;
    test<int, size_t, size>();
    cout << endl;

    cout << "<uint, int>" << endl;
    test<unsigned int, int, size>();
    cout << endl;

    cout << "<uint, size_t>" << endl;
    test<unsigned int, size_t, size>();
    cout << endl;

    cout << "<uint, uint>" << endl;
    test<unsigned int, unsigned int, size>();
    cout << endl;
}

就个人而言,我不喜欢隐式转换。 为了解决此类问题,将警告级别提高到最大,并解决所有警告,然后转换为通用代码。这将帮助您识别问题。

此代码的结果显示为各种组合的结果。

【讨论】:

  • “几乎总是自动”这句话变得越来越相关
  • 您是如何设法将所有三种情况下的运行时间都减少到 0.67 秒的!!!!!!???????但是感谢您的出色回答。我会慢慢消化的。
  • @MajinSaha 将测试规模从400000 减少到40000
  • 哦,我明白了。抱歉,我没注意到。
  • 回复:“我不喜欢隐式转换”——没有这样的事情。强制转换是您在源代码中编写的内容,用于告诉编译器进行转换。也就是说,它始终是明确的。 转化可以是隐式的。
猜你喜欢
  • 1970-01-01
  • 2010-09-13
  • 2016-10-22
  • 1970-01-01
  • 1970-01-01
  • 2016-02-26
  • 1970-01-01
  • 2021-10-17
  • 2023-01-20
相关资源
最近更新 更多