一些基本操作的 CPU 成本数量级

Posted

技术标签:

【中文标题】一些基本操作的 CPU 成本数量级【英文标题】:CPU cost order of magnitude for some basic operations 【发布时间】:2011-06-26 01:13:30 【问题描述】:

在回复that SO question 并被否决后,我想和你核实一下。

为了大致了解我编写的代码的成本,我倾向于以这种方式扩展操作。

堆分配比堆栈分配慢大约 1000 倍。 带屏/输出的IO约为1000次 比堆分配慢。 硬盘上的 IO 大约慢 1000 倍 比屏幕上的图形 IO。

您认为这是正确的假设/数量级/估计吗?

(当然,没有什么能比得上真正的应用程序分析 :-))

编辑:根据您的回答和评论,作为第一个结论,有人可能会说我的数字 1000 很大程度上被高估了。

【问题讨论】:

我严重怀疑堆分配比堆栈分配慢 1000 倍。当然,它会更慢,但不会那么慢。 这毫无意义。比较堆分配和控制台IO的速度是什么意思? @Oli:尤其是当这样的输出被缓冲时(或者我希望如此):/ @Stephane,它对环境的依赖甚至超过了控制台 I/O。在DOS中更改一个像素基本上是直接写入视频RAM。用 GDI 做同样的事情会有很大的不同。使用 DirectX 你会得到另一个结果。在 X11 中可能是另一个时间。这些时间很可能在数量级上存在很大差异。根据文件是否已经打开,将 int 写入磁盘可能会有很大不同。 堆分配比堆栈分配慢大约 1000 倍。 这对于理解内存的工作原理来说太过抽象了。从缓存中访问内存很慢,现在内存访问“是新的硬盘”。这是关于发生多少次缓存未命中来计算成本,间接次数越多,成本就越高。内存不是随机访问。微基准确实会撒谎,而且如果您不知道如何测试/测试什么,它们就会撒谎。 【参考方案1】:

如果您要进行这样的大规模概括,您可能需要考虑使用硬数据来支持它们。

我不怀疑您对大多数架构的相对效率是正确的(我说最简单是因为可能有一些我不知道的奇怪架构)但是1000x 的比率在没有证据的情况下是可疑的。

实际上,我不确定屏幕和磁盘 I/O 的相对效率,因为它可能会受到缓冲的影响。我经常发现在将输出定向到磁盘文件时,向屏幕输出数千行的程序运行得更快。

例如下面的程序:

#include <stdio.h>
int main (void) 
    int i;
    for (i = 100000; i > 0; i--)
        printf ("hello\n");
    return 0;

运行方式:

pax$ time myprog
hello
hello
:
hello

real    0m12.861s
user    0m1.762s
sys     0m2.002s

pax$ time ./myprog >/tmp/qq

real    0m0.191s
user    0m0.160s
sys     0m0.050s

换句话说,该环境(XP 下的 CygWin)中的屏幕 I/O 需要 67 倍的持续时间和 17 倍的 CPU 时间(可能是因为所有的 windows 更新)。

【讨论】:

通过屏幕 IO,我宁愿考虑彩色窗口管理环境而不是控制台。【参考方案2】:

这是另一个快速而有趣的测试,如果不是科学可靠且经过深思熟虑的测试:

char *memory;
NSLog (@"Start heap allocs");
for (int allocations = 0;  allocations < 100000000;  allocations++)

    memory = malloc (1024);
    memory[0] = 1;
    memory[1023] = memory[0] + 1;
    free(memory);

NSLog (@"End heap allocs");
NSLog (@"Start stack allocs");
for (int allocations = 0;  allocations < 100000000;  allocations++)

    char memory2 [1024];
    memory2[0] = 1;
    memory2[1023] = memory2[0] + 1;

NSLog (@"End stack allocs");

和输出:

2011-02-12 11:46:54.078 Veg Met Chilli[4589:207] Start heap allocs
2011-02-12 11:47:06.759 Veg Met Chilli[4589:207] End heap allocs
2011-02-12 11:47:06.759 Veg Met Chilli[4589:207] Start stack allocs
2011-02-12 11:47:07.057 Veg Met Chilli[4589:207] End stack allocs

自己算算,但这会使堆分配时间延长大约 42 倍。我必须强调不要引用我的话,它肯定有缺陷!最值得注意的是实际将值分配给数据所需的相对时间。

编辑:新的测试数据。

所以现在我只是为每个堆和堆栈分配调用一个方法,而不是让它们立即进入循环。结果:

