为什么第二次迭代大量字节的速度明显变慢?以及如何解决它?

Lyi*_*Sky 13 c++ performance

这段代码:

#include <memory>
#include <time.h>
#include <chrono>
#include <thread>
#include <stdio.h>
#include <stdlib.h>

void Test( ) {
#define current_milliseconds std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now( ).time_since_epoch( ) ).count( )
    int *c = ( int* )malloc( 1024 * 1024 * 1024 );
    int result = 0;
    auto millis = -current_milliseconds;
    //clock_t timer = -clock( );
    for ( int i = 0 ; i < 1024 * 1024 * 256 /* 1024 / 4 */; ++i )
        result += c[ i ];
    millis += current_milliseconds;
    printf( "Took: %ldms (JUST PASSING BY: %d)\n", millis, result );
    free( c );
#undef current_milliseconds
}

int main( ) {
    std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
    Test( );
    std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
    Test( );
    return -1;
}
Run Code Online (Sandbox Code Playgroud)

我进行了7次测试并给出了最后6项输出:

Took: 502ms (JUST PASSING BY: 0)
Took: 607ms (JUST PASSING BY: 0)

Took: 480ms (JUST PASSING BY: 0)
Took: 588ms (JUST PASSING BY: 0)

Took: 492ms (JUST PASSING BY: 0)
Took: 562ms (JUST PASSING BY: 0)

Took: 506ms (JUST PASSING BY: 0)
Took: 558ms (JUST PASSING BY: 0)

Took: 470ms (JUST PASSING BY: 0)
Took: 555ms (JUST PASSING BY: 0)

Took: 510ms (JUST PASSING BY: 0)
Took: 562ms (JUST PASSING BY: 0)
Run Code Online (Sandbox Code Playgroud)

如果您的输出不同,则尝试再次运行可执行文件(硬盘缓存未命中)或尝试扩大迭代次数和分配的字节数(有一种感觉).

请注意,定时器的代码范围仅在循环上而不是分配; 然后再次提出问题:为什么第二次迭代会变慢?有办法解决吗?

附加信息:

  1. 该PC拥有奔腾2.8GHZ @ 2核心(Intel E6300)处理器,4GB RAM(执行测试前有2.2GB可用RAM)和企业级英特尔SSD.
  2. 似乎在执行测试时,它写了一些100MB.为什么它有足够的可用内存?(我释放1GB然后分配另外1GB,它不应该传递pre-swapfile吧)

Han*_*ant 20

我机器上的输出:

采取:371毫秒(只能通过:0)
采取:318毫秒(只是通过:0)

对于我希望大多数程序员在尝试您的程序时看到的更为典型.您可以进行一些小改动以获得截然不同的结果:

int *c = (int*)malloc(1024 * 1024 * 1024);
memset(c, 0, 1024 * 1024 * 1024);          // <== added
// etc..
Run Code Online (Sandbox Code Playgroud)

哪个在我的机器上生产:

采取:104毫秒(只能通过:0)
采取:102毫秒(只能通过:0)

一个胖胖的x3加速,只是从初始化内存内容.希望它能在您的机器上重现,它应该.您需要得出的第一个结论是,您已经做了一些与代码成本完全不同的基准测试.现代机器上非常典型的基准危险.

这是需求分页虚拟内存操作系统的行为.像Windows,Linux或OSX.你的malloc()调用从未实际分配任何内存,它只是保留了地址空间.只是处理器的数字,每个4096字节的内存有一个.直到稍后实际解决时,您才需要支付使用内存的费用.当需求分页功能发挥作用时.

您的result += c[ i ];陈述中会发生这种情况 此时踏板必须符合金属,并且操作系统必须实际上使存储器可用.程序生成页面错误每4096个字节.操作系统介入并将4096字节的RAM映射到虚拟内存页面.您的程序生成1GB/4096 = 262,144的页面错误.您可以得出结论,您的操作系统需要大约400ms/262144~ = 1.5微秒才能处理页面错误.我的速度快了两倍.

注意memset()调用如何隐藏该成本,它您开始执行代码之前生成所有这些页面错误.从而真正衡量代码的成本并避免不可避免的初始化开销.

第一次运行需要多长时间取决于操作系统使RAM可用的速度.从一次尝试到另一次尝试,实际测量可能会有很大差异,这取决于有多少其他进程映射了RAM页面.如果操作系统需要首先找到空间并取消映射页面,在页面文件中保留其内容,可能需要一段时间.

第二次运行需要多长时间取决于操作系统可以多快地回收RAM页面,如果没有足够的可用于映射另一个千兆字节.我的问题不多,我有8 GB的RAM,现在只使用5.6.它们需要零初始化,典型操作系统上的低优先级任务.

那么,基本结论如下:

  • 您正在测量初始化成本,它并不代表您的程序在继续使用内存时将如何受到影响.按原样,您的基准测试不会告诉您任何信息.
  • 您观察到的差异是操作系统实现细节,它与您的程序没有任何关系.