4

Java で SoE アルゴを簡単に実装しました (最後にコードを示します)。デュアル コア AMD プロセッサでの出力は次のとおりです。

割り当て: 31
肉: 10140
リスト: 10171
準備終了: 10187
  • 「肉」セクションは、予想どおり最大の時間を消費します。

  • 私が観察したことの 1 つは、 usingMath.pow(variable, 2)は よりも遅いということでした(variable * variable)。関数のジャンプ以外にも、他にもオーバーヘッドがあるのではないかと思います。

  • Math.pow(x, 2) には 2、3 などの累乗の最適化がありますか? Java のネイティブのアルゴリズムよりもはるかに高速な乗算アルゴリズムを備えたユーザー提供の Java ライブラリがいくつかあるため、質問します。

ここに私の質問があります:

  • 肉のセクションにどのような算術最適化を提案できますか? モジュラス演算子を完全に回避する方法はありますか?

  • start == end の場合、関数は機能しません。sieve(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;
    }
}
4

5 に答える 5

3

内部ループを書き直すことでモジュロを回避できます。

        for (int i = runningPrime; i < naturals.length; i++) {
            if(-1 != naturals[i]) {
                if(naturals[i] % runningPrime == 0) {
                    naturals[i] = -1;
                }
            }
        }

なので

        for (int i = runningPrime; i < naturals.length; i+=runningPrime) {
             naturals[i] = -1;
        }

startまた、パラメータを含めると複雑になることも少し心配です(の場合を考えるとsieve(4, 10))。

于 2010-11-11T15:23:58.907 に答える
1

私が書く何かを見逃さなかったと仮定すると:

 for(int runningPrime = (start == 1 ? 2: start); end > runningPrime*runningPrime;
 runningPrime++) 

なので

int limit = Math.sqrt(end);
for(int runningPrime = (start == 1 ? 2: start); runningPrime < limit; 
runningPrime++) 

反復ごとの不要な乗算を防ぐため。また、配列を奇数で埋めるだけで、その長さを効果的に半分にできます。

于 2010-11-11T15:20:04.630 に答える
1

あなたの解決策は、エラトステネスのふるいではありません。moduloコードで演算子を使用しているため、これは明らかです。エラトステネスの適切なふるいは、内側のループで加算のみを使用し、除算やモジュロは使用しません。これはエラトステネスのふるいの単純なバージョンで、n未満の素数の LinkedListをインポートBitSetしてLinkedListから返します。java.util

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 bPrime ) を作成し、ふるいを繰り返して、連続する各素数を探して報告し、見つかったときに、マークしてふるいにかけますComposite(クリアされたビットとして表されます)。倍数は除算ではなく加算によって求められ、内側のループは加算、ビットクリア操作、ふるいの終わりを探すための比較、およびループの先頭へのジャンプバックのみで構成されるため、は非常に高速です。

エラトステネスのふるいをさらに高速に実行する最適化が利用可能ですが、これで十分です。もっと読む準備ができたら、私のブログでこのエッセイをお勧めします。

ゼロから始まらない範囲の素数が必要な場合は、セグメント化されたエラトステネスのふるいが必要です。セグメント化されたエラトステネスのふるいについては、以前Stack Overflow で説明しましたが、ブログでも説明しています。

于 2013-02-01T03:57:50.393 に答える
0

オッズのみで埋める(2を除く)、runningPrimeによる増分、および除算性チェックの喪失は、すでに提案されているように、おそらく最も重要な最適化です。

JavaのMath.powはdouble用です!二乗の最適化はありません。ほとんどの場合、bcはすぐに2をdoubleとして再キャストします。

于 2010-11-11T15:43:51.127 に答える
0

私の意見では、最適化を開始する前に、2 つの重大なバグを修正する必要があります。

あなたのコードを Java プログラムとしてコンパイルし、計算してみました

sieve(1, 9)

sieve(4,10);

最初のケースは、9 が素数であると見なされたことを除いて正しく機能しました。9 の平方根は素数ですが、ループ条件により、そこに到達する直前にふるい分けが停止します。

2 番目のケースでは、素数とされるものは 4、5、6、7、8、9、10 です。これは、範囲の開始より下の素数のいずれかによるふるい分けをスキップしたためです。それは、最適化が行き過ぎているのではないかと思います:-)

于 2010-11-11T16:27:15.003 に答える