2011-02-12 12:13:42.644 Veg Met Chilli[4678:207] Start heap allocs
2011-02-12 12:13:56.518 Veg Met Chilli[4678:207] End heap allocs
2011-02-12 12:13:56.519 Veg Met Chilli[4678:207] Start stack allocs
2011-02-12 12:13:57.842 Veg Met Chilli[4678:207] End stack allocs

这使得堆分配的时间只有堆栈分配的 10 倍左右。 为了使结果更准确,我还应该有一个不进行内存分配的控制方法(但至少会做一些事情以免被优化出来),并带走那段时间。接下来我会这样做......

编辑:对... 现在代码如下所示:

int control = 0;
NSLog (@"Start heap allocs");
for (int allocations = 0;  allocations < 100000000;  allocations++)

    control += [self HeapAlloc];

NSLog (@"End heap allocs");
NSLog (@"Start stack allocs");
for (int allocations = 0;  allocations < 100000000;  allocations++)

    control += [self StackAlloc];

NSLog (@"End stack allocs");
NSLog (@"Start no allocs");
for (int allocations = 0;  allocations < 100000000;  allocations++)

    control += [self NoAlloc];

NSLog (@"End no allocs");
NSLog (@"%d", control);


-(int) HeapAlloc

    int controlCalculation = rand();

    char *memory = malloc (1024);
    memory[0] = 1;
    memory[1023] = memory[0] + 1;
    free(memory);

    return controlCalculation;


-(int) StackAlloc

    int controlCalculation = rand();

    char memory [1024];
    memory[0] = 1;
    memory[1023] = memory[0] + 1;   

    return controlCalculation;


-(int) NoAlloc

    int controlCalculation = rand();

    return controlCalculation;

结果是:

2011-02-12 12:31:32.676 Veg Met Chilli[4816:207] Start heap allocs
2011-02-12 12:31:47.306 Veg Met Chilli[4816:207] End heap allocs
2011-02-12 12:31:47.306 Veg Met Chilli[4816:207] Start stack allocs
2011-02-12 12:31:49.458 Veg Met Chilli[4816:207] End stack allocs
2011-02-12 12:31:49.459 Veg Met Chilli[4816:207] Start no allocs
2011-02-12 12:31:51.325 Veg Met Chilli[4816:207] End no allocs

所以控制时间是1.866秒。 把它从分配时间中拿走给出: 堆叠 0.286 秒 堆 12.764 秒

因此,堆分配所花费的时间大约是堆栈分配的 45 倍。

谢谢你,晚安! :)

【讨论】:

我不完全确定这是每次循环中的堆栈分配。我已经看到生成的汇编代码会在函数启动时为类似 once 的东西递减堆栈。这当然是一个有效的优化——唯一的要求是该变量仅在该块内可用,而不是每次循环时堆栈都会上下移动 1K。但是,由于添加到堆栈指针的操作通常非常快,因此您的数字可能已经足够接近了。 是的,编译器优化可能会以多种方式使该测试无效! @paxdiablo,我刚刚检查了没有优化的 GCC 程序集输出,它确实在函数开始时分配了所有内容。但这并不重要,因为即使更新循环变量也可能比更改堆栈指针寄存器花费 10 倍以上的时间。 也许我应该调用一个函数来执行分配? 所有问题的答案都是42【参考方案3】:

第一点取决于很多事情,真的。如果你的内存用完了,那么在堆上分配一些东西可能需要几分钟。另一方面,堆栈可能已经在那个时候被分配了。

第二点取决于所使用的终端。输出到 DOS 屏幕是一回事,输出到 Windows 控制台窗口是另一回事,xterm 也和它们完全不同。

至于第三点,我宁愿说它与现代硬盘相反。他们可以轻松处理每秒兆数,你怎么能想象在这么短的时间内输出这么多到任何终端?不过,对于少量数据,您可能是对的,因为硬盘 I/O 可能需要一些时间来准备。

【讨论】:

我选择这个作为最终答案,因为它是最深思熟虑的......虽然其他两个也足够有趣......并且正在适度......

以上是关于一些基本操作的 CPU 成本数量级的主要内容,如果未能解决你的问题,请参考以下文章

进程和线程的一些问题

原子操作成本

大家讨论一下SAP如何计算产品成本

零成本搭建WDS轻量级系统批量部署环境视频课程

性能优化之基础资源cpu&内存(JVM)

别再纠结线程池大小/线程数量了,没有固定公式的