- iOS/Objective-C 元类和类别
- objective-c - -1001 错误,当 NSURLSession 通过 httpproxy 和/etc/hosts
- java - 使用网络类获取 url 地址
- ios - 推送通知中不播放声音
[简短回答:糟糕的基准测试方法。你会认为我现在已经想通了。]
问题表现为“找到一种快速计算 x^y 的方法,其中 x 和 y 是正整数”。典型的“快速”算法如下所示:
public long fastPower(int x, int y) {
// Replaced my code with the "better" version described below,
// but this version isn't measurably faster than what I had before
long base = x; // otherwise, we may overflow at x *= x.
long result = y % 2 == 1 ? x : 1;
while (y > 1) {
base *= base;
y >>= 1;
if (y % 2 == 1) result *= base;
}
return result;
}
我想看看这比调用 Math.pow() 或使用像这样将 x 本身乘以 y 次这样的天真方法快多少:
public long naivePower(int x, int y) {
long result = 1;
for (int i = 0; i < y; i++) {
result *= x;
}
return result;
}
编辑:好吧,有人(正确地)向我指出我的基准测试代码没有使用结果,这完全把一切都扔掉了。一旦我开始使用结果,我仍然看到朴素方法比“快速”方法快 25% 左右。
原文:
I was very surprised to find that the naive approach was 4x faster than the "fast" version, which was itself about 3x faster than the Math.pow() version.
我的测试使用了 10,000,000 次试验(然后是 1 亿次,只是为了绝对确保 JIT 有时间预热),每次都使用随机值(以防止调用被优化掉)2 <= x <= 3,和 25 <= y <= 29。我选择了一个狭窄的值范围,不会产生大于 2^63 的结果,但偏向于较大的指数,试图给“快速”版本带来优势。我正在预先生成 10,000 个伪随机数,以从计时中消除该部分代码。
我知道对于小指数,天真的版本可能会更快。 “快速”版本有两个分支而不是一个,并且通常会执行两倍于原始版本的算术/存储操作 - 但我希望对于大指数,这仍然会导致快速方法节省一半的操作最好的情况,并且在最坏的情况下大致相同。
任何人都知道为什么天真的方法会比“快速”版本快得多,即使数据偏向“快速”版本(即更大的指数)?该代码中的额外分支是否会在运行时造成如此大的差异?
基准测试代码(是的,我知道我应该为“官方”基准测试使用一些框架,但这是一个玩具问题)- 更新以预热并使用结果:
PowerIf[] powers = new PowerIf[] {
new EasyPower(), // just calls Math.pow() and cast to int
new NaivePower(),
new FastPower()
};
Random rand = new Random(0); // same seed for each run
int randCount = 10000;
int[] bases = new int[randCount];
int[] exponents = new int[randCount];
for (int i = 0; i < randCount; i++) {
bases[i] = 2 + rand.nextInt(2);
exponents[i] = 25 + rand.nextInt(5);
}
int count = 1000000000;
for (int trial = 0; trial < powers.length; trial++) {
long total = 0;
for (int i = 0; i < count; i++) { // warm up
final int x = bases[i % randCount];
final int y = exponents[i % randCount];
total += powers[trial].power(x, y);
}
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
final int x = bases[i % randCount];
final int y = exponents[i % randCount];
total += powers[trial].power(x, y);
}
long end = System.currentTimeMillis();
System.out.printf("%25s: %d ms%n", powers[trial].toString(), (end - start));
System.out.println(total);
}
产生输出:
EasyPower: 7908 ms-407261252961037760 NaivePower: 1993 ms-407261252961037760 FastPower: 2394 ms-407261252961037760
使用随机数的参数和试验确实会改变输出特性,但测试之间的比率始终与显示的相同。
最佳答案
您的 fastPower
有两个问题:
y % 2 == 0
替换为 (y & 1) == 0
;按位运算更快。y
并执行额外的乘法,包括 y
为偶数的情况。这部分最好放在else
子句中。无论如何,我猜你的基准测试方法并不完美。 4 倍的性能差异听起来很奇怪,如果不看完整代码就无法解释。
应用上述改进后,我使用 JMH
进行了验证fastPower
确实比 naivePower
快 1.3 到 2 倍。
package bench;
import org.openjdk.jmh.annotations.*;
@State(Scope.Benchmark)
public class FastPow {
@Param("3")
int x;
@Param({"25", "28", "31", "32"})
int y;
@Benchmark
public long fast() {
return fastPower(x, y);
}
@Benchmark
public long naive() {
return naivePower(x, y);
}
public static long fastPower(long x, int y) {
long result = 1;
while (y > 0) {
if ((y & 1) == 0) {
x *= x;
y >>>= 1;
} else {
result *= x;
y--;
}
}
return result;
}
public static long naivePower(long x, int y) {
long result = 1;
for (int i = 0; i < y; i++) {
result *= x;
}
return result;
}
}
结果:
Benchmark (x) (y) Mode Cnt Score Error Units
FastPow.fast 3 25 thrpt 10 103,406 ± 0,664 ops/us
FastPow.fast 3 28 thrpt 10 103,520 ± 0,351 ops/us
FastPow.fast 3 31 thrpt 10 85,390 ± 0,286 ops/us
FastPow.fast 3 32 thrpt 10 115,868 ± 0,294 ops/us
FastPow.naive 3 25 thrpt 10 76,331 ± 0,660 ops/us
FastPow.naive 3 28 thrpt 10 69,527 ± 0,464 ops/us
FastPow.naive 3 31 thrpt 10 54,407 ± 0,231 ops/us
FastPow.naive 3 32 thrpt 10 56,127 ± 0,207 ops/us
注意:整数乘法是相当快的运算,sometimes even faster than an extra comparison .不要期望适合 long
的值会带来巨大的性能改进。在指数较大的 BigInteger
上,快速幂算法的优势将很明显。
既然作者发布了基准测试,我必须承认令人惊讶的性能结果来自于常见的基准测试陷阱。我在保留原始方法的同时改进了基准测试,现在它表明 FastPower
确实比 NaivePower
快,see here .
改进后的版本有哪些关键变化?
y % 2
替换为 y & 1
因为 HotSpot 不会自动执行此优化。手动编写微基准测试是一项艰巨的任务。这就是为什么强烈建议使用适当的基准测试框架,如 JMH .
关于java - "Fast"Java中的整数幂,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35666078/
我在编写数学函数时遇到了麻烦。它应该接受 3 个变量并像这样计算方程。 答案 = x(1 + y/100)^ z 我把它写成: public compute_cert (int years, doub
我正在开发一个计算器,以便更好地学习 Java。我编写了自己的代码来使用 BigDecimal 参数计算幂。截至目前,代码无法处理分数幂,例如 2^2.2。为了解决这个问题,我想在我的代码中实现指数恒
我正在寻找一种算法(或者更好的是,代码!)来生成幂,特别是奇数指数大于 1 的数字:三次幂、五次幂、七次幂等等。然后我想要的输出是 8, 27, 32, 125, 128, 216, 243, 343
在 Codewars 上找到这个。该函数接受两个参数 A 和 B,并返回 A^B 的最后一位。下面的代码通过了前两个测试用例,但不会通过下一个测试用例。 def last_digit(n1, n2):
像 2^(2%1) 这样的表达式在 GHCi 中不会进行类型检查,并且错误消息是神秘的。为什么这不起作用,我需要改变什么? 我无法转换为其他类型,我希望将其用于 27^(1%3) 等表达式。 最佳答案
我的二次幂没有达到应有的水平,所以我想也许我可以 #define 做点什么。 不幸的是,我在预处理器指令方面经验不足,我不知道如何做 for 循环之类的事情。我看了看: http://www.cplu
如何在 Math.net 中获得三角函数的幂? Expr x = Expr.Variable("x"); Expr g = (2 * x).Sinh().Pow(2); g.ToString()给出输
我正在尝试拟合这个渐近接近零(但从未达到它)的数据。 我相信最好的曲线是逆逻辑函数,但欢迎建议。关键是预期的衰减“S 曲线”形状。 这是我到目前为止的代码,以及下面的绘图图像,这是一个非常丑陋的适合。
这个问题在这里已经有了答案: The most efficient way to implement an integer based power function pow(int, int) (2
我试图获得指数非常大的 double 值的幂(Java BigInteger 可以包含它(指数),例如:10^30 ) 也就是说,我想找到类似 1.75^(10^30) 或 1.23^(3423453
我有一个数学表达式,例如: ((2-x+3)^2+(x-5+7)^10)^0.5 我需要更换 ^符号到pow C语言的功能。我认为正则表达式是我需要的,但我不知道像专业人士那样的正则表达式。所以我最终
这是我的 previous question on bit flags 的后续内容,我澄清了一些重大误解。 我需要创建这些函数来查找包含零个或多个标志的 int 中的单个位标志: BitBinaryU
我已经在 java 中为 BigInteger 尝试过 modPow() 函数。 但它需要太长时间。 我知道模乘法,甚至也知道求幂。 但由于条件限制,我无法解决这个问题。 a、b 的值可以包含 100
我是一名优秀的程序员,十分优秀!