- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
我正在编写一个C++程序来生成Mandelbrot集缩放。我所有的复数最初都是两个double
(一个是实数部分,一个是复数部分)。这工作得很快。对于我生成的图像类型,每帧15秒。
由于缩放效果,我想为更放大的帧提高精度,因为这些帧在min_x
和max_x
之间具有如此小的差异。我希望GMP可以帮助我解决这个问题。
现在,它要慢得多。每帧15:38分钟。图像的设置与以前相同,算法也相同。唯一发生变化的是我使用mpf_class
表示需要精确的小数(即仅是复数)。为了比较性能,我使用与double相同的精度:mpf_set_default_prec(64);
GMP是否会更改mpf_class
的精度以满足表达式的需求?换句话说,如果我有两个64位mpf_class
对象,并使用它们进行计算并将结果存储在另一个mpf_class
中,精度是否会提高?我认为,随着时间的推移,这会破坏性能,但是我不确定这是导致我的问题的原因。
我的问题:这种性能下降仅仅是GMP和其他任意精度库的本质吗?您会提出什么建议?
编辑1
我(即一直都是)使用-O3
标志进行优化。我还进行了一项测试,以验证GMP不会自动提高mpf_class
对象的精度。因此,仍然存在关于性能急剧下降的原因的问题。
编辑2
作为说明示例,我将以下代码编译为g++ main.cpp -lgmp -lgmpxx
,如下所示,一次,然后将每个double
替换为mpf_class
一次。使用double
,它运行了12.75秒,而使用mpf_class
,它运行了24:54分钟。当它们具有相同的精度时,为什么会这样?
#include <gmpxx.h>
double linear_map(double d, double a1, double b1, double a2, double b2) {
double a = (d-a1)/(b1-a1);
return (a*(b2-a2)) + (a2);
}
int iterate(double x0, double y0) {
double x, y;
x = 0;
y = 0;
int i;
for (i = 0; i < 1000 && x*x + y*y <= 65536; i++) {
double xtemp = x*x - y*y + x0;
y = 2*x*y + y0;
x = xtemp;
}
return i;
}
int main() {
mpf_set_default_prec(64);
for (int j = 0; j < 3200; j++) {
for (int i = 0; i < 3200; i++) {
double x = linear_map(i, 0, 3200, -2, 1);
double y = linear_map(j, 0, 3200, -1.5, 1.5);
iterate(x, y);
}
}
return 0;
}
最佳答案
如评论中所述,完全可以从GMP之类的库中获得这种减慢效果。
内置double
乘法是当今CPU和编译器最优化的 Realm 之一。 CPU具有多个执行单元,这些单元通常在编译器的帮助下设法并行执行多个浮点运算,这些编译器会尝试对循环进行自动向量化(尽管这不适用于您的情况,因为最内层的循环强烈依赖于之前的迭代)。
另一方面,在多个动态精度库(例如GMP)中,每个操作都需要大量工作-即使要检查两个操作数是否具有相同/正确的精度,也要检查多个分支,并且所实现的计算算法是通用的并针对“更高的精度”端进行了定制,这意味着它们并未针对当前用例进行特别优化(以与double
相同的精度使用它们);同样,GMP值可以在创建时确实分配内存,这是另一项昂贵的操作。
我采用了您的程序并对其进行了少许修改,以使其对要使用的类型具有参数性(使用#define
),将采样方的边减少(从3200减少到800,以使测试更快),并添加了返回值的累加器iterate
将其打印在最后,既可以检查各个版本之间的所有工作是否都以相同的方式进行,又可以确保优化程序不会完全放弃循环。
我的机器上的double
版本大约需要0.16秒,然后撞到探查器中,在火焰图中显示出完全平坦的探查图;一切都发生在iterate
中。
相反,GMP版本需要45秒(300倍;您谈到的速度降低了60倍,但您正在与未优化的基本情况进行比较),并且变化更大:
和以前一样,iterate
一直占用大量时间(因此就优化而言,我们可以完全忽略linear_map
)。所有这些“塔”都是对GMP代码的调用; __gmp_expr<...>
的东西不是特别相关-它们只是模板样板,可以在没有太多临时变量的情况下评估复杂的表达式,并且可以完全内联。大部分时间都花在那些塔的顶部,在那里进行实际的计算。
实际上,最终大部分时间都花在了GMP原语和内存分配上:
鉴于我们无法接触GMP内部,我们唯一能做的就是更加谨慎地使用它,因为每次GMP操作的确很昂贵。
确实,请务必记住,尽管编译器可以避免多次为double
值计算相同的表达式,但对于GMP值却不能做到相同,因为它们都有副作用(内存分配,外部函数调用),并且也有副作用无论如何,要对其进行检查很复杂。在您的内部循环中,我们有:
double x, y;
x = 0;
y = 0;
int i;
for (i = 0; i < 1000 && x*x + y*y <= 65536; i++) {
T xtemp = x*x - y*y + x0;
T
是我正在使用的通用类型,定义为
double
或
mpf_class
)
x*x
和
y*y
。我们可以将其优化为:
T x = 0, y = 0, xsq, ysq;
for(i = 0; i < 1000; i++) {
xsq = x*x;
ysq = y*y;
if(xsq+ysq > 65536) break;
T xtemp = xsq - ysq + y0;
xsq
和
ysq
置于循环之外,以避免在每次迭代时重新创建它们。这是因为,与
double
值(最终只是寄存器空间,或者更糟的是堆栈空间,它们都是空闲的,并且由编译器静态处理)不同,
mpt_class
对象并非每次都可以自由地重新创建在上面的探查器跟踪中突出显示了内存分配功能;我并不完全了解GMP C++包装器的内部工作原理,但我怀疑它喜欢类似于
std::vector
的优化-赋值时,已分配的值将能够在分配时回收其空间,而无需再次分配。
xtemp
定义提升到循环之外
int iterate(T x0, T y0) {
T x = 0, y = 0, xsq , ysq, xtemp;
int i;
for (i = 0; i < 1000; i++) {
xsq = x*x;
ysq = y*y;
if(xsq+ysq > 65536) break;
xtemp = xsq - ysq + y0;
y = 2*x*y + y0;
x = xtemp;
}
return i;
}
malloc
/
free
失去了几个位置。
mpf_class
更快地执行它们。此时,您应该:
perf record
执行关于c++ - 我是否应该期望GMP的mpf_class比原始数据类型double慢得多?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54258810/
我在远程机器上导航基于 Java 的 CLI 菜单,并在 bash 脚本中使用 expect,我试图从输出中提取某些内容而不离开 expect session 。 我的脚本中的 Expect 命令是:
我正在尝试使用 expect.h header 编译用 c 编写的程序。我正在尝试这个: cc -I/usr/include main.c -lexpect -ltcl cc -I/usr/inclu
我正在使用Expect与SSH session 和ERP程序进行自动交互。 而不是依靠正则表达式来捕获我期望脚本中的变量,是否有可能在收到用户的特定击键后将屏幕区域(例如一个字段)捕获为代码中的变量?
我是 PHP 面向对象编程的新手。我有个问题。我写了一个代码,但它不起作用。我知道这很容易,但我想知道它有什么问题。我出现以下错误: 当我尝试在另一个文件中使用它时,我现在遇到了这个错误:( 最佳答
声明了哪些出现了前所未见的错误,并试图找到解决方案。与以前的程序一样奇怪,它使用相同的语法但不会抛出任何错误 这是一个使用游标从表中检索信息,然后将其插入到另一个表中的过程,这样做是为了可以使用其中的
我已经用 CASE 编写了一个查询,但遇到了 () 问题。 select SM.subscriber_name as name , SM.accountType as accountTy
这个问题在这里已经有了答案: Why does removing return give me an error: expected type `()` but found type (1 个回答)
我有一个脚本可以登录服务器并执行一些命令。我需要能够从每个命令中检索返回代码,以确定脚本是否成功。我写了以下脚本,但没有按照我的意愿进行。目标是执行“cd/I/dont/exist”,这会产生错误代码
关闭。 这个问题需要 debugging details 。它目前不接受答案。 编辑问题以包含 desired behavior, a specific problem or error, and
我正在运行一个 expect 脚本,它将生成一些来自 stdin 的动态输入。 是否有一种方法/模式可以解决从标准输入读取并将相关输入存储(?)到某处以在后面的步骤中处理/解析的概念? 示例: ./m
我正在运行一个 expect 脚本,该脚本在远程机器上调用多个脚本。这些 shell 脚本返回颜色输出(主要是红色和绿色)。问题是,那些颜色代码进入了我不想要的 log_file 和 STDOUT。我
我正在开发一个脚本,用于对软件安装进行回归测试。期望代码如下。前几行代码在浏览并同意许可证文件的地方运行良好。但是,脚本在“请输入有效许可证文件的路径名:”处停止,并且不执行任何操作。 (注意:手动安
我们创建以下简单的 expect 脚本以运行 netdata-installer.sh 预期脚本是: #!/usr/bin/expect set timeout 20 send "cd /tmp/ne
有人有T_PAAMAYIM_NEKUDOTAYIM吗? 最佳答案 是双冒号运算符 :: (见 list of parser tokens)。 关于PHP 期望 T_PAAMAYIM_NEKUDOTAY
我正在使用 Vercel SWR Hook usrSWR,我希望我可以将数据存储在某个遥远组件的缓存中,而不必使用上下文或其他一些全局状态管理器。 具体来说,我在 IndexPage 中使用 init
我刚刚注意到,如果我添加 if,Spock 不会断言条件。预期块中的子句,如 def myTest() { given: a = true expect: if ( a ) {
我有一个这样的方法: getValues(...args: Array) : Array { return args.map(k => { return this.shared
我正在使用 typescript + jest,并且在创建模拟实现时遇到了一些类型检查问题。例如,我想模拟 Credentials来自 aws-sdk 的对象: import { Credential
我依赖于一个以 Map 作为参数的方法。 public interface Service { void doSomething(Map map); } 我想写一个断言,用适当的 map 内容
我有一个适配器,它有一个方法,它采用可变参数列表,并将其转发给一个在我使用的框架中采用相同参数的方法。我想测试我的适配器是否正确转发了参数。然而,我不希望我的测试知道框架支持哪种参数。 我有一个工作期
我是一名优秀的程序员,十分优秀!