3

優れたプログラミング習慣を促進し、コードの効率を高めるために(「兄と私はいくつかのコードについて議論しています」を読んでください)、経験豊富なプログラマーにこの質問を提案します。

「より良い」コードのブロックはどれですか?コードを読むのが面倒な人にとって、forループ内に条件を配置して、冗長コードの量を減らすことは、外部に配置して2つのforループを作成するよりも価値がありますか?どちらのコードも機能します。問題は、効率と読みやすさです。

    - (NSInteger)eliminateGroup {
            NSMutableArray *blocksToKill = [[NSMutableArray arrayWithCapacity:rowCapacity*rowCapacity] retain];
            NSInteger numOfBlocks = (NSInteger)[self countChargeOfGroup:blocksToKill];
            Block *temp;
            NSInteger chargeTotal = 0;

//Start paying attention here

            if (numOfBlocks > 3) 
                for (NSUInteger i = 0; i < [blocksToKill count]; i++) {
                    temp = (Block *)[blocksToKill objectAtIndex:i];
                    chargeTotal += temp.charge;
                    [temp eliminate];
                    temp.beenCounted = NO;
                }
            }
            else {
                for (NSUInteger i = 0; i < [blocksToKill count]; i++) {
                    temp = (Block *)[blocksToKill objectAtIndex:i];
                    temp.beenCounted = NO;
                }
            }   
            [blocksToKill release];
            return chargeTotal;
        }

または...

        - (NSInteger)eliminateGroup {
            NSMutableArray *blocksToKill = [[NSMutableArray arrayWithCapacity:rowCapacity*rowCapacity] retain];
            NSInteger numOfBlocks = (NSInteger)[self countChargeOfGroup:blocksToKill];
            Block *temp;
            NSInteger chargeTotal = 0;

//Start paying attention here

            for (NSUInteger i = 0; i < [blocksToKill count]; i++) {
                temp = (Block *)[blocksToKill objectAtIndex:i];
                if (numOfBlocks > 3) {
                    chargeTotal += temp.charge;
                    [temp eliminate];
                }
                temp.beenCounted = NO;
            }
            [blocksToKill release];
            return chargeTotal;
        }

これはゲーム用であることに注意してください。このメソッドは、ユーザーが画面をダブルタップするたびに呼び出され、forループは通常1〜15回、最大64回の反復で実行されます。それほど重要ではないことを理解しています。これは主に、条件文がどれほどコストがかかるかを正確に理解するのに役立つためです。(読んでください:私は自分が正しいかどうか知りたいだけです。)

4

8 に答える 8

9

最初のコードブロックは、反復全体を通してnumOfBlocks> 3のチェックがtrueまたはfalseのいずれかであるため、よりクリーンで効率的です。

2番目のコードブロックはコードの重複を回避するため、リスクが低くなる可能性があります。ただし、概念的にはより複雑です。

2番目のブロックは追加することで改善できます

bool increaseChargeTotal = (numOfBlocks > 3)

ループの前に、ループ内の実際のチェックの代わりにこのブール変数を使用して、反復中に変更されないという事実を強調します。

個人的には、この場合、ループ本体が小さく、条件がループの外部にあることを明確に示しているため、最初のオプション(重複ループ)に投票します。また、それはより効率的であり、「一般的なケースを速くする」というパターンに適合する可能性があります。

于 2009-06-29T04:38:37.097 に答える
8

他のすべての条件が同じであれば、ループのすべての反復ではなく1回テストを実行するため、2つの別々のループを持つ方が一般的に高速になります。反復ごとのループ内の分岐は、パイプラインストールと分岐の予測ミスのために大幅に遅くなることがよくあります。ただし、分岐は常に同じように進むため、分岐予測を備えたCPUを使用していると仮定すると、最初の数回を除いて、CPUはほぼ確実にすべての反復で分岐を正しく予測します(ARMチップが使用されているかどうかはわかりません) iPhoneには分岐予測ユニットがあります)。

ただし、考慮すべきもう1つのことは、コードサイズです。2つのループのアプローチでは、特にループの本体の残りの部分が大きい場合に、より多くのコードが生成されます。これにより、プログラムのオブジェクトコードのサイズが大きくなるだけでなく、命令キャッシュのパフォーマンスが低下します。キャッシュミスが大幅に増加します。

すべてのことを考慮して、コードがアプリケーションの重大なボトルネックでない限り、ループ内のブランチを使用します。これにより、コードがより明確になり、「繰り返しない」という原則に違反しなくなります。ループの1つに変更を加え、2ループバージョンで他のループを変更するのを忘れた場合、あなたは傷ついた世界にいます。

