我在Java中快速实现了SoE算法(最后的代码)。我的双核AMD处理器的输出是:
Allocation: 31 Meat: 10140 Listing: 10171 Preparing end: 10187
“肉类”部分按预期消耗最长时间。
我的一个观察结果是使用Math.pow(variable, 2)
比(variable * variable)
慢。我认为除了函数跳转之外,可能还有其他开销。
Math.pow(x,2)是否对2,3的幂等进行了优化?我问,因为有一些用户贡献的Java库,其乘法算法比Java的原生库快得多。
以下是我的问题:
您可以向Meat部分建议哪些算术优化?有什么办法可以完全避开模数运算符吗?
当start == end时,该功能不起作用。如果我筛选(4,4),返回的数组长度为1:[4]。我究竟做错了什么?它应该返回[](基本上是新的int(0))。
您知道哪些快速数字/数学相关的Java库?
感谢阅读。最后这是我写的代码:不是GangOfFour / TopCoder的质量,但也不是太可怜(我希望!,SO的代码格式有点......很奇怪?):
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Sieve {
public static void main(String[] args) {
/* small test */
int[] primes = sieve(1, 1000000);
}
/**
* returns an array of prime integers
* in the given range
*
* @param start range start
* @param end range end
* @return
*/
private static int[] sieve(int start, int end) {
long startTime = System.currentTimeMillis();
/* some basic range checks */
if(end < start || start < 1 || end < 1) {
throw new ArithmeticException("Messed up input");
}
/* generate ints within range */
int[] naturals = new int[end-start+1];
for (int j = 0; j < end - start + 1; j++) {
naturals[j] = start + j;
}
System.out.println("Allocation: \t" + (System.currentTimeMillis() - startTime));
/* init running prime to start, and increment until
* running prime squared is greater than the end
*/
for (int runningPrime = (start == 1 ? 2: start); end > runningPrime*runningPrime; runningPrime++) {
for (int i = runningPrime; i < naturals.length; i++) {
if(-1 != naturals[i]) {
if(naturals[i] % runningPrime == 0) {
naturals[i] = -1;
}
}
}
}
System.out.println("Meat: \t\t" + (System.currentTimeMillis() - startTime));
if(naturals[0] == 1) {
naturals[0] = -1;
}
/* list primes */
List list = new ArrayList();
for (int i = 0; i < naturals.length; i++) {
if(-1 != naturals[i])
list.add(naturals[i]);
}
System.out.println("Listing: \t" + (System.currentTimeMillis() - startTime));
/* create the return int array */
int[] primes = new int[list.size()];
int k = 0;
for (Iterator iterator = list.iterator(); iterator.hasNext();) {
primes[k++] = ((Integer) iterator.next()).intValue();
}
System.out.println("Preparing end: \t" + (System.currentTimeMillis() - startTime));
return primes;
}
}
感谢所有反馈。这是下面的固定版本(直到有人设法再次破坏它:)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Sieve {
public static void main(String[] args) {
/* small test */
int[] primes = sieve(2, 5);
System.out.println("Number of primes: " + primes.length);
for (int i : primes) {
System.out.println(i);
}
}
/**
* returns an array of prime integers
* in the given range
*
* @param start range start
* @param end range end
* @return
*/
private static int[] sieve(int start, int end) {
long startTime = System.currentTimeMillis();
/* some basic range checks */
if(end < start || start < 1 || end < 1) {
throw new ArithmeticException("Messed up input");
}
/* generate ints within range */
int[] naturals = new int[(int)Math.floor((end-start+1) / 2) + 1];
int allocator = 0;
for (int j = 0; j < end - start + 1; j++) {
if(!((start + j) % 2 == 0)) {
naturals[allocator++] = start + j;
}
}
System.out.println("Allocation: \t" + (System.currentTimeMillis() - startTime));
/* init running prime to 2, and increment until
* running prime squared is greater than the end
*/
for (int runningPrime = 2; end >= runningPrime*runningPrime; runningPrime++) {
for (int i = 0; i < naturals.length; i++) {
if(-1 != naturals[i]) {
if(naturals[i] != runningPrime && naturals[i] % runningPrime == 0) {
naturals[i] = -1;
}
}
}
}
System.out.println("Meat: \t\t" + (System.currentTimeMillis() - startTime));
if(naturals[0] == 1) {
naturals[0] = -1;
}
/* list primes */
List list = new ArrayList();
for (int i = 0; i < naturals.length; i++) {
if(-1 != naturals[i])
list.add(naturals[i]);
}
System.out.println("Listing: \t" + (System.currentTimeMillis() - startTime));
/* create the return int array */
int size = list.size();
int k = 0;
/* tricky tricky :) */
if(start <= 2) {
size += 1;
k = 1;
}
int[] primes = new int[size];
if(start <= 2) {
primes[0] = 2;
}
for (Iterator iterator = list.iterator(); iterator.hasNext();) {
primes[k++] = ((Integer) iterator.next()).intValue();
}
System.out.println("Preparing end: \t" + (System.currentTimeMillis() - startTime));
return primes;
}
}
答案 0 :(得分:3)
你可以通过重写内循环来避免模数:
for (int i = runningPrime; i < naturals.length; i++) {
if(-1 != naturals[i]) {
if(naturals[i] % runningPrime == 0) {
naturals[i] = -1;
}
}
}
as
for (int i = runningPrime; i < naturals.length; i+=runningPrime) {
naturals[i] = -1;
}
我也有点担心包含start
参数会使事情复杂化(考虑sieve(4, 10)
的情况)。
答案 1 :(得分:1)
假设我没有错过我写的东西:
for(int runningPrime = (start == 1 ? 2: start); end > runningPrime*runningPrime;
runningPrime++)
as
int limit = Math.sqrt(end);
for(int runningPrime = (start == 1 ? 2: start); runningPrime < limit;
runningPrime++)
防止每次迭代不必要的乘法。我也只会填补 奇数的数组,有效地将其长度减半。
答案 2 :(得分:1)
你的解决方案不是 Eratosthenes的Sieve。这很明显,因为您在代码中使用modulo
运算符;一个适当的Eratosthenes筛子只使用内环中的加法,而不是除法或模数。以下是Eratosthenes筛选的简单版本,它从BitSet
导入LinkedList
和java.util
并返回小于 n 的素数的LinkedList:
public static LinkedList sieve(int n)
{
BitSet b = new BitSet(n);
LinkedList ps = new LinkedList();
b.set(0,n);
for (int p=2; p<n; p++)
{
if (b.get(p))
{
ps.add(p);
for (int i=p+p; i<n; i+=p)
{
b.clear(i);
}
}
}
return ps;
}
基本想法是创建筛子(BitSet
b ),每个项目最初设置为Prime
(表示为一组比特),遍历筛子寻找并报告每个连续的素数,并且当发现一个人通过标记它Composite
(表示为清除位)从筛子中击出它的所有倍数时。通过加法而不是除法找到倍数,并且内循环仅包括加法,位清除操作,查找筛子末端的比较,以及跳回到循环开头的所有内容,因此它非常快。
有一些优化可以让Eratosthenes的Sieve运行得更快,但这应该足以让你开始。当您准备好了更多时,我在我的博客上谦虚地推荐this essay。
如果你想要一个不从零开始的范围内的素数,你需要一个分段的 Eratosthenes筛子。我在Stack Overflow上讨论了分段的Eratosthenes Sieve previously,并在我的博客上讨论了discuss it。
答案 3 :(得分:0)
只填充赔率(2除外),通过runningPrime递增,并且已经建议丢失可分性检查,可能是最重要的优化。
Java的Math.pow用于双打!它没有优化平方,大多数情况下它会立即将2重铸为双倍。
答案 4 :(得分:0)
在我看来,在开始优化之前,你应该修复两个严重的错误。
我将您的代码编译为Java程序,然后尝试计算
sieve(1, 9)
和
sieve(4,10);
第一种情况正常,但9被认为是素数。 9的平方根是素数,但是你的循环条件会在你到达之前停止筛分。
在第二种情况下,所谓的素数是4,5,6,7,8,9,10。这是因为你跳过了范围开始以下任何素数的筛选。那,我恐怕是一个优化太过分了: - )