以下代码在Mac OS X上的执行速度比在Linux上慢200倍。我不知道为什么,问题不似乎是微不足道的。我怀疑Mac上或Mac OS X本身或我硬件中的gcc存在错误。
代码分叉将复制页面表的进程,但不 Mac OS X上的内存。内存在写入时被复制,在运行结束时在for循环中发生方法。在那里,对于前4次运行调用,必须复制所有页面,因为每个页面都被触摸。对于跳过512的第二次4次调用,需要复制每隔一页,因为每触摸一次第二页。直观地说,前4个呼叫应该花费大约两个呼叫的两倍,这绝对不是这种情况。对我来说,程序的输出如下:
169.655ms
670.559ms
2784.18ms
16007.1ms
16.207ms
25.018ms
42.712ms
79.676ms
在Linux上它是
5.306ms
10.69ms
20.91ms
41.042ms
6.115ms
12.203ms
23.939ms
40.663ms
Mac OS X上的总运行时间是20秒,在Linux上大约0.5秒,对于使用gcc编译的完全相同的程序。我已经尝试用gcc4,4.2和4.4编译mac os版本 - 没有变化。
有什么想法吗?
代码:
#include <stdint.h>
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <cstring>
#include <cstdlib>
#include <sys/time.h>
using namespace std;
class Timestamp
{
private:
timeval time;
public:
Timestamp() { gettimeofday(&time,0); }
double operator-(const Timestamp& other) const { return static_cast<double>((static_cast<long long>(time.tv_sec)*1000000+(time.tv_usec))-(static_cast<long long>(other.time.tv_sec)*1000000+(other.time.tv_usec)))/1000.0; }
};
class ForkCoW
{
public:
void run(uint64_t size, uint64_t skip) {
// allocate and initialize array
void* arrayVoid;
posix_memalign(&arrayVoid, 4096, sizeof(uint64_t)*size);
uint64_t* array = static_cast<uint64_t*>(arrayVoid);
for (uint64_t i = 0; i < size; ++i)
array[i] = 0;
pid_t p = fork();
if (p == 0)
sleep(99999999);
if (p < 0) {
cerr << "ERRROR: Fork failed." << endl;
exit(-1);
}
{
Timestamp start;
for (uint64_t i = 0; i < size; i += skip) {
array[i] = 1;
}
Timestamp stop;
cout << (stop-start) << "ms" << endl;
}
kill(p,SIGTERM);
}
};
int main(int argc, char* argv[]) {
ForkCoW f;
f.run(1ull*1000*1000, 512);
f.run(2ull*1000*1000, 512);
f.run(4ull*1000*1000, 512);
f.run(8ull*1000*1000, 512);
f.run(1ull*1000*1000, 513);
f.run(2ull*1000*1000, 513);
f.run(4ull*1000*1000, 513);
f.run(8ull*1000*1000, 513);
}
答案 0 :(得分:1)
只有这么长时间睡眠的原因才是这一行:
sleep(300000);
导致300秒的睡眠(300 * 1000)。也许{os} x上fork()
的实现与你期望的不同(并且它总是返回0)。
答案 1 :(得分:1)
这与C ++无关。我在C中重写了您的示例并使用waitpid(2)而不是sleep / SIGCHLD并且无法重现问题:
#include <errno.h>
#include <inttypes.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
void ForkCoWRun(uint64_t size, uint64_t skip) {
// allocate and initialize array
uint64_t* array;
posix_memalign((void **)&array, 4096, sizeof(uint64_t)*size);
for (uint64_t i = 0; i < size; ++i)
array[i] = 0;
pid_t p = fork();
switch(p) {
case -1:
fprintf(stderr, "ERRROR: Fork failed: %s\n", strerror(errno));
exit(EXIT_FAILURE);
case 0:
{
struct timeval start, stop;
gettimeofday(&start, 0);
for (uint64_t i = 0; i < size; i += skip) {
array[i] = 1;
}
gettimeofday(&stop, 0);
long microsecs = (long)(stop.tv_sec - start.tv_sec) *1000000 + (long)(stop.tv_usec - start.tv_usec);
printf("%ld.%03ld ms\n", microsecs / 1000, microsecs % 1000);
exit(EXIT_SUCCESS);
}
default:
{
int exit_status;
waitpid(p, &exit_status, 0);
break;
}
}
}
int main(int argc, char* argv[]) {
ForkCoWRun(1ull*1000*1000, 512);
ForkCoWRun(2ull*1000*1000, 512);
ForkCoWRun(4ull*1000*1000, 512);
ForkCoWRun(8ull*1000*1000, 512);
ForkCoWRun(1ull*1000*1000, 513);
ForkCoWRun(2ull*1000*1000, 513);
ForkCoWRun(4ull*1000*1000, 513);
ForkCoWRun(8ull*1000*1000, 513);
}
在OS X 10.8,10.9和10.10上,我得到的结果如下:
6.163 ms
12.239 ms
24.529 ms
49.223 ms
6.027 ms
12.081 ms
24.270 ms
49.498 ms
答案 2 :(得分:0)
我怀疑你的问题是在Linux上执行的顺序是它首先运行父进程,然后父执行并且子进程终止,因为它的父进程已经消失,但是在Mac OS上它首先运行子进程,这涉及到300秒睡觉。
在任何Unix标准中,绝对无法保证fork之后的两个进程将并行运行。关于操作系统能力的断言尽管如此。
为了证明这是睡眠时间,我用“SLEEPTIME”替换了“30000”代码并编译并用g++ -DSLEEPTIME=?? foo.c && ./a.out
运行:
SLEEPTIME output
20 20442.1
30 30468.5
40 40431.4
10 10449 <just to prove it wasn't getting longer each run>
答案 3 :(得分:0)
您正在分配400兆字节,并再次从fork()
分配(因为该过程是重复的,包括内存分配)。
缓慢的原因可能只是从具有两个进程的fork()
开始,您的可用物理内存耗尽,并且正在使用磁盘中的swap
内存。
这通常比使用物理内存慢得多。
编辑以下评论
我建议您在写入数组的第一个元素后更改代码以开始计时测量。
array[0] = 1;
Timestamp start;
for (int64_t i = 1; i < size; i++) {
array[i] = 1;
这样, timestamp 中不会考虑第一次写入后内存分配所用的时间。
答案 4 :(得分:0)
当您在子项上拥有父waitpid()
并确保它已退出(以及安全处理SIGCHLD
以确保获取该进程时)会发生什么。)在Linux上似乎可能孩子本可以早点退出,现在页面错误处理程序必须减少写入时复制的工作,因为页面只由一个进程引用。
第二......你知道fork()
必须做的工作吗?特别是它不应该被假定为“快”。从语义上讲,它必须复制进程地址空间中的每个页面。他们说,从历史上看,这就是旧的Unix所做的。通过最初将这些页面标记为“写时复制”(即,页面被标记为只读,内核的页面错误处理程序将在第一次写入时复制它们)来改进这一点,但这仍然是 / i>很多工作,这意味着你在每一页上的第一次写访问都会很慢。
我祝贺Linux开发人员为您的访问模式快速获取他们的fork()
和他们的写时复制实现......但是,如果Mac OS的话,声称这是一个很大的问题似乎是一件非常奇怪的事情。内核不是那么好,或者系统的其他部分碰巧生成不同的访问模式,或者其他什么。分叉,并在分叉后写页面,假设是快速的。
我想我想说的是,如果您将代码移动到具有不同设计选择的内核,并且突然间fork()
更慢,更难,这是移动您的一部分代码到不同的操作系统。
答案 5 :(得分:-1)
您是否确认fork()正在运行:
int main()
{
pid_t pid = fork();
if( pid > 0 ) {
std::cout << "Parent\n";
} else if( pid == 0 ) {
std::cout << "Child\n";
} else {
std::cout << "Failed to fork!\n";
}
}
也许MAC OS-X对分支子进程有一些限制。