于 2009-06-29T04:47:31.810 に答える
8

「より良い」という要件を定義せずにこれに答える方法はありません。実行時の効率ですか?コンパイルされたサイズ?コードの可読性?コードの保守性?コードの移植性?コードの再利用性?アルゴリズムの証明可能性?開発者の効率?(私が見逃した人気のある測定値についてコメントを残してください。)

実行時の絶対効率が重要な場合もありますが、質問にうなずくほど一般的に想像されるほど頻繁ではありませんが、少なくともテストは簡単です。多くの場合、これらすべての懸念が混ざり合っており、最終的には主観的な判断を下す必要があります。

ここでのすべての答えは、これらの側面の個人的な組み合わせを適用することであり、すべての人が正しい状況で正しいため、人々はしばしば激しい聖戦に巻き込まれます。これらのアプローチは最終的に間違っています。唯一の正しいアプローチは、あなたにとって重要なことを定義し、それに対して測定することです。

于 2009-06-29T04:59:04.590 に答える
5

メソッドの開始/終了時に、数十の比較を実行するのにかかる時間よりも、無意味で不要な[blocksToKillの保持]/[blocksToKillの解放]に多くの時間を浪費することになります。戻った後はアレイが不要であり、それまでにクリーンアップされることはないため、アレイを保持する必要はありません。

私見ですが、コードの重複はバグの主な原因であり、可能な限り避ける必要があります。

高速列挙を使用するためのJensの推奨事項と、明確に名前が付けられたブール値を使用するためのAnttiの推奨事項を追加すると、次のようになります。

    - (NSInteger)eliminateGroup {
        NSMutableArray *blocksToKill = [NSMutableArray arrayWithCapacity:rowCapacity*rowCapacity];
        NSInteger numOfBlocks = (NSInteger)[self countChargeOfGroup:blocksToKill];
        NSInteger chargeTotal = 0;

        BOOL calculateAndEliminateBlocks = (numOfBlocks > 3);
        for (Block* block in blocksToKill) {
            if (calculateAndEliminateBlocks) {
                chargeTotal += block.charge;
                [block eliminate];
            }
            block.beenCounted = NO;
        }
        return chargeTotal;
    }

プロジェクトを終了し、プログラムが十分に高速に実行されていない場合(2つの大きなif)、プロジェクトのプロファイルを作成してホットスポットを見つけ、そのブランチを検討するために費やした数マイクロ秒が検討に値するかどうかを判断できます。今はまったく考える価値がありません。つまり、どちらがより読みやすく、保守しやすいかが唯一の考慮事項です。

于 2009-06-29T05:50:09.050 に答える
5

私は2番目のオプションで行きます。ループ内のすべてのロジックが完全に異なる場合は、2つのforループを作成するのが理にかなっていますが、一部のロジックは同じであり、一部は条件に基づいて追加されます。したがって、2番目のオプションはよりクリーンです。

最初のオプションの方が高速ですが、わずかにそうです。ボトルネックが見つかった場合にのみ使用します。

于 2009-06-29T04:48:41.780 に答える
4

私の投票は2番目のブロックに強く賛成です。

2番目のブロックは、ロジックの違いを明確にし、同じループ構造を共有します。読みやすく、保守しやすくなっています。

最初のブロックは、時期尚早の最適化の例です。

ブール値を使用してこれらすべてのLTE比較を「保存」することに関しては、この場合、それが役立つとは思いません。機械語には、まったく同じ数とサイズの命令が必要になる可能性があります。

于 2009-06-29T04:49:56.007 に答える
3

「if」テストのオーバーヘッドは、少数のCPU命令です。マイクロ秒未満です。ユーザーの入力に応じてループが数十万回実行されると思わない限り、それはノイズの中で失われます。コードが小さくて理解しやすいので、2番目のソリューションを使用します。

ただし、どちらの場合も、ループを次のように変更します

for(temp inblocksToKill){...}

これは、配列の各要素を手動で取得するよりも、読みやすく、かなり高速です。

于 2009-06-29T04:49:57.753 に答える
0

読みやすさ(したがって保守性)は、パフォーマンスの名の下に犠牲にすることができ、犠牲にする必要がありますが、パフォーマンスが問題であると判断された場合に限ります。

2番目のブロックはより読みやすく、速度が問題にならない限り/まではより良いです(私の意見では)。アプリのテスト中に、このループが許容できないパフォーマンスの原因であることがわかった場合は、保守が難しくなったとしても、必ずそれを高速化するようにしてください。しかし、あなたがしなければならないまでそれをしないでください。

于 2009-06-29T05:16:06.067 に答える