C++ 循环性能和多线程性能问题

C++ 循环性能和多线程性能问题,c++,multithreading,c++11,for-loop,C++,Multithreading,C++11,For Loop,我有点无聊,所以我想尝试使用std::thread,并最终测量单线程和多线程控制台应用程序的性能。这是一个由两部分组成的问题。所以我从一个巨大的整数向量(800000整数)的单线程和开始 此时,控制台输出如下所示: index loop: 30.0017ms range loop: 221.013ms iterator loop: 442.025ms 这是一个调试版本,所以我改为发行版,与基于索引的版本相比,差异约为1ms。没什么大不了的,只是出于好奇:这三个for循环在调试模式上应该有这么大

我有点无聊,所以我想尝试使用std::thread,并最终测量单线程和多线程控制台应用程序的性能。这是一个由两部分组成的问题。所以我从一个巨大的整数向量(800000整数)的单线程和开始

此时,控制台输出如下所示:

index loop: 30.0017ms
range loop: 221.013ms
iterator loop: 442.025ms
这是一个调试版本,所以我改为发行版,与基于索引的版本相比,差异约为1ms。没什么大不了的,只是出于好奇:这三个for循环在调试模式上应该有这么大的区别吗?或者在释放模式下1毫秒内有差异

我继续创建线程,并尝试使用这个lambda对数组进行并行求和(通过引用捕获所有内容,以便我可以使用前面声明的int向量和互斥量),使用基于索引的for

auto func = [&](int start, int total, int index)
{
    int partial_sum = 0;

    auto s = chrono::high_resolution_clock::now();
    for (int i = start; i < start + total; ++i)
        partial_sum += ints[i];
    auto e = chrono::high_resolution_clock::now();
    auto d = e - s;

    m.lock();
    cout << "thread " + to_string(index) + ": " << chrono::duration<double, milli>(d).count() << "ms" << endl;
    sum += partial_sum;
    m.unlock();
};

for (int i = 0; i < 8; ++i)
    threads.push_back(thread(func, i * 100000, 100000, i));
我猜问题的第二部分是这里发生了什么?含2个螺纹的溶液也以约30ms结束。乒乓球?还有别的吗?如果我做错了什么,正确的方法是什么?如果相关的话,我在一个有8个线程的i7上尝试了这个,所以我知道我没有计算主线程,但是用7个单独的线程尝试了它,得到了几乎相同的结果

编辑:很抱歉,忘记了在Windows7上使用VisualStudio2013和VisualStudio的v120编译器或其他名称时提到过这一点

编辑2:以下是整个主要功能:
在未启用优化的情况下,所有在幕后执行的方法调用都可能是真实的方法调用。内联函数可能不是内联的,而是真正调用的。对于模板代码,您确实需要启用优化,以避免所有代码都被逐字逐句地理解。例如,您的迭代器代码可能会调用iter.end()800000次,运算符!=对于比较800000次,它调用运算符==以此类推


对于多线程代码,处理器是复杂的。操作系统是复杂的。你的代码在计算机上并不孤单。您的计算机可以更改其时钟速度,更改为涡轮模式,更改为热防护模式。而将时间舍入到毫秒并没有真正的帮助。可能是一个线程到6.49毫秒,另一个线程到6.51毫秒,取整方式不同

由于不知道这些std::thread类是如何实现的,53ms的一个可能解释是:

线程在实例化时立即启动。(我没有看到thread.start()或threads.StartAll()或类似的线程)。因此,在第一个线程实例处于活动状态期间,主线程可能会(也可能不会)被抢占。毕竟,不能保证线程是在单个内核上生成的(线程亲和性)

如果您仔细查看POSIXAPI,就会发现“应用程序上下文”和“系统上下文”的概念,这基本上意味着可能存在一个操作系统策略,该策略不会将所有内核用于一个应用程序

在Windows上(这是您测试的地方),可能线程不是直接生成的,而是通过线程池生成的,可能带有一些额外的std::thread功能,这可能会产生开销/延迟。(如完成端口等)

不幸的是,我的机器速度非常快,所以我不得不增加处理的数据量,以获得显著的时间。但从好的方面来看,这提醒我指出,通常情况下,当计算时间远远超过一个时间片的时间时(经验法则),并行计算就开始有回报了

