【问题标题】:How to speed up my prime calculations?如何加快我的素数计算?
【发布时间】:2014-03-26 13:44:13
【问题描述】:

我正在尝试回答以下欧拉问题 (#10):

10 以下的素数之和为 2 + 3 + 5 + 7 = 17。

求两百万以下的所有质数之和。

我的程序运行正常,但是我发现计算它需要 100 秒,使用以下代码,以 new Problem10().run() 为起点:

public class Problem10 extends Problem<Long> {
    @Override
    public void run() {
        result = Iterators.finiteLongStream(new PrimeGenerator(), i -> i <= 2_000_000)
                .sum();
    }

    @Override
    public String getName() {
        return "Problem 10";
    }
}

public abstract class Iterators {
    ///

    public static PrimitiveIterator.OfLong finiteLongIterator(final PrimitiveIterator.OfLong iterator, final LongPredicate predicate) {
        return new PrimitiveIterator.OfLong() {
            private long next;

            @Override
            public boolean hasNext() {
                if (!iterator.hasNext()) {
                    return false;
                }
                next = iterator.nextLong();
                return predicate.test(next);
            }

            @Override
            public long nextLong() {
                return next;
            }
        };
    }

    public static LongStream finiteLongStream(final PrimitiveIterator.OfLong iterator, final LongPredicate predicate) {
        return Iterators.longStream(Iterators.finiteLongIterator(iterator, predicate));
    }

    public static LongStream longStream(final PrimitiveIterator.OfLong iterator) {
        return StreamSupport.longStream(
                Spliterators.spliteratorUnknownSize(iterator, 0), false
        );
    }

    ///
}

public class PrimeGenerator implements PrimitiveIterator.OfLong {
    private final static LongNode HEAD_NODE = new LongNode(2);

    private LongNode lastNode = HEAD_NODE;
    private long current = 2;

    @Override
    public boolean hasNext() {
        return true;
    }

    @Override
    public long nextLong() {
        if (lastNode.value == current) {
            if (lastNode.next != null) {
                long old = lastNode.value;
                lastNode = lastNode.next;
                current = lastNode.value;
                return old;
            }
            return current++;
        }
        while (true) {
            if (isPrime(current)) {
                appendNode(current);
                return current++;
            }
            current++;
        }
    }

    private boolean isPrime(final long number) {
        LongNode prime = HEAD_NODE;
        while (prime != null && prime.value <= number) {
            if (number % prime.value == 0) {
                return false;
            }
            prime = prime.next;
        }
        return true;
    }

    private void appendNode(final long value) {
        LongNode newNode = new LongNode(value);
        couple(lastNode, newNode);
        lastNode = newNode;
    }

    private void couple(final LongNode first, final LongNode second) {
        first.next = second;
        second.previous = first;
    }

    private static class LongNode {
        public final long value;

        public LongNode previous;
        public LongNode next;

        public LongNode(final long value) {
            this.value = value;
        }
    }
}

我该如何优化呢?如果可能的话,首先按照我当前的代码行提出建议,然后提出一个完全不同的算法。

编辑,我也想避免使用有限的Sieve of Eratosthenes,作为这种迭代器的重点。流是为了能够以无限的价格做到这一点,我不确定埃拉托色尼筛法是否适用于无限的数字,我认为这不是微不足道的。

【问题讨论】:

  • 这看起来像是 Code Review(另一个 Stack Q&A 论坛)的问题。
  • 仅供参考,编写一个好的 Eratoshenes 筛选算法是一个明智的想法,因为它非常适合生成高达 10^10 左右的素数列表。也可以修改它以快速分解所有素数高达 10^10。两者在后来的 Project Euler 问题中都很有用。

标签: java performance iterator primes java-8


【解决方案1】:

如果您观察到只需要考虑小于数平方根的素数因子,则可以减少方法 isPrime() 中的迭代次数。

所以目前的情况是:

  while (prime != null && prime.value <= number) 

可以改成:

   while (prime != null && prime.value <= square_root(number) )

可能还有其他方法可以优化您的代码,但这需要对您的代码进行详细审查。

【讨论】:

  • 哇...这将我的运行时间从 100 秒缩短到 375 毫秒。即使没有预热 JVM 或其他任何东西,也只是冷启动。
  • @skiwi 调查一下为什么会这样会很有趣。乍一看,它似乎会将您的运行时间从 100 秒缩短到 sqrt(100) 秒,但您的运行时间几乎减少了三个数量级(几乎比预期的多两个)。了解它发生的原因可能会帮助您发现其他优化。
  • @John 恐怕你的数学有问题,因为 sqrt(100000) = ~316.23,这与我的经历有关。
  • @skiwi 很高兴它对您有用。但请继续探索其他优化代码的方法。
  • @skiwi 感谢您指出这一点。如果先转换为毫秒,则 100 秒的平方根不会变成 10 秒?这让我难以置信。
【解决方案2】:

这里有一些想法(不是代码,因为这似乎是一个家庭作业/项目问题):

  • 使用 int 而非 long 进行计算(int 足以容纳 2 000 000)虽然您的素数总和可能需要很长
  • 只检查循环中以 3 开头的奇数(为什么要检查一个你知道不是素数的偶数>!
  • 确保尽可能使用最少的代码.. 代码中有很多结构。不会对 int 值进行简单的循环(并检查您发现的素数仅达到您的值的 sqrt 吗?)

并且不要使用平方根函数。而是计算您必须检查的最大素数的索引为 prime[p]*prime[p],只要它大于您的试验值。例如,在您的代码中使用类似这样的内容(其中 ps 是您正在检查的第一个素数的索引,而 iMax 在您进入循环之前是 primes[ps]*primes[ps]。为了节省时间,请始终使用“尽可能减少计算强度。

while (i > iMax) { 
    ps++; iMax = primes[ps]*primes[ps];
};

【讨论】:

  • “尽可能使用最少代码”的说法没有实际意义。好的代码不取决于使用的代码量,如果有的话,如果你真的需要编写更多的代码,就会变得不太清楚。我也打算让它长期工作,而不仅仅是整数。
  • int 可能持有 200 万,但 int 可能无法持有所有质数的总和低于 200 万。
  • 你应该能够得到一个在 1 秒内执行的循环(我的电脑上是 .463)
猜你喜欢
  • 2018-07-08
  • 2021-05-30
  • 2018-11-28
  • 1970-01-01
  • 2021-01-30
  • 2014-12-14
  • 1970-01-01
  • 1970-01-01
  • 2013-01-12
相关资源
最近更新 更多