- android - RelativeLayout 背景可绘制重叠内容
- android - 如何链接 cpufeatures lib 以获取 native android 库?
- java - OnItemClickListener 不起作用,但 OnLongItemClickListener 在自定义 ListView 中起作用
- java - Android 文件转字符串
我在一段 C/C++ 代码中遇到了一段极其奇怪的性能行为,正如标题中所建议的,我不知道如何解释。
这是一个与我发现的一样接近最小的工作示例 [编辑:请参阅下面的较短示例]:
#include <stdio.h>
#include <stdlib.h>
#include <complex>
using namespace std;
const int pp = 29;
typedef complex<double> cdbl;
int main() {
cdbl ff[pp], gg[pp];
for(int ii = 0; ii < pp; ii++) {
ff[ii] = gg[ii] = 1.0;
}
for(int it = 0; it < 1000; it++) {
cdbl dual[pp];
for(int ii = 0; ii < pp; ii++) {
dual[ii] = 0.0;
}
for(int h1 = 0; h1 < pp; h1 ++) {
for(int h2 = 0; h2 < pp; h2 ++) {
cdbl avg_right = 0.0;
for(int xx = 0; xx < pp; xx ++) {
int c00 = xx, c01 = (xx + h1) % pp, c10 = (xx + h2) % pp,
c11 = (xx + h1 + h2) % pp;
avg_right += ff[c00] * conj(ff[c01]) * conj(ff[c10]) * gg[c11];
}
avg_right /= static_cast<cdbl>(pp);
for(int xx = 0; xx < pp; xx ++) {
int c01 = (xx + h1) % pp, c10 = (xx + h2) % pp,
c11 = (xx + h1 + h2) % pp;
dual[xx] += conj(ff[c01]) * conj(ff[c10]) * ff[c11] * conj(avg_right);
}
}
}
for(int ii = 0; ii < pp; ii++) {
dual[ii] = conj(dual[ii]) / static_cast<double>(pp*pp);
}
for(int ii = 0; ii < pp; ii++) {
gg[ii] = dual[ii];
}
#ifdef I_WANT_THIS_TO_RUN_REALLY_FAST
printf("%.15lf\n", gg[0].real());
#else // I_WANT_THIS_TO_RUN_REALLY_SLOWLY
#endif
}
printf("%.15lf\n", gg[0].real());
return 0;
}
以下是在我的系统上运行它的结果:
me@mine $ g++ -o test.elf test.cc -Wall -Wextra -O2
me@mine $ time ./test.elf > /dev/null
real 0m7.329s
user 0m7.328s
sys 0m0.000s
me@mine $ g++ -o test.elf test.cc -Wall -Wextra -O2 -DI_WANT_THIS_TO_RUN_REALLY_FAST
me@mine $ time ./test.elf > /dev/null
real 0m0.492s
user 0m0.490s
sys 0m0.001s
me@mine $ g++ --version
g++ (Gentoo 4.9.4 p1.0, pie-0.6.4) 4.9.4 [snip]
这段代码计算的内容并不重要:它只是对长度为 29 的数组进行的大量复杂算术运算。它已从我关心的大量复杂算术运算中“简化”而来。
因此,行为似乎如标题中所述:如果我将此 print 语句放回原处,代码会变得更快。
我玩过一点:例如,打印一个常量字符串并不能加快速度,但打印时钟时间可以。有一个非常明确的阈值:代码要么快要么慢。
我考虑了一些奇怪的编译器优化是否起作用的可能性,这可能取决于代码是否有副作用。但是,如果是这样的话,它是非常微妙的:当我查看反汇编的二进制文件时,它们看起来是相同的,只是其中一个有一个额外的 print 语句并且它们使用不同的可互换寄存器。我可能(必须?)错过了一些重要的事情。
我完全无法解释地球可能造成这种情况的原因。更糟糕的是,它确实影响了我的生活,因为我经常运行相关代码,并且四处插入额外的打印语句感觉并不是一个好的解决方案。
任何似是而非的理论都将非常受欢迎。如果您可以解释“您的计算机坏了”这样的回答是可以接受的。
更新:很抱歉问题的长度越来越长,我已将示例缩小为
#include <stdio.h>
#include <stdlib.h>
#include <complex>
using namespace std;
const int pp = 29;
typedef complex<double> cdbl;
int main() {
cdbl ff[pp];
cdbl blah = 0.0;
for(int ii = 0; ii < pp; ii++) {
ff[ii] = 1.0;
}
for(int it = 0; it < 1000; it++) {
cdbl xx = 0.0;
for(int kk = 0; kk < 100; kk++) {
for(int ii = 0; ii < pp; ii++) {
for(int jj = 0; jj < pp; jj++) {
xx += conj(ff[ii]) * conj(ff[jj]) * ff[ii];
}
}
}
blah += xx;
printf("%.15lf\n", blah.real());
}
printf("%.15lf\n", blah.real());
return 0;
}
我可以让它更小,但机器代码已经是可管理的了。如果我将对应于第一个 printf 的 callq 指令的二进制文件的 5 个字节 更改为 0x90,则执行从快变慢。
编译后的代码对 __muldc3() 的函数调用非常繁重。我觉得这一定与 Broadwell 架构处理或不处理这些跳跃的方式有关:两个版本运行相同数量的指令,因此指令/周期存在差异(大约 0.16 对大约 2.8)。
此外,编译 -static 可以再次加快速度。
进一步无耻更新:我意识到我是唯一可以玩这个的人,所以这里有更多的观察结果:
这似乎是第一次调用任何库函数——包括我编写的一些什么都不做的愚蠢函数——使执行进入缓慢状态。随后对 printf、fprintf 或 sprintf 的调用以某种方式清除了状态,执行速度再次加快。所以,重要的是,第一次调用 __muldc3() 时我们进入慢速状态,下一个 {,f,s}printf 重置一切。
一旦库函数被调用一次,并且状态被重置,该函数就变得自由了,您可以随意使用它而无需更改状态。
所以,例如:
#include <stdio.h>
#include <stdlib.h>
#include <complex>
using namespace std;
int main() {
complex<double> foo = 0.0;
foo += foo * foo; // 1
char str[10];
sprintf(str, "%c\n", 'c');
//fflush(stdout); // 2
for(int it = 0; it < 100000000; it++) {
foo += foo * foo;
}
return (foo.real() > 10.0);
}
很快,但是注释掉第 1 行或取消注释第 2 行会使它再次变慢。
必须相关的是,第一次运行库调用时,PLT 中的“蹦床”被初始化为指向共享库。因此,也许这种动态加载代码会以某种方式将处理器前端留在一个糟糕的地方,直到它被“拯救”。
最佳答案
郑重声明,我终于弄明白了。
事实证明,这与 AVX–SSE 转换惩罚有关。引用this exposition from Intel :
When using Intel® AVX instructions, it is important to know that mixing 256-bit Intel® AVX instructions with legacy (non VEX-encoded) Intel® SSE instructions may result in penalties that could impact performance. 256-bit Intel® AVX instructions operate on the 256-bit YMM registers which are 256-bit extensions of the existing 128-bit XMM registers. 128-bit Intel® AVX instructions operate on the lower 128 bits of the YMM registers and zero the upper 128 bits. However, legacy Intel® SSE instructions operate on the XMM registers and have no knowledge of the upper 128 bits of the YMM registers. Because of this, the hardware saves the contents of the upper 128 bits of the YMM registers when transitioning from 256-bit Intel® AVX to legacy Intel® SSE, and then restores these values when transitioning back from Intel® SSE to Intel® AVX (256-bit or 128-bit). The save and restore operations both cause a penalty that amounts to several tens of clock cycles for each operation.
我上面的主循环的编译版本包括遗留的 SSE 指令(movapd
和 friend ,我认为),而 libgcc_s 中的 __muldc3
的实现使用了很多奇特的AVX 指令(vmovapd
、vmulsd
等)。
这是放缓的最终原因。事实上,Intel 性能诊断表明,每次调用 `__muldc3'(在上面发布的代码的最后一个版本中)时,这种 AVX/SSE 切换几乎恰好发生一次:
$ perf stat -e cpu/event=0xc1,umask=0x08/ -e cpu/event=0xc1,umask=0x10/ ./slow.elf
Performance counter stats for './slow.elf':
100,000,064 cpu/event=0xc1,umask=0x08/
100,000,118 cpu/event=0xc1,umask=0x10/
(事件代码取自表 19.5 of another Intel manual)。
这就留下了一个问题,为什么当你第一次调用一个库函数时减速会打开,而当你调用 printf
、sprintf
或其他任何东西时又会关闭.线索是in the first document again :
When it is not possible to remove the transitions, it is often possible to avoid the penalty by explicitly zeroing the upper 128-bits of the YMM registers, in which case the hardware does not save these values.
因此,我认为完整的故事如下。当您第一次调用库函数时,ld-linux-x86-64.so
中设置 PLT 的蹦床代码会使 MMY 寄存器的高位处于非零状态.当您调用 sprintf
时,它会将 MMY 寄存器的高位清零(我不确定是偶然还是设计)。
用 asm("vzeroupper")
替换 sprintf
调用——它明确指示处理器将这些高位归零——具有相同的效果。
可以通过将 -mavx
或 -march=native
添加到编译标志来消除这种影响,这就是系统其余部分的构建方式。我猜为什么默认情况下不会发生这种情况只是我的系统的一个谜。
我不太确定我们在这里学到了什么,但就是这样。
关于c++ - 添加打印语句可将代码速度提高一个数量级,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42358211/
我正在比较工作簿中的工作表。该工作簿有两张名为 PRE 和 POST 的工作表,每张工作表都有相同的 19 列。行数每天都不同,但特定一天的两张表的行数相同。该宏将 PRE 工作表中的每一行与 POS
我有一个对象数组,我一次循环遍历该数组一个对象,然后进行几次检查以查看该数组中的每个对象是否满足特定条件,如果该对象满足此条件,则复制一个属性将此对象放入数组中(该属性还包含另一个对象)。 for(v
我正在编写一个必须非常快的应用程序。我使用 Qt 5.5 和 Qt Creator,Qt 的 64 位 MSVC2013 编译版本。 我使用非常困倦的 CS 来分析我的应用程序,我看到占用最多独占时间
我有以下 CountDownTimer 在我的 Android 应用程序中不断运行。 CountDownTimer timer_status; timer_status = new CountDown
有一个优化问题,我必须调用随机森林回归器的预测函数数千次。 from sklearn.ensemble import RandomForestRegressor rfr = RandomForestR
我正在努力提高现有 Asp.Net Web 应用程序的数据访问层的性能。场景是。 它是一个基于 Web 的 Asp.Net 应用程序。 数据访问层使用 NHibernate 1.2 构建并作为 WCF
我在我的 Intel Edison 上运行 Debian,并尝试使用 ffmpeg 通过 USB 网络摄像头捕获视频。我正在使用的命令是: ffmpeg -f video4linux2 -i /dev
我有一个 For循环遍历整数 1 到 9 并简单地找到与该整数对应的最底部的条目(即 1,1,1,2,3,4,5 将找到第三个“1”条目)并插入一个空白行。我将数字与仅对应于此代码的应用程序的字符串“
我有一个带有非规范化架构(1 个表)的 postgresql 数据库,其中包含大约 400 万个条目。现在我有这个查询: SELECT count(*) AS Total, (SELECT c
在 Ltac 中实现复杂的策略时,有一些 Ltac 命令或策略调用我预计会失败以及预期失败(例如终止 repeat 或导致回溯)。这些故障通常在故障级别 0 时引发。 更高级别引发的故障“逃避”周
我正在尝试提高 ansible playbook 的性能。我有一个测试剧本如下: --- - name: Test hosts: localhost connection: local g
我正在使用 axios从 Azure 存储 Blob 下载文件 (~100MB)。 axios({ method: 'get', url: uri, onDownloadProgress:
我有一个 ClojureScript 程序,主要对集合执行数学计算。它是在惯用的、独立于主机的 Clojure 中开发的,因此很容易对其进行基准测试。令我惊讶的是(与答案对 Which is fast
我有一个程序必须在硬件允许的情况下尽快发出数千个 http 请求。在现实世界中,这些连接中的每一个都将连接到一个离散的服务器,但我已经编写了一个测试程序来帮助我模拟负载(希望如此)。 我的程序使用 A
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
我正在计算 Clojure 中 3d 点云的边界框。点云表示为 Java 原始浮点数组,点云中的每个点都使用 4 个浮点存储,其中最后一个浮点未使用。像这样: [x0 y0 z0 u0 x1 y1
就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引起辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the he
我正在尝试使用rayshader 包制作图像。我很高兴能够使用如下代码创建一个 png 文件: library(ggplot2) library(rayshader) example_plot <-
更新 显然,jQuery 模板可以被编译,并且它有助于显示带有 if 语句 的模板的性能 here . 但是如图here ,预编译的 jQuery 模板对我的情况没有多大作用,因为我的模板不包含逻辑
我是编程新手。我有一个启用分页的 ScrollView ,其中包含许多页面(最多十个),并且在每个页面上都有一个自定义按钮。每个自定义按钮都有一个自定义图像。我在 Interface Builder
我是一名优秀的程序员,十分优秀!