这里是我的“本机”Windows实现,对于足够大的数组,它最终使线程战胜了单线程计算

#include <stdafx.h>
#include <nativethreadTest.h>

#include <vector>
#include <cstdint>
#include <Windows.h>
#include <chrono>
#include <iostream>
#include <thread>

struct Range
{
    Range( const int32_t *p, size_t l)
        : data(p)
        , length(l)
        , result(0)
    {}
    const int32_t *data;
    size_t length;
    int32_t result;
};

static int32_t Sum(const int32_t * data, size_t length)
{
    int32_t sum = 0;
    const int32_t *end = data + length;
    for (; data != end; data++)
    {
        sum += *data;
    }
    return sum;
}

static int32_t TestSingleThreaded(const Range& range)
{
    return Sum(range.data, range.length);
}

DWORD 
WINAPI 
CalcThread
(_In_  LPVOID lpParameter
)
{
    Range * myRange = reinterpret_cast<Range*>(lpParameter);
    myRange->result = Sum(myRange->data, myRange->length);
    return 0;
}

static int32_t TestWithNCores(const Range& range, size_t ncores)
{
    int32_t result = 0;
    std::vector<Range> ranges;
    size_t nextStart = 0;
    size_t chunkLength = range.length / ncores;
    size_t remainder = range.length - chunkLength * ncores;
    while (nextStart < range.length)
    {
        ranges.push_back(Range(&range.data[nextStart], chunkLength));
        nextStart += chunkLength;
    }
    Range remainderRange(&range.data[range.length - remainder], remainder);

    std::vector<HANDLE> threadHandles;
    threadHandles.reserve(ncores);
    for (size_t i = 0; i < ncores; ++i)
    {
        threadHandles.push_back(::CreateThread(NULL, 0, CalcThread, &ranges[i], 0, NULL));
    }
    int32_t remainderResult = Sum(remainderRange.data, remainderRange.length);
    DWORD waitResult = ::WaitForMultipleObjects((DWORD)threadHandles.size(), &threadHandles[0], TRUE, INFINITE);
    if (WAIT_OBJECT_0 == waitResult)
    {
        for (auto& r : ranges)
        {
            result += r.result;
        }
        result += remainderResult;
    }
    else
    {
        throw std::runtime_error("Something went horribly - HORRIBLY wrong!");
    }
    for (auto& h : threadHandles)
    {
        ::CloseHandle(h);
    }
    return result;
}

static int32_t TestWithSTLThreads(const Range& range, size_t ncores)
{
    int32_t result = 0;
    std::vector<Range> ranges;
    size_t nextStart = 0;
    size_t chunkLength = range.length / ncores;
    size_t remainder = range.length - chunkLength * ncores;
    while (nextStart < range.length)
    {
        ranges.push_back(Range(&range.data[nextStart], chunkLength));
        nextStart += chunkLength;
    }
    Range remainderRange(&range.data[range.length - remainder], remainder);

    std::vector<std::thread> threads;
    for (size_t i = 0; i < ncores; ++i)
    {
        threads.push_back(std::thread([](Range* range){ range->result = Sum(range->data, range->length); }, &ranges[i]));
    }

    int32_t remainderResult = Sum(remainderRange.data, remainderRange.length);
    for (auto& t : threads)
    {
        t.join();
    }
    for (auto& r : ranges)
    {
        result += r.result;
    }
    result += remainderResult;
    return result;
}

