質問内容
素数である数を後々使うので,先に素数である数をピックアップしようと思って書いたのですが,この部分の計算が遅いことが原因で正解になりませんでした.どのようにすれば高速化できますか?
素数のとき,配列の中身を0にしようと思っています.
コード
C
1int num,num2; 2 int box[246913]={1,1}; 3 int i,j; 4for(i=2;i<246913;i++){ 5 if(box[i]==0){ 6 for(j=i+1;j<246913;j++){ 7 if(j%i==0&&box[j]==0) box[j]=1; 8 } 9 } 10 }
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答5件
0
素数を求めること自体が目的でないなら、固定値でいいんじゃないでしょうか?
投稿2020/06/07 23:49
総合スコア17000
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/06/10 11:32
2020/06/10 11:41
2020/06/10 11:44
0
ベストアンサー
結論
hnk_llさんのソースコードが以下の通りですね.
c
1int main() 2{ 3 int num, num2; 4 int box[246913] = {1, 1}; 5 int i, j; 6 for (i = 2; i < 246913; i++) 7 { 8 if (box[i] == 0) 9 { 10 for (j = i + 1; j < 246913; j++) 11 { 12 if (j % i == 0 && box[j] == 0) 13 box[j] = 1; 14 } 15 } 16 } 17 return 0; 18}
これを少しいじって,
c
1int main() 2{ 3 int num, num2; 4 int box[246913] = {1, 1}; 5 int i, j; 6 for (i = 2; i < 246913; i++) 7 { 8 if (box[i] == 0) 9 { 10 for (j = i + i; j < 246913; j += i) 11 box[j] = 1; 12 } 13 } 14 return 0; 15}
にすると,大分速くなると思います.
解説1:なぜ従来の手法が遅いのか
従来の手法ですと,
c
1for (j = i + 1; j < 246913; j++) 2{ 3 if (j % i == 0 && box[j] == 0) 4 box[j] = 1; 5}
の部分で,j を1ずつ増やして,j % i == 0 && box[j] == 0
が真のとき(つまり素数でないとき)にbox[j]を0にしていますね.
しかし,これだと,無駄が多いです.
というのも,下の画像を見てみてください.
この画像は従来の手法でした場合のfor文の中で回っている部分です.
緑の部分がfor文の中で回っている部分です.
質問者のhnk_llさんはおそらく,
「if文で条件を絞って,エラトステネスの篩を実装しよう」
と思ったのかもしれません.
しかし,このif文の条件文が計算量の多い計算を含んでいるので,すごい時間がかかっているのです.
ikadzuchiさんのおっしゃるとおり,除算や剰余はたいへん遅い処理なのです…
解説2:どのように改善すればよいか
さて,どのように改善すれば速くなるかということですが,
このif文を使わずに実装することです.
具体的には,
i = 2のとき,j = 4 6 8 10 12 ...のときにbox[j]を0にし,
i = 3のとき,j = 6 9 12 15 18 ...のときにbox[j]を0にし…
というふうにiの倍数(ただし1倍を除く)のときだけfor文を回るようにします.
そうすればif文は不要になります.
画像だとこうなります.(緑の部分がfor文の中で回っている部分です.)
緑の部分が減りましたね!
それが
c
1for (j = i + 1; j < 246913; j++) 2{ 3 box[j] = 1; 4}
というふうにすれば速くなる理由です.
投稿2020/06/08 02:39
編集2020/06/08 03:32総合スコア35
0
一番の問題はここですね。
j%i==0
除算や剰余はたいへん遅い処理ですので可能な限り避けるべきです。
エラトステネスの篩を実装するのが正攻法だと思います。
あと、
box[j]==0
この条件判定は無駄なので無くしたほうが速くなりそうな気がしたのですが、試してみると遅くなったりほぼ変わらなかったりですね…。
投稿2020/06/07 14:47
総合スコア3047
0
エラトステネスの篩では高速化出来ませんか?
手持ちの機械では↓のように成ります。
usr ~/Project/NewProject % soe 246913
Time:0.000495
*** prime count: 21787 ***
≒5/10000秒です。
GitHubに上げている『エラトステネスの篩』です。
最初に見つかった非0を残してその倍数を(0で)消去しています。
最後には穴だらけの配列に成りますが・・・
c
1/* 2 * エラトステネスの篩本体 O(n log log n) 3 */ 4void SieveOfEratosthenes(long arry[], long siz) 5{ 6 long lim = (long)(ceil(sqrt(siz+2))); // 最大値の平方根+αまで調べればいい 7 for (long i = 2; i < lim; i++) { 8 if (arry[i]) { 9 long tmp = arry[i]; // 素数を取り出す 10 for (long j = i + i; j < siz; j += tmp) { 11 arry[j] = 0; // 素数の倍数をクリア 12 } 13 } 14 } 15}
最後に別の配列を用意して、非0だけを抽出すれば、素数の表が作れます。
投稿2020/06/07 11:52
編集2020/06/07 13:01総合スコア6851
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/06/07 11:56
2020/06/07 12:23
2020/06/07 12:25
2020/06/07 12:39
2020/06/07 13:26 編集
2020/06/10 11:34
0
素数の計算を行う場合、巨大な配列を扱うことになるかと思いますが、スタック・ヒープ・キャッシュの仕組みについて理解しておく必要があるかと思います。
スタックというのはCのローカル変数などを保持しておくための領域で、スタック領域のサイズは小さめにとってあります。なのでグローバル変数・ローカル変数で巨大な固定長の配列を用意するとコンパイラがエラーを出します。スタック領域のサイズは大きくすることもできますが、下述のキャッシュの仕組みにより推奨されません。
ヒープはスタックよりも多くのデータを扱うことができます。ただしヒープは動的にメモリを確保する必要があります。最近のPCはRAM(主記憶メモリ)を4GB以上搭載していることが多いのでヒープを大量に使ってもメモリ不足になることはあまりないかと思います。ヒープがRAMを食いつぶしてもOSにメモリスワップの仕組みがあるのでRAMの容量以上のメモリをプログラムが使用してもプログラムが停止することはないかと思います。ただしメモリスワップは急激なパフォーマンスの低下を招きます。
PCに搭載されているプロセッサ(CPU)にはキャッシュ(キャッシュメモリ)という仕組みがあります。キャッシュはデータの局在性を用いた高速化の仕組みです。スワップ領域のサイズが小さい理由の一つとしては、わざと近くのメモリ領域だけで計算をさせるという意図があります。ヒープを使うとデータの局在性が失われ、計算速度が低下します。
こんなことを言われてもわからないと思いますが、一つわかりやすい例をあげます。複雑なアルゴリズムのほうが簡単なアルゴリズムよりも高速に計算しそうに思われます。理論上はオーダー…つまりO(n)やO(n^2)などのこと、をさげるなど高速な計算に寄与しそうに思われますが、複雑なアルゴリズムはプログラムサイズと使用するデータ量を増加させやすく、結果高速化にはつながらないということがありえます。
素数の計算には様々な手法がありますが、理論的な計算速度の他にもデータの局在性による影響も絡んでいるということを頭の隅にいれておくとよいかと思います。
投稿2020/06/10 12:16
総合スコア667
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。