jjzjj

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

coder 2023-06-03 原文

这段代码:

#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;
}

我进行了 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)

如果你的输出不同,那么尝试再次运行可执行文件(硬盘缓存未命中)或尝试扩大迭代次数和分配字节数(感受一下)。

请注意,定时器的代码范围仅在循环中,而不是分配;那么问题又来了:为什么第二次迭代更慢?有办法解决吗?

附加信息:

  1. 该 PC 具有 2.8GHZ @ 2 核 (Intel E6300) 处理器、4GB RAM(在执行测试之前有 2.2GB 可用 RAM)和企业级 Intel SSD。
  2. 似乎在执行测试时,它写入了几个 100MB。为什么当它有足够的空闲 RAM 时它会这样做? (我释放了 1GB,然后又分配了 1GB,它不应该通过 pre-swapfile 它)

最佳答案

我的机器上的输出:

Took: 371ms (JUST PASSING BY: 0)
Took: 318ms (JUST PASSING BY: 0)

对于我希望大多数程序员在尝试您的程序时看到的内容来说,这比较典型。你可以做一个小的改变来获得完全不同的结果:

int *c = (int*)malloc(1024 * 1024 * 1024);
memset(c, 0, 1024 * 1024 * 1024);          // <== added
// etc..

在我的机器上产生:

Took: 104ms (JUST PASSING BY: 0)
Took: 102ms (JUST PASSING BY: 0)

仅通过初始化内存内容即可实现 3 倍的加速。希望它在您的机器上重现,它应该。您需要得出的第一个结论是,您一直在对与代码成本完全不同的东西进行基准测试。现代机器上非常典型的基准风险。

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

这发生在您的 result += c[ i ]; 语句中。此时踏板必须与金属接触,操作系统被迫实际使内存可用。您的程序每 4096 字节生成一个页面错误。操作系统介入并将 4096 字节的 RAM 映射到虚拟内存页面。您的程序会生成 1GB/4096 = 262,144 个页面错误。您可以得出结论,您的操作系统大约需要 400 毫秒/262144 ~= 1.5 微秒来处理页面错误。我的速度大约是它的两倍。

注意 memset() 调用 隐藏 的成本,它在您开始计时代码执行之前 生成所有页面错误。从而真正衡量代码的成本并避免否则不可避免的初始化开销。

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

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

所以,这里的基本结论:

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

关于c++ - 为什么第二次迭代大量字节时速度明显变慢?以及如何解决?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28987615/

有关c++ - 为什么第二次迭代大量字节时速度明显变慢?以及如何解决?的更多相关文章

  1. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  2. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  4. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  5. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  6. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  7. ruby - ruby 中的 TOPLEVEL_BINDING 是什么? - 2

    它不等于主线程的binding,这个toplevel作用域是什么?此作用域与主线程中的binding有何不同?>ruby-e'putsTOPLEVEL_BINDING===binding'false 最佳答案 事实是,TOPLEVEL_BINDING始终引用Binding的预定义全局实例,而Kernel#binding创建的新实例>Binding每次封装当前执行上下文。在顶层,它们都包含相同的绑定(bind),但它们不是同一个对象,您无法使用==或===测试它们的绑定(bind)相等性。putsTOPLEVEL_BINDINGput

  8. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  9. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  10. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

随机推荐