std::async 在 Windows 和 Solaris 10 上的性能
std::async performance on Windows and Solaris 10
我是 运行 在 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;
}
作为一般规则,C++ 标准库定义的 classes 不 有任何内部锁定。从多个线程修改标准库 class 的实例,或者从一个线程读取它同时从另一个线程写入它,是未定义的行为,除非 "objects of that type are explicitly specified as being sharable without data races"。 (N3337,第 17.6.4.10 和 17.6.5.9 节。)RNG class 不是 "explicitly specified as being sharable without data races"。 (cout
是 是 "sharable with data races" 的 stdlib 对象的示例 — 只要您还没有完成 ios::sync_with_stdio(false)
。)
因此,您的程序 不正确 因为它同时从多个线程访问全局 RNG 对象;每次您请求另一个随机数时,生成器的内部状态都会被修改。在 Solaris 上,这似乎会导致访问序列化,而在 Windows 上,它可能反而导致您无法正确获取 "random" 数字。
解决方法是为每个线程创建单独的 RNG。这样每个线程就会独立运行,既不会互相拖慢对方的速度,也不会踩到对方的脚趾头。这是一个非常普遍的原则的特例:共享数据越少,多线程总是工作得越好。
还有一个问题需要担心:每个线程都会几乎同时调用 system_clock::now
,因此您可能 最终会得到一些 per-使用相同值播种的线程 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";
}
(这不是真正的答案,但它不适合评论,尤其是使用格式化链接的命令时。)
您可以使用 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 编译您的代码以获得更多数据。
我是 运行 在 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;
}
作为一般规则,C++ 标准库定义的 classes 不 有任何内部锁定。从多个线程修改标准库 class 的实例,或者从一个线程读取它同时从另一个线程写入它,是未定义的行为,除非 "objects of that type are explicitly specified as being sharable without data races"。 (N3337,第 17.6.4.10 和 17.6.5.9 节。)RNG class 不是 "explicitly specified as being sharable without data races"。 (cout
是 是 "sharable with data races" 的 stdlib 对象的示例 — 只要您还没有完成 ios::sync_with_stdio(false)
。)
因此,您的程序 不正确 因为它同时从多个线程访问全局 RNG 对象;每次您请求另一个随机数时,生成器的内部状态都会被修改。在 Solaris 上,这似乎会导致访问序列化,而在 Windows 上,它可能反而导致您无法正确获取 "random" 数字。
解决方法是为每个线程创建单独的 RNG。这样每个线程就会独立运行,既不会互相拖慢对方的速度,也不会踩到对方的脚趾头。这是一个非常普遍的原则的特例:共享数据越少,多线程总是工作得越好。
还有一个问题需要担心:每个线程都会几乎同时调用 system_clock::now
,因此您可能 最终会得到一些 per-使用相同值播种的线程 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";
}
(这不是真正的答案,但它不适合评论,尤其是使用格式化链接的命令时。)
您可以使用 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 编译您的代码以获得更多数据。