Multithreading std::Windows和Solaris 10上的异步性能

Multithreading std::Windows和Solaris 10上的异步性能,multithreading,c++11,unix,std,solaris,Multithreading,C++11,Unix,Std,Solaris,我正在Windows机器(使用MSVS2015编译)和运行Solaris 10(使用GCC 4.9.3编译)的服务器上运行一个简单的线程测试程序。在Windows上,通过将线程数量从1增加到可用的内核数量,我获得了显著的性能提升;但是,同样的代码在Solaris 10上看不到任何性能提升 Windows计算机有4个核(8个逻辑核),Unix计算机有8个核(16个逻辑核) 这可能是什么原因?我使用-pthread进行编译,它正在创建线程,因为它在第一个“F”之前打印所有的“S”。我在Solaris

我正在Windows机器(使用MSVS2015编译)和运行Solaris 10(使用GCC 4.9.3编译)的服务器上运行一个简单的线程测试程序。在Windows上,通过将线程数量从1增加到可用的内核数量,我获得了显著的性能提升;但是,同样的代码在Solaris 10上看不到任何性能提升

Windows计算机有4个核(8个逻辑核),Unix计算机有8个核(16个逻辑核)

这可能是什么原因?我使用
-pthread
进行编译,它正在创建线程,因为它在第一个“F”之前打印所有的“S”。我在Solaris机器上没有root访问权限,而且从我所看到的情况来看,没有安装的工具可以用来查看进程的关联性

示例代码:

#包括
#包括
#包括
#包括
#包括
std::default_random_engine gen(std::chrono::system_clock::now().time_since_epoch().count());
标准:正态分布randn(0.0,1.0);
双生成随机(uint64迭代)
{
//线程启动时打印“S”

STD::CUT< P>作为一般规则,C++标准库定义的类没有任何内部锁定。除非从多个线程中修改标准库类的实例,或者从一个线程中读取另一个线程时从另一个线程读取它,否则是未定义的行为,除非“该类型的对象明确指定为无数据竞争可共享”。(,第17.6.4.10节和第17.6.5.9节)RNG类没有“明确指定为无数据竞争可共享”。(
cout
是“可与数据竞争共享”的stdlib对象的一个示例“-只要您没有执行
ios::与stdio同步(false)

因此,您的程序是不正确的,因为它同时从多个线程访问全局RNG对象;每次您请求另一个随机数时,生成器的内部状态都会被修改。在Solaris上,这似乎会导致访问序列化,而在Windows上,这可能会导致您无法获取属性“随机”数

解决方法是为每个线程创建单独的RNG。然后每个线程将独立运行,它们既不会减慢彼此的速度,也不会妨碍彼此。这是一个非常普遍的原则的特例:共享数据越少,多线程总是工作得越好

还有一个额外的问题需要担心:每个线程将在几乎相同的时间调用
system\u clock::now
,因此您可能最终得到一些每个线程的RNG,它们的种子值相同。最好都从
random\u device
对象中播种。
random\u device
从e操作系统,不需要种子;但速度可能非常慢。
random\u设备
应该在
main
中创建和使用,种子传递给每个工作函数,因为全局
random\u设备
可以从多个线程访问(如本答案的上一版本)与全局
默认\u随机\u引擎一样未定义

总之,您的程序应该如下所示:

#include <iostream>
#include <vector>
#include <future>
#include <random>
#include <chrono>

static double generate_randn(uint64_t iterations, unsigned int seed)
{
    // Print "S" when a thread starts
    std::cout << "S";
    std::cout.flush();

    std::default_random_engine gen(seed);
    std::normal_distribution<double> randn(0.0, 1.0);

    double rvalue = 0;
    for (int i = 0; i < iterations; i++)
    {
        rvalue += randn(gen);
    }
    // Print "F" when a thread finishes
    std::cout << "F";
    std::cout.flush();

    return rvalue/iterations;
}

int main(int argc, char *argv[])
{
    if (argc < 2)
        return 0;

    uint64_t count = 100000000;
    uint32_t threads = std::atoi(argv[1]);

    double total = 0;

    std::vector<std::future<double>> futures;
    std::chrono::high_resolution_clock::time_point t1;
    std::chrono::high_resolution_clock::time_point t2;

    std::random_device make_seed;

    // Start timing
    t1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < threads; i++)
    {
        // Start async tasks
        futures.push_back(std::async(std::launch::async,
                                     generate_randn,
                                     count/threads,
                                     make_seed()));
    }
    for (auto &future : futures)
    {
        // Wait for tasks to finish
        future.wait();
        total += future.get();
    }
    // End timing
    t2 = std::chrono::high_resolution_clock::now();

    // Take the average of the threads' results
    total /= threads;

    std::cout << '\n' << total
              << "\nFinished in "
              << std::chrono::duration_cast<
                   std::chrono::milliseconds>(t2 - t1).count()
              << " ms\n";
}
#包括
#包括
#包括
#包括
#包括
静态双生成随机数(uint64迭代,无符号整数种子)
{
//线程启动时打印“S”
std::cout(这不是一个真正的答案,但它不适合于注释,尤其是当命令格式化链接时。)

您可以在Solaris上使用配置文件来配置可执行文件。在Solaris上,该配置文件将能够显示线程的竞争位置

collect -d /tmp -p high -s all app [app args]
然后使用以下命令查看结果:

/tmp/test.1.er
替换为
收集
配置文件运行生成的输出路径

如果您的线程正在争夺@zwol在其答案中发布的某些资源,您将看到它

该工具集的Oracle营销简报可在以下位置找到:


您还可以尝试使用Solaris Studio编译代码以获取更多数据。

我刚想起来,尝试将
gen
randn
的声明移动到
generate\u randn
中,这样每个线程就有一个RNG实例,而不是一个共享的RNG。我还希望看到f每线程运行时。你是对的,在我将gen和randn移到generate_randn中后,它工作得很好!添加它作为答案,我将标记它:“Solaris的实现具有内部锁定”,FWIW问题是它是GCC4.9,在Solaris或任何其他平台上都没有锁定。
。我不知道为什么共享引擎+发行版会这么慢,但它是UB,所以我不太想知道。每个线程使用一个是正确的必要条件,所以如果它也有助于解决性能问题,那很好:-)@JonA显然,你对这种现象有其他解释吗?我不能方便地在Solaris或Windows上进行实验。我最好的猜测是,不同的线程被安排在不同的处理器上,并且它们不断地获得共享变量的缓存未命中,因此缓存争用有效地使它们失效以串行方式运行。虽然问题已经解决,但感谢您的回复!在我遇到问题的实际应用程序中使用Solaris Studio是不可能的,这就是我在GCC中进行测试的原因。如果以后遇到任何类似问题,我肯定会使用analyzer!
analyzer /tmp/test.1.er &