- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
在对某些代码进行基准测试时,我发现即使是最无害的代码更改,其执行时间也会有所不同。
我试图将下面的代码归结为最小的测试用例,但它仍然相当冗长(对此我深表歉意)。几乎任何改变都会极大地影响基准测试结果。
#include <string>
#include <vector>
#include <iostream>
#include <random>
#include <chrono>
#include <functional>
constexpr double usec_to_sec = 1000000.0;
// Simple convenience timer
class Timer
{
std::chrono::high_resolution_clock::time_point start_time;
public:
Timer() : start_time(std::chrono::high_resolution_clock::now()) { }
int64_t operator()() const {
return static_cast<int64_t>(
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now()-start_time).count()
);
}
};
// Convenience random number generator
template <typename T>
class RandGen
{
mutable std::default_random_engine generator;
std::uniform_int_distribution<T> distribution;
constexpr unsigned make_seed() const {
return static_cast<unsigned>(std::chrono::system_clock::now().time_since_epoch().count());
}
public:
RandGen(T min, T max) : generator(make_seed()), distribution(min, max) { }
T operator ()() { return distribution(generator); }
};
// Printer class
class Printer
{
std::string filename;
template <class S>
friend Printer &operator<<(Printer &, S &&s);
public:
Printer(const char *filename) : filename(filename) {}
};
template <class S>
Printer &operator<<(Printer &pm, S &&s) {
std::cout << s;
return pm;
}
// +------------+
// | Main Stuff |
// +------------+
void runtest(size_t run_length)
{
static RandGen<size_t> word_sz_generator(10, 20);
static RandGen<int> rand_char_generator(0, 25);
size_t total_char_count = 0;
std::vector<std::string> word_list;
word_list.reserve(run_length);
Printer printer("benchmark.dat");
printer << "Running test... ";
Timer timer; // start timer
for (auto i = 0; i < run_length; i++) {
size_t word_sz = word_sz_generator();
std::string word;
for (auto sz = 0; sz < word_sz; sz++) {
word.push_back(static_cast<char>(rand_char_generator())+'a');
}
word_list.emplace_back(std::move(word));
total_char_count += word_sz;
}
int64_t execution_time_usec = timer(); // stop timer
printer << /*run_length*/ word_list.size() << " words, and "
<< total_char_count << " total characters, were built in "
<< execution_time_usec/usec_to_sec << " seconds.\n";
}
int main(int argc, char **argv)
{
constexpr size_t iterations = 30;
constexpr size_t run_length = 50000000;
for (auto i = 0; i < iterations; i++)
runtest(run_length);
return EXIT_SUCCESS;
}
Timer
, 只是一个方便的小类(为了简洁起见,故意没有很好的功能)用于计时代码。
RandGen
(它只是生成随机值),但是任何试图从测试代码中排除它的尝试都会使问题自动神奇地消失。所以,我怀疑这个问题与它有关。但我无法弄清楚如何。
Printer
对于这个问题似乎完全没有必要,但同样,包括它似乎加剧了这个问题。
main()
(它只是运行测试)和
runtest()
.
runtest()
是可怕的,所以请不要从“干净的代码”的角度来看它。以任何方式更改它(例如将内部
for loop
移动到它自己的函数)都会导致基准测试结果发生变化。最简单、最令人困惑的例子是最后一行:
printer << /*run_length*/ word_list.size() << " words, and "
<< total_char_count << " total characters, were built in "
<< execution_time_usec/usec_to_sec << " seconds.\n";
run_length
和
word_list.size()
是相同的。 vector 的大小
word_list
由
run_length
定义.但是,如果我按原样运行代码,我得到的平均执行时间为
9.8 秒 , 而如果我取消注释
run_length
并注释掉
word_list.size()
,执行时间实际上增加到平均
10.6 秒 .我无法理解如此微不足道的代码更改如何在如此程度上影响整个程序的时间。
printer << /*run_length*/ word_list.size() << " words, and "
<< total_char_count << " total characters, were built in "
<< execution_time_usec/usec_to_sec << " seconds.\n";
printer << run_length /*word_list.size()*/ << " words, and "
<< total_char_count << " total characters, were built in "
<< execution_time_usec/usec_to_sec << " seconds.\n";
Running test... 50000000 words, and 750000798 total characters, were built in 9.83379 seconds.
Running test... 50000000 words, and 749978210 total characters, were built in 9.84541 seconds.
Running test... 50000000 words, and 749996688 total characters, were built in 9.87418 seconds.
Running test... 50000000 words, and 749995415 total characters, were built in 9.85704 seconds.
Running test... 50000000 words, and 750017699 total characters, were built in 9.86186 seconds.
Running test... 50000000 words, and 749998680 total characters, were built in 9.83395 seconds.
...
Running test... 50000000 words, and 749988517 total characters, were built in 10.604 seconds.
Running test... 50000000 words, and 749958011 total characters, were built in 10.6283 seconds.
Running test... 50000000 words, and 749994387 total characters, were built in 10.6374 seconds.
Running test... 50000000 words, and 749995242 total characters, were built in 10.6445 seconds.
Running test... 50000000 words, and 749988379 total characters, were built in 10.6543 seconds.
Running test... 50000000 words, and 749969532 total characters, were built in 10.6722 seconds.
...
std::string filename
来自 Printer
的成员对象类产生不同的基准测试结果 - 在这样做的情况下,消除(或减少到微不足道的比例)上面提供的两个基准之间的差异。 最佳答案
您可能会遇到某种代码对齐效果。大多数情况下,现代 x86-64 CPU 在对齐方面相当稳健,但对齐会影响分支预测器中的分支相互混淆(如提到的 @rcgldr)和各种前端效果。
见 https://agner.org/optimize/ ,以及 the x86 tag wiki 中的性能链接.但老实说,我认为这里没有任何有用的解释,除了您发现您的循环对对齐效果敏感,无论是来自前端还是来自分支预测。这意味着即使在主程序中不同对齐的相同机器代码也可能具有不同的性能。
这是众所周知的现象。关于 Code alignment in one object file is affecting the performance of a function in another object file 的回答有一些关于对齐如何重要的一般性评论,另见 Why would introducing useless MOV instructions speed up a tight loop in x86_64 assembly?某处有一篇关于以不同顺序链接目标文件如何影响性能的文章(这是工具链的意外影响),但我找不到它。
您可以使用硬件性能计数器来测量分支预测错误率,看看这是否解释了为什么一个版本比另一个版本慢。 或者如果有其他一些前端效果。
但不幸的是,您无能为力;微不足道的源差异,如果它们影响汇编,将改变一切的对齐方式。
您有时可以通过用无分支代码替换分支来重新设计对分支预测不那么敏感的东西 .例如总是生成 16 个字节的随机字母,并将其截断为随机长度。 (复制它时可能不可避免地会出现一些大小分支,除非创建一个 16 字节的 std::string
然后截断它可以是无分支的。)
您可以使用 SIMD 加快速度,例如使用矢量化的 PRNG,如 with an SSE2 or AVX2 xorshift+
一次生成 16 个字节的随机字母。 (通过压缩字节操作有效地获得统一的 0..25 分布可能很棘手,但可能与我在 3.9GHz Skylake 上每 ~0.03 秒 generate 1GiB of space-separated random ASCII digits 使用的 0..9 分布相同的技术会很有用。但是,它不是完全均匀分布的,因为 65536 % 10 有余数(如 65536/25),但您可能可以更改质量与速度的权衡,并且仍然运行得很快。)
比较两个版本的编译器输出
runtest
中两个版本的内部循环的 asm功能基本相同 ,至少如果编译器asm输出我们看到on the Godbolt compiler explorer与您在 MSVC 的可执行文件中实际获得的内容相匹配。 (与 gcc/clang 不同,它的 asm 输出不一定能组装成工作目标文件。)如果您的真实发布版本进行了任何可能内联某些库代码的链接时优化,它可能会在最终做出不同的优化选择可执行。
我输入了 #ifdef
所以我可以使用 -DUSE_RL
有两个 MSVC 2017 输出,它们以不同的方式构建相同的源,并将这些 asm 输出提供给差异 Pane 。 ( 差异 Pane 位于我链接的凌乱布局的底部;单击其上的全屏框以仅显示 。)
整个功能的唯一区别是:
mov edx, DWORD PTR _tls_index
和 mov QWORD PTR run_length$GSCopy$1$[rbp-121], rcx
在只运行一次的函数的顶部。 (但不在代码大小中,因此它们不会影响以后的对齐)。这应该对以后的代码没有影响,他们最终对架构状态进行了相同的更改,只是使用了一个不再使用的不同的临时注册。 [rbp + disp8]
寻址模式。 mov rdx, QWORD PTR word_list$[rbp-113]
sub rdx, QWORD PTR word_list$[rbp-121] ; word_list.size() = end - start
...
sar rdx, 5 ; >> 5 arithmetic right shift
mov rdx, rsi ; copy run_length from another register
npad 7
用于在函数底部附近的分支目标之前对齐(在 call _Xtime_get_ticks
之后),在上述代码差异之后。 runtest
, word_list.size()
版本包含 ??$?6_K@@YAAEAVPrinter@@AEAV0@$QEA_K@Z PROC
的代码功能对于使用
run_length
的版本,它不会出现在任何地方. (C++ 名称修改将类型转换为函数的 asm 名称中的时髦字符。)这是为
class Printer
做的事情。 .
std::string filename
来自
Printer
删除了代码生成差异。那么该功能可能会随着这种变化而消失。 IDK 为什么 MSVC 决定发布它,更不用说只在一个版本与另一个版本中发布了。
g++ -O3
没有那种代码生成差异,这就是为什么您看不到差异的原因。 (假设您的 VM 是硬件虚拟化,g++ 生成的机器代码仍然在 CPU 上本地运行。从操作系统获取新的内存页面在 VM 中可能需要更长的时间,但在循环中花费的主要时间可能是在此代码的用户空间中。)
<source>:72:24: warning: comparison of integer expressions of different signedness: 'int' and 'size_t' {aka 'long unsigned int'} [-Wsign-compare]
for (auto i = 0; i < run_length; i++) {
~~^~~~~~~~~~~~
关于C++ 代码执行时间随不应该引入任何额外工作的小的源代码更改而变化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51663989/
在开始之前,我想指出的是,我已经通过Google进行了一次诚实而真实的搜索,涉及范围很广,无法找到。 我需要(对于我正在开发的项目)所有Delphi(从2007年到最新发布的版本,我不再支持任何早于2
我正在使用 RPM 将 Liquibase 数据库迁移添加到我们当前的产品部署中,并正在寻找一些有关如何实现我的预期目标的建议/技巧。 最好,RPM 能够安装在全新且 Shiny 的开发人员环境以及现
我目前正在使用一本书学习 UITableViewCell。为了在滚动时重用单元格,作者要求修改原始代码以包含一个if()。检查特定重用标识符的单元格是否存在的语句。但是,在添加 if() 之后语句,X
在 C++ 中引入 protected 访问说明符背后的基本原理是什么。举个例子会有帮助。 最佳答案 对于这类问题,我推荐 Bjarne Stroustrup 的The Design And Evol
我正在尝试使用模板参数中给定的维度和类型创建一个可重用的矩阵类。结构本身就是: template struct Matrix { T elements[N* M]; }; 当我尝试实现矩阵乘
我有一个简单的查询: $query1="SELECT * FROM wp_users WHERE now() < (last_login + INTERVAL 6 month)"; $resu
在 Ioke doc 中,ISpec 测试包含在文档中,参见 ioke.org/dok/index.html 这如何用 Ruby 的 RSpec 和 RDoc(或 SDoc)来完成?我找不到任何命令行
在客户端/服务器通信中,我看到来自客户端的 TCP ZeroWindow。 在这种情况之后预期的场景是什么(设置和发送什么标志)? 以下是我可能得到的日志。在这种情况下,服务器发送 RST 数据包来终
来自wikipedia关于 Lambda 函数和表达式的文章: users will often wish to define predicate functions near the place w
我有一个由父 POM 和几个子模块组成的 Maven 项目。它在 Intellij 中编译和运行良好(我假设它使用 javac 而不是 Maven)。 当我运行 maven clean install
所以我刚开始使用 d3.js,但我一直收到 JavaScript 错误,我不知道为什么。我刚刚用 svg 创建了三个圆圈,想用 d3 选择它们。这是我的代码:
Objective C 引入了一种称为 ARC 的技术,以将开发人员从内存管理的负担中解放出来。听起来不错,如果g++也有这个功能,我想C++开发者会很高兴的。 ARC allows you to p
在 package.json 添加 "font-awesome": "^4.7.0" 执行 npm install 在 main.js 引入
为什么 WSDL 引入 wsdl:message?和消息部分? 与在操作参数(输入、输出、故障)中直接使用 XSD 相比,他们可以带来什么优势? 它们(带有 wsdl 消息部分的 wsdl 消息)如何
I already read doc here : https://github.com/laravel/framework/pull/25997 我想知道的是使用 withCount()我们只是加载
我已经为此苦苦挣扎了一段时间,但不太明白发生了什么。我有一个包含 Sides(通常是 2 个)的 Card 实体 - 并且 Cards 和 Sides 都有一个 Stage。我正在使用 EF Code
下面的 swiftUI 代码在 iOS13 上运行良好,但是在使用 iOS14 进行测试时,我在尝试显示模式表时遇到了由强制解包选项引起的 fatal error 。据我所知,工作表不应该尝试为 se
出于个人原因,我需要记忆一下 jsp 上的一些事情 :) 我有一个简单的登录页面: Login First name:
据我了解,PYTHONCASEOK 选项允许通过不区分大小写的匹配来导入模块。但是,由于 python 中的几乎所有内容都区分大小写,为什么它必须启用此选项以实现更惰性的写入。 还有什么介绍的理由吗?
全新的早午餐(和 bower )。我通过 bower 安装了 Bootstrap,我有以下早午餐配置文件: exports.config = # See http://brunch.io/#doc
我是一名优秀的程序员,十分优秀!