用户时间是计算机花费在执行您的计算的秒数。系统时间是操作系统响应程序请求所花费的时间。经过时间是这两个时间加上您的程序和/或操作系统必须等待的任何“等待”时间的总和。需要注意的是,这些数字是花费的累积时间。您的程序可能会计算1秒钟,然后在操作系统上等待1秒钟,然后在硬盘上等待3秒钟,而当它运行时可能会重复这个周期多次。
根据您的程序所花费的系统时间与用户时间相同的事实,说明它需要大量进行IO操作。频繁地从磁盘读取或向磁盘写入数据。RAM速度很快,通常只需要几百纳秒。因此,如果所有内容都适合RAM,经过的时间通常比用户时间稍长一点。但是,磁盘可能需要几毫秒来搜索,甚至更长时间才能回复数据。这比RAM慢了一百万倍。
我们已确定您的处理器正在执行某些任务约为8 + 8 = ~16秒。那其他的38秒呢?在等待硬盘将其请求的数据发送给它。
更新1:
Matthew提出了一些很好的观点,我做出了一些可能不应该做出的假设。如果Adam愿意发布表中的所有行(我们只需要数据类型),我们可以更好地了解情况。
我刚刚编写了一个小程序来验证我的假设:未在用户空间和内核空间消耗时间的时间很可能是用于等待IO操作。
#include <stdio.h>
int main()
{
int i;
for(i = 0; i < 1000000000; i++)
{
int j, k, l, m;
j = 10;
k = i;
l = j + k;
m = j + k - i + l;
}
return 0;
}
当我运行生成的程序并计时时,我会看到类似于这样的东西:
mike@computer:~$ time ./waste_user
real 0m4.670s
user 0m4.660s
sys 0m0.000s
mike@computer:~$
通过检查,您可以看到该程序没有进行任何真正的工作,因此除了将其加载到RAM并开始运行之外,它不会要求内核做任何事情。 因此,几乎所有的“真实”时间都花费在“用户”时间上。
现在,一个依赖于内核的空操作程序(少了一些迭代以使时间合理):
#include <stdio.h>
int main()
{
FILE * random;
random = fopen("/dev/urandom", "r");
int i;
for(i = 0; i < 10000000; i++)
{
fgetc(random);
}
return 0;
}
当我运行那个时,我会看到更像这样的东西:
mike@computer:~$ time ./waste_sys
real 0m1.138s
user 0m0.090s
sys 0m1.040s
mike@computer:~$
可以通过检查轻松地看出程序只是请求内核给它随机字节。 /dev/urandom是一种非阻塞的熵源。那是什么意思?内核使用伪随机数生成器快速为我们的小测试程序生成“随机”值。这意味着内核必须进行一些计算,但它可以很快返回。因此,该程序大部分时间都在等待内核为其计算,我们可以看到几乎所有时间都花费在sys上。
现在,我们要做出一个小改变。我们将不再从非阻塞的/dev/urandom读取,而是从阻塞的/dev/random读取。那是什么意思?它不进行太多计算,而是等待你的计算机上发生内核开发人员经验确定为随机的事情。(我们还会进行更少的迭代,因为这需要更长时间)
#include <stdio.h>
int main()
{
FILE * random;
random = fopen("/dev/random", "r");
int i;
for(i = 0; i < 100; i++)
{
fgetc(random);
}
return 0;
}
当我运行并计时程序的这个版本时,我看到的是:
mike@computer:~$ time ./waste_io
real 0m41.451s
user 0m0.000s
sys 0m0.000s
mike@computer:~$
运行时花费了41秒,但在用户和实际时间上却只有微不足道的少量时间。为什么呢?所有时间都花在内核中了,但没有进行主动计算,内核只是在等待事件发生。一旦收集到足够的熵,内核就会重新唤醒并将数据发送回程序。(请注意,根据正在进行的情况,它可能需要更少或更多的时间才能在您的计算机上运行)。我认为用户+系统时间与实际时间之间的时间差是由于IO操作。
那么这一切意味着什么呢?这并不能证明我的答案是正确的,因为你看到这种行为还可能有其他解释。但它确实展示了用户计算时间、内核计算时间以及我声称用于执行IO操作的时间之间的区别。
这是我关于/dev/urandom和/dev/random之间差异的来源:http://en.wikipedia.org/wiki//dev/random
更新2:
我想尝试解决Matthew建议的问题的根源可能是L2缓存丢失。Core i7具有64字节的缓存行。我不知道你对高速缓存了解多少,所以我提供一些详细信息。当您从内存中请求一个值时,CPU不仅获取那个值,还会获取其周围的所有64个字节。这意味着如果您以非常可预测的模式访问内存,比如说array[0]、array[1]、array[2]等,获取值0需要一段时间,但之后的1、2、3、4..就会快很多。直到你到了下一个缓存行为止。如果这是int数组,0将会很慢,1..15将会很快,16将会很慢,17..31将会很快,依此类推。
http://software.intel.com/en-us/forums/topic/296674
为了测试这个,我做了两个程序。它们都有一个包含1024*1024个元素的结构体数组。在一个案例中,结构体中有一个双精度浮点数,在另一个案例中它有8个双精度浮点数。一个双精度浮点数长8字节,所以在第二个程序中,我们以最糟糕的方式访问内存。第一个程序将能够很好地使用缓存。
#include <stdio.h>
#include <stdlib.h>
#define MANY_MEGS 1048576
typedef struct {
double a;
} PartialLine;
int main()
{
int i, j;
PartialLine* many_lines;
int total_bytes = MANY_MEGS * sizeof(PartialLine);
printf("Striding through %d total bytes, %d bytes at a time\n", total_bytes, sizeof(PartialLine));
many_lines = (PartialLine*) malloc(total_bytes);
PartialLine line;
double x;
for(i = 0; i < 300; i++)
{
for(j = 0; j < MANY_MEGS; j++)
{
line = many_lines[j];
x = line.a;
}
}
return 0;
}
运行此程序时,我看到以下输出:
mike@computer:~$ time ./cache_hits
Striding through 8388608 total bytes, 8 bytes at a time
real 0m3.194s
user 0m3.140s
sys 0m0.016s
mike@computer:~$
这是拥有大型结构体的程序,它们每个占用64字节的内存,而不是8字节。
#include <stdio.h>
#include <stdlib.h>
#define MANY_MEGS 1048576
typedef struct {
double a, b, c, d, e, f, g, h;
} WholeLine;
int main()
{
int i, j;
WholeLine* many_lines;
int total_bytes = MANY_MEGS * sizeof(WholeLine);
printf("Striding through %d total bytes, %d bytes at a time\n", total_bytes, sizeof(WholeLine));
many_lines = (WholeLine*) malloc(total_bytes);
WholeLine line;
double x;
for(i = 0; i < 300; i++)
{
for(j = 0; j < MANY_MEGS; j++)
{
line = many_lines[j];
x = line.a;
}
}
return 0;
}
当我运行它时,我看到这个:
mike@computer:~$ time ./cache_misses
Striding through 67108864 total bytes, 64 bytes at a time
real 0m14.367s
user 0m14.245s
sys 0m0.088s
mike@computer:~$
第二个程序——那个特意设计成缓存未命中的程序——要运行相同数量的内存访问,需要五倍的时间。
值得注意的是,在两种情况下,所有耗费的时间都花在了用户空间而不是系统空间。这意味着操作系统将程序等待数据的时间计入程序的时间,而不是计入操作系统的时间。根据这两个例子,我认为缓存未命中不太可能导致你的流逝时间比你的用户时间长得多。
更新3:
我刚看到你的更新,说压缩后的表比常规大小的表运行快了10倍左右。这也说明了(正如另一个Matthew所说的那样),你的电脑已经用完了RAM。
一旦你的程序尝试使用比你的计算机实际安装的更多的内存,它就开始交换到磁盘。这比你的程序崩溃要好,但它比RAM慢得多,可能会导致大幅减速。
明天我会尝试提供一个显示交换问题的示例。
更新4:
好的,这里有一个非常类似于之前程序的示例程序。但现在结构体的大小是4096字节,而不是8字节。总共,这个程序将使用2GB的内存,而不是64MB。我还稍微改变了一下,确保随机访问而不是逐个元素访问,这样内核就不会聪明地开始预测我的程序需要什么。缓存由硬件驱动(仅由简单的启发式算法驱动),但kswapd(内核交换守护进程)完全可能比缓存聪明得多。
#include <stdio.h>
#include <stdlib.h>
typedef struct {
double numbers[512];
} WholePage;
int main()
{
int memory_ops = 1024*1024;
int total_memory = memory_ops / 2;
int num_chunks = 8;
int chunk_bytes = total_memory / num_chunks * sizeof(WholePage);
int i, j, k, l;
printf("Bouncing through %u MB, %d bytes at a time\n", chunk_bytes/1024*num_chunks/1024, sizeof(WholePage));
WholePage* many_pages[num_chunks];
for(i = 0; i < num_chunks; i++)
{
many_pages[i] = (WholePage*) malloc(chunk_bytes);
if(many_pages[i] == 0){ exit(1); }
}
WholePage* page_list;
WholePage* page;
double x;
for(i = 0; i < 300*memory_ops; i++)
{
j = rand() % num_chunks;
k = rand() % (total_memory / num_chunks);
l = rand() % 512;
page_list = many_pages[j];
page = page_list + k;
x = page->numbers[l];
}
return 0;
}
从我调用的cache_hits到cache_misses程序中,我们看到内存大小增加了8倍,执行时间增加了5倍。当我们运行这个程序时,你会预期看到什么?它使用的内存量是cache_misses的32倍,但具有相同数量的内存访问。
mike@computer:~$ time ./page_misses
Bouncing through 2048 MB, 4096 bytes at a time
real 2m1.327s
user 1m56.483s
sys 0m0.588s
mike@computer:~$
在拥有4GB内存的计算机上,它花费了cache_misses的8倍时间和cache_hits的40倍时间。我的程序使用了50%的内存,而cache_misses只使用1.5%,cache_hits只使用0.2%。即使没有使用完整个计算机的所有内存,它的运行速度也明显变慢,足以对程序性能产生显著影响。希望这对诊断程序运行缓慢问题有所帮助。
?system.time
中的建议,查看?proc.time
。你需要澄清对那里所写内容有什么疑惑? - Joshua Ulrich