C++ C++;暖性病媒

C++ C++;暖性病媒,c++,std,stdvector,C++,Std,Stdvector,为什么第二次填充std::vector更快?即使有空间留给乞丐 int total = 1000000; struct BaseClass { float m[16]; int id; BaseClass(int _id) { id = _id; } }; int main() { std::vector<BaseClass> ar; ar.reserve(total); { auto t_start = std::chrono::high_

为什么第二次填充std::vector更快?即使有空间留给乞丐

int total = 1000000;

struct BaseClass {
  float m[16];
  int id;

  BaseClass(int _id) { id = _id; }
};

int main() {

  std::vector<BaseClass> ar;
  ar.reserve(total);

  {
    auto t_start = std::chrono::high_resolution_clock::now();
    for (int var = 0; var < total; ++var) {
      ar.emplace_back(var);
    }
    auto t_end = std::chrono::high_resolution_clock::now();
    std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(
                     t_end - t_start).count() << "\n";
    ar.clear();
  }

  {
    auto t_start = std::chrono::high_resolution_clock::now();
    for (int var = 0; var < total; ++var) {
      ar.emplace_back(var);
    }
    auto t_end = std::chrono::high_resolution_clock::now();
    std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(
                     t_end - t_start).count() << "\n";
    ar.clear();
  }

  {
    auto t_start = std::chrono::high_resolution_clock::now();
    for (int var = 0; var < total; ++var) {
      ar.emplace_back(var);
    }
    auto t_end = std::chrono::high_resolution_clock::now();
    std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(
                     t_end - t_start).count() << "\n";
    ar.clear();
  }

  return 0;
}
inttotal=1000000;
结构基类{
浮动m[16];
int-id;
基类(int _id){id=_id;}
};
int main(){
std::向量ar;
应收账款准备金(总额);
{
自动启动=标准::时钟::高分辨率时钟::现在();
对于(int-var=0;varstd::cout第一次运行比其他两次运行慢的原因是运行时尚未从操作系统获取内存页

我检测了您的程序,以输出任务在开始时以及在上述三个阶段中的每个阶段之后发生的主要和次要页面错误的数量。(注意:这在Linux上有效。不知道它是否在您所使用的任何操作系统上都有效。)代码:

注意:更新为最新版本,将
reserve()
移到顶部并封装在自己的
getrusage
调用中

请注意,第一个测量的for循环发生了16000多个小故障。这些故障使应用程序可以使用内存,并导致运行时间变慢。此后不会发生其他故障。相反,
reserve()
调用本身只发生了24个小故障

在大多数现代虚拟内存操作系统中,操作系统实现了延迟内存分配,即使其上运行的软件没有。当运行时从操作系统请求额外内存时,操作系统会记录该请求。如果请求成功,则运行时现在有一个新的可用虚拟地址范围。(细节取决于调用的API和操作系统,但本质相同。)操作系统可能会将虚拟地址范围指向一个标记为只读的零填充页面

操作系统不一定使这些页面立即可供任务使用。相反,操作系统会等到任务实际尝试写入分配给它的内存。此时,操作系统会分配一个物理页面,以支持分配给任务的虚拟页面。这将注册为“小错误”用UNIX的说法,这个过程可能会很昂贵

您的任务所度量的是懒惰的分配

为了证明这一点,我还对应用程序进行了
扫描

getrusage(RUSAGE_SELF, {ru_utime={0, 0}, ru_stime={0, 0}, ...}) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe3aa339000
write(1, "minflt: 328 majflt: 0\n", 22) = 22
mmap(NULL, 68001792, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe3a551c000
getrusage(RUSAGE_SELF, {ru_utime={0, 0}, ru_stime={0, 0}, ...}) = 0
write(1, "minflt: 352 majflt: 0\n", 22) = 22
write(1, "52\n", 3)                     = 3
getrusage(RUSAGE_SELF, {ru_utime={0, 30000}, ru_stime={0, 20000}, ...}) = 0
write(1, "minflt: 16953 majflt: 0\n", 24) = 24
write(1, "20\n", 3)                     = 3
getrusage(RUSAGE_SELF, {ru_utime={0, 50000}, ru_stime={0, 20000}, ...}) = 0
write(1, "minflt: 16953 majflt: 0\n", 24) = 24
write(1, "15\n", 3)                     = 3
getrusage(RUSAGE_SELF, {ru_utime={0, 70000}, ru_stime={0, 20000}, ...}) = 0
write(1, "minflt: 16953 majflt: 0\n", 24) = 24
munmap(0x7fe3a551c000, 68001792)        = 0
exit_group(0)                           = ?

如您所见,任务分配了内存,在前两个代码> GETROUSS系统调用之间有一个<代码> MMAP>代码>调用,但是,该步骤只发生了24个小故障。因此,即使C++不懒惰,Linux也懒惰地将任务存储到内存中。 具体地说,第一个

mmap
调用似乎为第一个
写入
mesage分配一个I/O缓冲区。第二个
mmap
调用(分配68001792字节)发生在第二个
getrusage
调用之前。然而,在这次运行中,您只能看到这两个调用之间发生了24个额外的故障


你们中目光锐利的人会注意到,这次运行的数字与我上面显示的数字略有不同。我已经运行过多次此可执行文件,每次的数字都略有变化。但是,它们总是在同一个总体范围内。

因为您的测试不是独立的。.第一次运行会将内容放入缓存,一个第二次运行则不必向内存写入任何内容。您使用的
emplace\u back
方法错误,
emplace\u back
应该“就地”构建对象,你甚至没有用
std::move
施放
var
,你到底想做什么?@user2485710-我在向量中动态构建了一堆对象。比我再次这样做要慢。为什么第一次慢了?@Thomas:它可能还触发了从操作系统获取内存的操作。
reserve()
可能已获得拥有内存页的权限,但实际触摸内存页会导致内存页出错。@tower120:处理器的硬件内存缓存。@luk32:大多数操作系统都会执行延迟分配。如果调用
malloc
new
并请求一些较大的内存(超出当前堆),C运行时向内核请求空间,内核记录下来并说“是的,当然。”直到程序尝试访问这些页面并出现页面错误,操作系统才会去寻找给任务的内存。在作弊聊天期间,我想到了另一个问题:调用
std::chrono
触发的变量可能是
静态的
?这将解释为什么它们会减慢速度wn(间接)第一次。他们可能会有首次使用惩罚,尽管在第一节之前测量一个虚拟节可以很容易地排除。我还认为这更多地取决于
分配器,而不是容器本身,这也意味着这个问题的答案可以根据alloca的类型而改变为
std::vector
选择的对象。分页是一种非常特定于操作系统的行为。@user2485710:每当总进程虚拟内存大小增加时,操作系统都必须为您提供分页,而不管
分配器如何。
分配器在具有稳定resi的长期运行进程中更为重要凹痕工作集大小。@Joe Z-请查看我的更新代码。并从度量值中移出“保留”。我们只想看到“emplace_back”结果。
minflt: 343 majflt: 0
minflt: 367 majflt: 0
48    minflt: 16968 majflt: 0
16
minflt: 16968 majflt: 0
15
minflt: 16968 majflt: 0
getrusage(RUSAGE_SELF, {ru_utime={0, 0}, ru_stime={0, 0}, ...}) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe3aa339000
write(1, "minflt: 328 majflt: 0\n", 22) = 22
mmap(NULL, 68001792, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fe3a551c000
getrusage(RUSAGE_SELF, {ru_utime={0, 0}, ru_stime={0, 0}, ...}) = 0
write(1, "minflt: 352 majflt: 0\n", 22) = 22
write(1, "52\n", 3)                     = 3
getrusage(RUSAGE_SELF, {ru_utime={0, 30000}, ru_stime={0, 20000}, ...}) = 0
write(1, "minflt: 16953 majflt: 0\n", 24) = 24
write(1, "20\n", 3)                     = 3
getrusage(RUSAGE_SELF, {ru_utime={0, 50000}, ru_stime={0, 20000}, ...}) = 0
write(1, "minflt: 16953 majflt: 0\n", 24) = 24
write(1, "15\n", 3)                     = 3
getrusage(RUSAGE_SELF, {ru_utime={0, 70000}, ru_stime={0, 20000}, ...}) = 0
write(1, "minflt: 16953 majflt: 0\n", 24) = 24
munmap(0x7fe3a551c000, 68001792)        = 0
exit_group(0)                           = ?