某所で素数を列挙するプログラムを見かけて、自分も作ってみようという気になりました。また、ついでに C言語、C++、C# でも同じように作成して、そのコードの違いや実行速度の差を検証もしてみました。私の当初の予定では、圧倒的に C++ が高速だと思ってました。さて、その結果はどうなったでしょうか。
つまり、1から順番に計算した場合は、今まで素数と判定できた値で除算して試せば、他の数字は確認する必要がないですよね。そのため、今まで出てきた素数をまとめてどこかに覚えておくことが必要です。そこで使うのがリスト構造です。リスト構造は、総数が分からない集合体を作っていくのにとても都合が良いのです。リスト構造は別途解説していますので、詳しくは以下を参照してください。
指定の値で割り切れるかどうかです。例えば 3 で割り切れるかどうかだと、n / 3 * 3 の結果が元の値に戻れば、それは割り切れていると判断できます。整数型の場合は、割り算の結果、小数点以下が発生した際は切り捨てられますから、同じ値を掛けても元の値に戻りません。この性質を利用します。

C言語にはリスト構造を処理する関数が用意されていないので、自分で処理系を用意する必要があります。リスト構造で1つのユニットに必要なのは、値と次のユニット位置を示すポインタです。そこで、最初に構造体でユニットを定義しています。また、ユニットを取り出す専用の GetUnit という関数も用意しました。

プログラム全体は言語に関わらず全て同様としています。最初に調査の最大数入力して、その数字までを 2 から順番に調査して、素数であれば画面に表示します。最後に見つかった素数の数を表示しています。なお、処理時間も表示するようにはしていますが、これは後述する実行速度比較のための仕掛けで、素数判定には何も関係ありません。
※ マジでよく冷えます。夏場のゴルフの必需品になりました。

Cのプログラムと比較するとかなりシンプルですよね。クラスを使わずとも、ここまで可読性が良くなるのがC++の魅力でもあります。コード内に clock の値を読み込む箇所がありますが、ここは実行速度計測に使っていますので、素数判定には影響ありません。

こちらもシンプルです。パッと見では殆ど違いがわからないぐらい近いです。処理の流れに関しては、上記と全く同様です。時間計測は C# に用意されている Stopwatch を使用しています。
いずれもリリースビルドで、プログラム内で clock または Stopwatch で計測した値を提示しています。内藤的には意外な結果になってます。予想以上に C# が速いです。これはまあ、自分が現状で最も多用しているコンパイラなので、速度が出る記述方法をある程度理解しているためとも言えます。
C# は他にはデバッグビルドとリリースビルドでびっくりするぐらい実行速度が異なりました。試しにデバッグビルドで実行すると、こんな処理時間となります。
ここまで遅くなると不気味なぐらいですね…。あと、C言語が予想外に遅かったです。これは malloc による動的メモリ確保が遅いのだと予想しています。他の言語はメモリ確保はシステム側に任せてますので、無駄がないのではないかと。ゲーム制作の現場だと、ちまちまメモリ確保は断片化の原因になるので、まとめてどかんと確保して、自分でメモリの割り当てを管理していました。正直面倒でした…
- アルゴリズム
つまり、1から順番に計算した場合は、今まで素数と判定できた値で除算して試せば、他の数字は確認する必要がないですよね。そのため、今まで出てきた素数をまとめてどこかに覚えておくことが必要です。そこで使うのがリスト構造です。リスト構造は、総数が分からない集合体を作っていくのにとても都合が良いのです。リスト構造は別途解説していますので、詳しくは以下を参照してください。
指定の値で割り切れるかどうかです。例えば 3 で割り切れるかどうかだと、n / 3 * 3 の結果が元の値に戻れば、それは割り切れていると判断できます。整数型の場合は、割り算の結果、小数点以下が発生した際は切り捨てられますから、同じ値を掛けても元の値に戻りません。この性質を利用します。
- C言語での実装

C言語にはリスト構造を処理する関数が用意されていないので、自分で処理系を用意する必要があります。リスト構造で1つのユニットに必要なのは、値と次のユニット位置を示すポインタです。そこで、最初に構造体でユニットを定義しています。また、ユニットを取り出す専用の GetUnit という関数も用意しました。

プログラム全体は言語に関わらず全て同様としています。最初に調査の最大数入力して、その数字までを 2 から順番に調査して、素数であれば画面に表示します。最後に見つかった素数の数を表示しています。なお、処理時間も表示するようにはしていますが、これは後述する実行速度比較のための仕掛けで、素数判定には何も関係ありません。
※ マジでよく冷えます。夏場のゴルフの必需品になりました。
- C++での実装

