既出な内容でしたらすみません。特に過去ログは調べていません。
文字配列の中身を比較するコード(それぞれの要素で等しければ+1、等しくなければ何もせず)の中で、
以下のようなC言語のコードを見かけたのですが、
c
1while (*s && *t){ 2 count += (*s++ == *t++); 3}
2行目でif
文を使わないのは、実行速度を速くするためなのでしょうか?
仮にそうだとしても、等価演算子は1
を返すためのものではないですから、実行速度を犠牲にしてでも、
c
1if(*s++ == *t++) count++;
と書いた方がいいような気がします。
皆様はどう思われますか?
等価演算子で1
を返す言語はC言語ぐらいといえばそうかもしれませんが、説明に他の言語を絡めても構いません。
なんていうか、くだらない質問ですみません…皆様のご意見を伺いたいです。よろしくお願いします。m(_ _)m
追記
皆さま回答ありがとうございます。m(_ _)m
raccy様からいただいた検証コードを試してみましたので、この場を借りて掲載させてください。(2回分です。)
plain
1 user system total real 2count_eq 0.000000 0.000000 5.060000 ( 5.073679) 3count_if 0.000000 0.000000 5.040000 ( 5.045016) 4count_if_after 0.000000 0.000000 5.160000 ( 5.170517) 5count_if_null 0.000000 0.000000 5.130000 ( 5.135678) 6count_r_eq 0.000000 0.000000 5.200000 ( 5.206062) 7count_r_if 0.000000 0.010000 5.160000 ( 5.151430) 8count_r_if_after 0.000000 0.000000 5.090000 ( 5.103209) 9count_r_if_null 0.000000 0.000000 5.080000 ( 5.088278) 10result -> line: 10000, total count: 204632007 11 12 user system total real 13count_eq 0.000000 0.000000 5.140000 ( 5.150710) 14count_if 0.000000 0.000000 5.040000 ( 5.050327) 15count_if_after 0.000000 0.000000 5.100000 ( 5.104275) 16count_if_null 0.000000 0.000000 5.190000 ( 5.203628) 17count_r_eq 0.000000 0.000000 5.110000 ( 5.111663) 18count_r_if 0.000000 0.010000 5.150000 ( 5.137537) 19count_r_if_after 0.000000 0.000000 5.100000 ( 5.118537) 20count_r_if_null 0.000000 0.000000 5.080000 ( 5.077878)
パッとみた感じではif
の方が速く見える部分もあります。
環境
- MacBook Air (13-inch)
- Intel Core i5
- macOS High Sierra (10.13.1)
- gcc 4.2.1
- ruby 2.3.3p222 (2016-11-21 revision 56859) [universal.x86_64-darwin17]
皆様回答ありがとうございました。
BA悩みましたが、検証コードまで書いてくださったraccy様をBAとさせていただきます。
改めてありがとうございました。m(_ _)m
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答10件
0
ベストアンサー
パフォーマンスは推測より実測です。
ということで、パフォーマンスを図るためのコード類を作りました。長くなったので下記のgistにあげています。
https://gist.github.com/raccy/c7a7cdd9ff61f43768705076a5a737ca
RubyとGCCがある環境でruby count_bench.rb
と実行すれば、コンパイルして、テスト用のテキストを作って、速度を測ってくれます。
うちの環境の結果
$ ruby count_bench.rb user system total real count_eq 0.000000 0.000000 0.000000 ( 6.268309) count_if 0.000000 0.000000 0.000000 ( 6.683875) count_if_after 0.000000 0.000000 0.000000 ( 6.206465) count_if_null 0.000000 0.000000 0.000000 ( 6.046037) count_r_eq 0.000000 0.016000 0.016000 ( 6.874686) count_r_if 0.000000 0.000000 0.000000 ( 6.486357) count_r_if_after 0.000000 0.000000 0.000000 ( 5.993386) count_r_if_null 0.000000 0.000000 0.000000 ( 6.013234) result -> line: 10000, total count: 204655626
モバイル向けCPUであるCore i7-7Y75なのでブレが大きくなるときがありますが、ほぼほぼ一緒です。ただ、これは内の環境であって、異なるCPU、異なるコンパイラなら結果が違ってくることもあり得ます。コードとして処理量が必ず多いとか、全体の計算量が異なるアルゴリズムであると言った場合を除き、実際に計らないことにはこっちのほうが必ず速くなるなどと言う話にはならないと私は思っていますし、計った場合もその環境での結果であって、別の環境でも必ずしも同じとは限らないですし、逆の結果になる環境もあり得ると思います。
ということで、パフォーマンスの話をするのであれば、まず計れ、話はそれからだと思います。
投稿2017/12/26 15:43
総合スコア21733
0
C
1if(*s++ == *t++) count++;
の方が読みやすいですね。
こう書きたいです。
モダンな言語は(無理やりキャストなどしない限り)「敢えて」こういう書き方しかできないようになっています。
C 言語は TRUE == 1 なので真の値は 1 をとることが多くこういう書き方もできますが、どの関数でも必ず真の値として 1 を返すとは限らないためバグの原因となります。
if 文を使った方がいいと思います。
追記
実際にソースがどうコンパイルされるかはコンパイラによっても違いますし、そのコードが走るプラットフォームによっても最適化の方法は違います。
特定のプラットフォーム、特定のコンパイラに最適化したコーディングをすることは確かにありますが、それはコーディングの基本ではなく特定の場面に特化したテクニックの一つだと思っています。
GCC でコンパイルしてパイプラインの有効な CPU で動かすことを前提にしたコーディングは、場面に合わせて行う様々な最適化の一つです。優先して行うべきものではなく、成果物が現場の要件に合わなかった時(問題があり、それで問題が解決する時)に調整として行うものだというのが私の考えです。
異論はあると思います。
もっと言えば、実装の隠蔽によって複数のプラットフォームにソースを使いまわせるのは高レベル言語の長所の一つだと思っていますので、低レベルな処理を意識的に行うならばその部分にインラインアセンブラを使うのが好ましいと思っています。そうすればコンパイラを変えても大丈夫です。
あるいはコメントとして残しておくべきでしょう。そうすれば後任による書き換えを防ぐこともできます。
投稿2017/12/25 12:30
編集2017/12/26 02:23総合スコア28656
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/25 12:59
2017/12/25 13:12
2017/12/26 02:42
2017/12/26 02:50 編集
2017/12/26 04:49
0
if文でない書き方はアリです。まず、一行に、簡潔に書けることがメリットです。if文で書く場合、私のコーディング規約ではこうなります。
C
1 if (*s++ == *t++) { 2 count++; 3 }
たったこれだけの事に3行も(笑)使わなければなりません。言及された方がいる三項演算子なら
C
1 a = (条件) ? b : c; 2 3 if (条件) { 4 a = b; 5 } else { 6 a = c; 7 }
一行が5行にもなる。ソースコードの凸凹(インデント)も、何か見苦しく感じられる。行数が増えれば、一画面で見渡せる情報が減り、ソースコードの見通しを悪くする要因になります。そもそも、簡潔に書くことはプログラミング言語を発展させてきた大きな動機だったはず。
「*s++ == *t++
自体が複雑すぎ」。念の為、
C
1 while (*s != '\0' && *t != '\0') { 2 if (*s == *t) { 3 count++; 4 } 5 s++; 6 t++; 7 }
わかりやすくなった、誤解の余地が無くなった、けれども、冗長にも感じられます。難しすぎるトリッキーな書き方には、いわばイディオムもあります。慣れが要るのです。C言語には、こうしたイディオムを作ってきた歴史があり、当時の世界中のプログラマが、それを支持したことも普及の要因だと思っています。
イディオムに慣れた人が多ければ通用する書き方、即ちそれは、イディオムに慣れた人が少なければ通用しない・・・私も堅気の仕事(笑)では慎重になります。でも互換性が求められるCコンパイラは、今後もこうしたイディオムをコンパイルし続けます。トリッキーなコードを積極的に書く必要は無いけれど、読めたほうが安心、と思っていれば良いのでは。
次に、生成される機械語の質の良さです。x86, 64bit用GCC 5.4.0で、まずif文をコンパイルしてみた、その肝心部分は、こうです。
cmpb %al, %cl jne .L2 addl $1, -4(%rbp) .L2:
2文字を比較(cmpb命令)、条件分岐(jne命令)と、当たり前のコードですが、既に多くの方が言及されているように、分岐命令はパイプラインを乱す要因です。
count += (*s++ == *t++); をコンパイルした場合、肝心の部分はこうです。
cmpb %al, %cl sete %al movzbl %al, %eax addl %eax, -4(%rbp)
sete 命令を見慣れない方は多いと思うので念の為:「set + e」という意味の命令名で、e は Equal の e、「比較した結果が等しいならレジスタに1を、等しくなければ0をセットする」。C言語の比較演算子そのものです。ソースコードの違いに対応する違いがあるのですね。
ここに分岐命令はありません。パイプラインを乱す要因を無くすことができたのです。「この命令を実行したら、次はこの命令…」と流れ作業がスムーズに回る様子が目に浮かぶようです。この感覚、どう言ったら良いか、軽快・スッキリ感ですかね。常に速く動作する、とは言いませんが、ハードウェアを無駄なく上手に使っている感じがします。
何と言っても、C言語はハードウェア寄りの言語だから。C言語レベルで不可解なことが、アセンブリ言語レベル・ハードウェアレベルで理解できる事は少なくありません。
もちろん、こうしたことまで考えてプログラムする事は重要でなくなった、それこそが技術の進歩なのは皆様が仰る通りです。でもゼロになったとは思いません。現に、上に示した通り、コンパイラやCPUの技術に反映されてきているし、例えばDSP(Digital Signal Processor)を使う現場では1クロックでも減らす努力があるのでは、と想像します。
投稿2017/12/26 01:44
編集2017/12/26 04:05総合スコア1380
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/26 01:55
2017/12/26 01:58
2017/12/26 02:01 編集
2017/12/26 02:01
2017/12/26 02:36
2017/12/26 03:47 編集
2017/12/26 04:58
0
解決済みですが参考までに。
少なくともCにあっては、多少の記述の違いはコンパイラの最適化によって吸収される範疇です。ですので、速度に違いが出ることはそうそうありません。ほとんどの場合、可読性の方が重要視されます。(いまやコンピュータの計算速度は充分すぎるほど高速であり、大抵の場合、さらなる高速化を目指すよりもメンテナンスのしやすさに注力したほうがメリットが大きい)
といっても、可読性はプログラマの主観によるところが大きく、個人差の影響が出るという事実もあります。私の場合は、基本は一文一式の考えでプログラムを書きます。前置、後置インクリメント/デクリメントを使うことで一文二式以上にすることもありますが、二つ以上インクリメント/デクリメントを使うのは好きではありません。
たとえば
C
1 x++ + x++
というように同じ変数を対象とした場合なんかが怖いからです。
そして、最後に一言
C
1count += (*s++ == *t++);
ここで、==が成立する場合、countの加算値は**「0以外の整数値」**となります。(1とは限りません)つまり、この計算の結果は確定しません。
投稿2017/12/31 12:51
総合スコア4830
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/31 13:07
2017/12/31 13:38
2018/01/01 01:58
2018/01/01 02:24
0
ご質問における、if 文を使わないコードを(a)とし、if 文を使うコードを(b)とすると、
問題とされるのは以下の3点ではないでしょうか。
0. (a)が正しいかどうか
0. (a)が好ましいかどうか
0. 自分ならどうするか
1.については、「特定の条件下なら正しい」ので、それを良しとする人からすれば正しく、
それをグレーもしくは良しとしない人からすれば誤りとなります。
何をもって正しいとするか、という判断を伴う問題とも言えますね。
2.については、「人による」と言わざるを得ません。
移植性、可読性、俊敏性、流動性など、重視される面は様々です。
また、「好ましいかどうかを判断する人は誰なのか」にもよりますね。
(もちろん 1.の時点で正しくないと判断されれば(b)を採用するでしょうが)
最後に 3.について、私は以下のように考えました。
1.については、自分はさておき、誤りであると判断する人が少なからずいるため、グレー。
2.については、1.の判断により、好ましくないと考える。
したがって、新規のソースコードに採用するのは(b)。
ただし、限定された条件の元であえて(a)を採用する必要がある場合は、その意図をコメントに記載する。
なお、既存のソースコードに(a)があり、意図が不明であればそのまま残す。
(もし可能なら、その意図を確認した上で再度判断する。)
いずれにしても、きちんと機能することは大前提ですが、
誰(未来の自分も含む)が見てもその意図が伝わることが肝要かと思います。
投稿2017/12/28 01:57
総合スコア18
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
質問の意図とずれているかもしれませんけど。C言語のプロでも情報処理のプロでもない、プログラミングは実務での必要性に鑑みて独学で習得した人間の一人として言わせていただくなら、
*s++ == *t++
もうこの時点で複雑すぎて話にならないです。
大昔のマイコンならいざ知らず、現代のプログラミング環境においては、余程特殊な要件がない限り、プログラムのチューニング目的で裏技的なコーディング技術を駆使する必要性は低いと認識しています。基本的には人間の目から見た見やすさを優先すべきと考えます。見た目のスタイルが似ているperlをよく使いますが、私はインクリメント・デクリメント演算子は必ず単独で使います。
投稿2017/12/25 15:43
総合スコア13669
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/26 00:52 編集
0
等価演算子は1を返すためのもの
Cに於いてはそうですよ。
他の言語ではそうとは限らない(そうで無いことが多い)ので、
そういう言語で、count += bool2int(s[i] == t[i]);
のように書くのはやり過ぎだと思います。
実行速度を速くするためなのでしょうか?
速度より、コンパクト(簡潔)に書くためだと思います。
だらだら書くのが嫌いなのでしょう。この程度だとわかりにくくなるわけじゃ無いので。
投稿2017/12/25 12:57
総合スコア84421
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/25 13:14
2017/12/25 13:23
2017/12/25 13:33
2017/12/25 13:52
2017/12/26 00:00
2017/12/26 00:11
2017/12/27 02:57
2017/12/27 05:55
2017/12/27 07:52
2017/12/27 08:21
0
関数の戻り値はC99で標準入りしていたbool型を使っているなら1に変換されるのでifを書かなくてもいいとは思います。それ以外で真理値を代替している場合はだめですが。
==演算子など組み込みの演算子については、やはり1とみなして問題ありません。
実行速度についてはコンパイラの最適化で消えるたぐいのものです。消えなかった場合はCPUのパイプラインを崩すのでifを使わないほうが良いと思います。
問題の可読性ですが、そもそもoperator[]
というシンタックスシュガーを使わずにポインタ演算とか可読性のかけらもないのであれですが、はっきりいって文脈次第なのでなんとも・・・。
ちなみに私なら問題のコードは
c
1for(size_t i = 0; '\0' != s[i] && '\0' != t[i]; ++i) count += (s[i] == t[i]);
と書くと思います(s
とt
って多分文字列ですよね?)
投稿2017/12/26 03:30
総合スコア5850
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/26 03:49
2017/12/26 04:37
2017/12/26 12:23
2017/12/27 02:14
0
こんにちは。
C言語はメンテナンス性より性能を取る言語なので微妙ですが、私の場合は通常は後者ですね。
速度に差があり、かつ、速度優先な処理やライブラリの場合なら前者を取ることもあるかもしれませんが、そこまでギチギチな最適化が必要なケースは事実上ないです。
投稿2017/12/25 13:08
総合スコア23272
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/25 13:18
2017/12/25 13:19
2017/12/25 13:37
2017/12/25 13:58 編集
2017/12/25 14:09
2017/12/25 14:43
2017/12/25 14:55
2017/12/25 15:38
2017/12/26 00:19
2017/12/26 05:08
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/27 02:11