Windows和Solaris 10上的std :: async性能

时间:2016-08-26 11:40:21

标签: 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" es。 。我在Solaris计算机上没有root访问权限,而且我从中可以看到,没有可用于查看进程的已安装工具'亲和力。

示例代码:

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

std::default_random_engine gen(std::chrono::system_clock::now().time_since_epoch().count());
std::normal_distribution<double> randn(0.0, 1.0);

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

    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;

    // 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));
    }
    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 << std::endl;
    std::cout << total << std::endl;
    std::cout << "Finished in " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << " ms" << std::endl;
}

2 个答案:

答案 0 :(得分:3)

作为一般规则,由C ++标准库定义的类 not 具有任何内部锁定。从多个线程修改标准库类的实例,或者从一个线程读取它而从另一个线程读取它是未定义的行为,除非“明确指定该类型的对象在没有数据争用的情况下可共享”。 (N3337,第17.6.4.10节和第17.6.5.9节。)RNG类没有“明确指定为没有数据竞争的可共享”。 (cout是一个stdlib对象的示例, “可与数据竞争共享” - 只要您尚未完成ios::sync_with_stdio(false)。)

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

治愈方法是为每个线程创建单独的RNG。然后每个线程将独立运行,它们既不会相互减速也不会相互踩踏。这是一个非常一般原则的特例:多线程总是在共享数据较少的情况下效果更好。

还有一个额外的问题需要担心:每个线程几乎同时会调用system_clock::now,所以你可能最终得到了一些每个线程的RNG相同的价值。最好从random_device对象中播种它们。 random_device从操作系统请求随机数,不需要播种;但它可能会很慢。应在random_device内创建并使用main,并将种子传递给每个工作函数,因为从多个线程访问的全局random_device(如此答案的上一版本)未定义为全局default_random_engine

总而言之,你的程序应该是这样的:

#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";
}

答案 1 :(得分:2)

(这不是一个真正的答案,但它不适合评论,尤其是使用格式化链接的命令。)

您可以使用Solaris Studio's collect utility在Solaris上配置可执行文件。在Solaris上,它将能够显示您的线程争用的位置。

collect -d /tmp -p high -s all app [app args]

然后使用the analyzer utility查看结果:

analyzer /tmp/test.1.er &

/tmp/test.1.er替换为collect个人资料运行生成的输出路径。

如果你的主题在@zwol的答案中张贴了某些资源,你就会看到它。

可以在此处找到该工具集的Oracle营销简报:http://www.oracle.com/technetwork/server-storage/solarisstudio/documentation/o11-151-perf-analyzer-brief-1405338.pdf

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