Cのプログラムと比較するとかなりシンプルですよね。クラスを使わずとも、ここまで可読性が良くなるのがC++の魅力でもあります。コード内に clock の値を読み込む箇所がありますが、ここは実行速度計測に使っていますので、素数判定には影響ありません。
- C#での実装

こちらもシンプルです。パッと見では殆ど違いがわからないぐらい近いです。処理の流れに関しては、上記と全く同様です。時間計測は C# に用意されている Stopwatch を使用しています。
- 処理速度の比較
C++ | 4.147sec | 4.143sec | 4.130sec | 4.144sec | 4.165sec |
C# | 4.157sec | 4.191sec | 4.171sec | 4.201sec | 4.208sec |
C | 8.744sec | 8.852sec | 8.771sec | 8.837sec | 8.697sec |
いずれもリリースビルドで、プログラム内で clock または Stopwatch で計測した値を提示しています。内藤的には意外な結果になってます。予想以上に C# が速いです。これはまあ、自分が現状で最も多用しているコンパイラなので、速度が出る記述方法をある程度理解しているためとも言えます。
C# は他にはデバッグビルドとリリースビルドでびっくりするぐらい実行速度が異なりました。試しにデバッグビルドで実行すると、こんな処理時間となります。
C++ | 7.692sec | 7.604sec | 7.711sec | 7.699sec | 7.741sec |
ここまで遅くなると不気味なぐらいですね…。あと、C言語が予想外に遅かったです。これは malloc による動的メモリ確保が遅いのだと予想しています。他の言語はメモリ確保はシステム側に任せてますので、無駄がないのではないかと。ゲーム制作の現場だと、ちまちまメモリ確保は断片化の原因になるので、まとめてどかんと確保して、自分でメモリの割り当てを管理していました。正直面倒でした…
総括ですが、以前は遅いと言われていた C# がここまで速度改善が出来ているとは思いませんでした。正直、もう少し遅いと思っていました。ただ、今回はプログラムサイズ的には小さかったため、もしかしたら CPU キャッシュに収まったから速かっただけなのかもしれません。そう考えるとデバッグビルドで遅いのは、キャッシュから外れてるため…とも言えます。まあ、想像なので何が正しいのかは分かりませんが、何かの参考になれば幸いです。
※ ドリンクを冷やして持ち運ぶならこの組み合わせが最強です。朝、ゴルフ場に出掛けて、昼、飲んでもキンキンに冷えてます…というか、一部内容物が凍ってました。凄いです。
今回提示したプログラムのサンプルをダウンロードできるようにしておきました。興味があればご参照ください。
PrimeSrcSample.zip
20240704-1800
※ 20240705-1120追記
C の動作速度が遅いのは malloc だと確信があり、また X(Twitter)でも指摘がありましたので、毎回メモリ確保を取りやめて、最初に丸ごと動的配列確保として実行させてみました。その結果は、
結果、C が一番現時点で高速に動作していることが分かります。まー、ただここまで来ると誤差範囲ではありますよねぇ。以下が実行に使用したプログラムです。

※ ドリンクを冷やして持ち運ぶならこの組み合わせが最強です。朝、ゴルフ場に出掛けて、昼、飲んでもキンキンに冷えてます…というか、一部内容物が凍ってました。凄いです。
今回提示したプログラムのサンプルをダウンロードできるようにしておきました。興味があればご参照ください。
PrimeSrcSample.zip
20240704-1800
※ 20240705-1120追記
C の動作速度が遅いのは malloc だと確信があり、また X(Twitter)でも指摘がありましたので、毎回メモリ確保を取りやめて、最初に丸ごと動的配列確保として実行させてみました。その結果は、
C (動的配列) | 4.073sec | 4.104sec | 4.118sec | 4.143sec | 4.136sec |
結果、C が一番現時点で高速に動作していることが分かります。まー、ただここまで来ると誤差範囲ではありますよねぇ。以下が実行に使用したプログラムです。

コメント
コメント一覧 (3)
CとC++の差は連結リストと配列(vector)というデータ構造の違いに寄る所が大きそうです
C++の実装をvectorからlistに変えたらCに近くなるかもしれません
C#のListはインデックスアクセスが出来る事から実装はC++のvectorに近いかも?
内藤時浩
が
しました
C#がこんなに速くて、Cがこんなに遅いとは意外でした
C++のデバッグビルドが遅いのはSTLのデバッグ用チェックコードが有効になるのが原因だと思います
デバッグを有効にすると、例えば ①vectorのイテレータ取得 → ②vectorサイズ伸張 → ③①で取得したイテレータでアクセス
の③でアサーションで停止かメッセージが表示されます
内藤時浩
が
しました