void TestNativeThreads()
{
    const size_t DATA_SIZE = 800000000ULL;
    typedef std::vector<int32_t> DataVector;
    DataVector data;
    data.reserve(DATA_SIZE);

    for (size_t i = 0; i < DATA_SIZE; ++i)
    {
        data.push_back(static_cast<int32_t>(i));
    }

    Range r = { data.data(), data.size() };
    std::chrono::system_clock::time_point singleThreadedStart = std::chrono::high_resolution_clock::now();
    int32_t result = TestSingleThreaded(r);
    std::chrono::system_clock::time_point singleThreadedEnd = std::chrono::high_resolution_clock::now();
    std::cout
        << "Single threaded sum: "
        << std::chrono::duration_cast<std::chrono::milliseconds>(singleThreadedEnd - singleThreadedStart).count()
        << "ms." << " Result = " << result << std::endl;

    std::chrono::system_clock::time_point multiThreadedStart = std::chrono::high_resolution_clock::now();
    result = TestWithNCores(r, 8);
    std::chrono::system_clock::time_point multiThreadedEnd = std::chrono::high_resolution_clock::now();

    std::cout 
        << "Multi threaded sum: "
        << std::chrono::duration_cast<std::chrono::milliseconds>(multiThreadedEnd - multiThreadedStart).count()
        << "ms." << " Result = " << result << std::endl;

    std::chrono::system_clock::time_point stdThreadedStart = std::chrono::high_resolution_clock::now();
    result = TestWithSTLThreads(r, 8);
    std::chrono::system_clock::time_point stdThreadedEnd = std::chrono::high_resolution_clock::now();

    std::cout
        << "std::thread sum: "
        << std::chrono::duration_cast<std::chrono::milliseconds>(stdThreadedEnd - stdThreadedStart).count()
        << "ms." << " Result = " << result << std::endl;
}
最后同样重要的一点是,我觉得有必要提到,这段代码的编写方式与其说是一个核心CPU计算基准,不如说是一个内存IO性能基准。 更好的计算基准会使用少量的本地数据,适合CPU缓存等

也许将数据分成不同的范围进行实验会很有趣。如果每个线程从开始到结束都在数据上“跳跃”,并且有一个NCore的间隙,该怎么办?线程1:0 8 16。。。线程2:1917。。。等也许那时内存的“局部性”可以获得额外的速度

这三个for循环在调试模式上应该有这么大的区别吗

对。如果允许,一个好的编译器可以为3个不同的循环中的每一个生成相同的输出,但是如果没有启用优化,迭代器版本会有更多的函数调用,函数调用会有一定的开销

或者在释放模式下1毫秒内有差异

您的测试代码:

    start = ...
    for (auto& val : ints)
            sum += val;
    end = ...
    diff = end - start;
    sum = 0;
根本不使用循环的结果,因此在优化时,编译器应该简单地选择丢弃代码,从而产生如下结果:

    start = ...
    // do nothing...
    end = ...
    diff = end - start;
为了你所有的循环


1ms的差异可能由标准库所用实现中“
高分辨率时钟
”的高粒度以及执行过程中进程调度的差异产生。我测量的基于索引的速度慢了0.04毫秒,但这一结果毫无意义。

除了在Windows上如何实现std::thread之外,我想提醒您注意可用的执行单元和上下文切换

i7没有8个实际执行单元。它是一个具有超线程的四核处理器。而且HT不会神奇地将可用线程数增加一倍,无论它是如何宣传的。这是一个非常聪明的系统,只要有可能,它会尝试从额外的管道中插入指令。但最终所有指令只经过四个执行单元。 因此,运行8(或7)个线程仍然超出了您的CPU真正能够同时处理的范围。这意味着您的CPU必须在争抢计算时间的8个热线程之间切换很多。除此之外,操作系统还提供了数百个线程,其中大多数线程都处于休眠状态
#include <stdafx.h>
#include <nativethreadTest.h>

#include <vector>
#include <cstdint>
#include <Windows.h>
#include <chrono>
#include <iostream>
#include <thread>

struct Range
{
    Range( const int32_t *p, size_t l)
        : data(p)
        , length(l)
        , result(0)
    {}
    const int32_t *data;
    size_t length;
    int32_t result;
};

static int32_t Sum(const int32_t * data, size_t length)
{
    int32_t sum = 0;
    const int32_t *end = data + length;
    for (; data != end; data++)
    {
        sum += *data;
    }
    return sum;
}

static int32_t TestSingleThreaded(const Range& range)
{
    return Sum(range.data, range.length);
}

DWORD 
WINAPI 
CalcThread
(_In_  LPVOID lpParameter
)
{
    Range * myRange = reinterpret_cast<Range*>(lpParameter);
    myRange->result = Sum(myRange->data, myRange->length);
    return 0;
}

static int32_t TestWithNCores(const Range& range, size_t ncores)
{
    int32_t result = 0;
    std::vector<Range> ranges;
    size_t nextStart = 0;
    size_t chunkLength = range.length / ncores;
    size_t remainder = range.length - chunkLength * ncores;
    while (nextStart < range.length)
    {
        ranges.push_back(Range(&range.data[nextStart], chunkLength));
        nextStart += chunkLength;
    }
    Range remainderRange(&range.data[range.length - remainder], remainder);

    std::vector<HANDLE> threadHandles;
    threadHandles.reserve(ncores);
    for (size_t i = 0; i < ncores; ++i)
    {
        threadHandles.push_back(::CreateThread(NULL, 0, CalcThread, &ranges[i], 0, NULL));
    }
    int32_t remainderResult = Sum(remainderRange.data, remainderRange.length);
    DWORD waitResult = ::WaitForMultipleObjects((DWORD)threadHandles.size(), &threadHandles[0], TRUE, INFINITE);
    if (WAIT_OBJECT_0 == waitResult)
    {
        for (auto& r : ranges)
        {
            result += r.result;
        }
        result += remainderResult;
    }
    else
    {
        throw std::runtime_error("Something went horribly - HORRIBLY wrong!");
    }
    for (auto& h : threadHandles)
    {
        ::CloseHandle(h);
    }
    return result;
}

static int32_t TestWithSTLThreads(const Range& range, size_t ncores)
{
    int32_t result = 0;
    std::vector<Range> ranges;
    size_t nextStart = 0;
    size_t chunkLength = range.length / ncores;
    size_t remainder = range.length - chunkLength * ncores;
    while (nextStart < range.length)
    {
        ranges.push_back(Range(&range.data[nextStart], chunkLength));
        nextStart += chunkLength;
    }
    Range remainderRange(&range.data[range.length - remainder], remainder);

    std::vector<std::thread> threads;
    for (size_t i = 0; i < ncores; ++i)
    {
        threads.push_back(std::thread([](Range* range){ range->result = Sum(range->data, range->length); }, &ranges[i]));
    }

    int32_t remainderResult = Sum(remainderRange.data, remainderRange.length);
    for (auto& t : threads)
    {
        t.join();
    }
    for (auto& r : ranges)
    {
        result += r.result;
    }
    result += remainderResult;
    return result;
}

void TestNativeThreads()
{
    const size_t DATA_SIZE = 800000000ULL;
    typedef std::vector<int32_t> DataVector;
    DataVector data;
    data.reserve(DATA_SIZE);

    for (size_t i = 0; i < DATA_SIZE; ++i)
    {
        data.push_back(static_cast<int32_t>(i));
    }

    Range r = { data.data(), data.size() };
    std::chrono::system_clock::time_point singleThreadedStart = std::chrono::high_resolution_clock::now();
    int32_t result = TestSingleThreaded(r);
    std::chrono::system_clock::time_point singleThreadedEnd = std::chrono::high_resolution_clock::now();
    std::cout
        << "Single threaded sum: "
        << std::chrono::duration_cast<std::chrono::milliseconds>(singleThreadedEnd - singleThreadedStart).count()
        << "ms." << " Result = " << result << std::endl;

    std::chrono::system_clock::time_point multiThreadedStart = std::chrono::high_resolution_clock::now();
    result = TestWithNCores(r, 8);
    std::chrono::system_clock::time_point multiThreadedEnd = std::chrono::high_resolution_clock::now();

    std::cout 
        << "Multi threaded sum: "
        << std::chrono::duration_cast<std::chrono::milliseconds>(multiThreadedEnd - multiThreadedStart).count()
        << "ms." << " Result = " << result << std::endl;

    std::chrono::system_clock::time_point stdThreadedStart = std::chrono::high_resolution_clock::now();
    result = TestWithSTLThreads(r, 8);
    std::chrono::system_clock::time_point stdThreadedEnd = std::chrono::high_resolution_clock::now();

    std::cout
        << "std::thread sum: "
        << std::chrono::duration_cast<std::chrono::milliseconds>(stdThreadedEnd - stdThreadedStart).count()
        << "ms." << " Result = " << result << std::endl;
}
Single threaded sum: 382ms. Result = -532120576
Multi threaded sum: 234ms. Result = -532120576
std::thread sum: 245ms. Result = -532120576
Press any key to continue . . ..
    start = ...
    for (auto& val : ints)
            sum += val;
    end = ...
    diff = end - start;
    sum = 0;
    start = ...
    // do nothing...
    end = ...
    diff = end - start;