5

先日、Verilogでクールなトリックを学びました。繰り返し何かをする必要があるとき。シフトレジスタを使用して、インクリメントの数をカウントできます。1をLSBからMSBにシフトするだけで、MSBに到達したら完了です。

Cでは、次のようになります。

for(j=0b1; !(j & (1<<16)); j=j<<1)
{
/*do a thing 16 times*/
}

ビット幅の関係で使用が制限されていることは知っていますが、追加が必要ないため高速です。だから私の質問:これの使用法はありますか?Cまたは他の高級言語で使用する価値はありますか?

たぶん、リソースが限られている組み込みシステムで。

ありがとう

4

8 に答える 8

8

これは非常に価値がありません。これにより、コードがはるかにクリーンで読みにくくなり、パフォーマンスの違いは無視できるようになります。

コンパイラーは、これらのタイプの最適化をあなたよりもはるかにうまく行うことができます。このような短いループは、パフォーマンス上の理由から展開される場合もあります。ただし、そのようにループを作成すると、コンパイラーはそれを簡単に理解できない可能性があるため、プログラムの速度を低下させる可能性さえあります。

これは実際にはマイクロ最適化のケースであり、プログラムの実行時に目立った違いが生じることはほとんどありません。

于 2012-05-25T19:35:51.770 に答える
5

コメント/回答する人のほとんどは、アスカーが何について話しているのかを本当に理解していないようです。Verilog言語はハードウェア設計用であり、ハードウェア設計はソフトウェア設計とは大きく異なり、CPUサイクルなどはありません。ただし、短い答えはまだです:いいえ。長い答え:

確かに、シフトは加算よりもはるかに簡単です。シフトの場合、FF(フリップフロップ)からFFへのロジックははるかに少なくなります。さらに、キャリーはLSBビットからMSBビットに伝搬する必要があります。これは、log2(N)レベルのロジックを意味します(Nはカウンターが到達する最大値です)。一方、シフトレジスタはN FFを使用しますが、加算器はlog2(N)FFのみを使用します。したがって、パフォーマンスと領域のトレードオフがあり、これもNに大きく依存します。加算器に関するいくつかの「独立した」情報:http: //en.wikipedia.org/wiki/Adder_%28electronics%29 シフトに関する同様の記事が見つかりませんでした。しかし、加算器を理解すれば、シフターは明白になるはずです。

これは、RTLでステートマシンを設計するときに重要になる場合があります。しかし、あなたが提示したコードは、実際には上記とは何の関係もありません。verilogのこの「for」ループは、すべての「作業」が単一のサイクルで実行されることを意味します。したがって、実際にはN個のロジックがあります。このループは実装とは何の関係もありません。verilogコンパイラを混乱させて、奇妙なものを吐き出し、シミュレーションに影響を与えることさえあるかもしれません(CPUサイクルが重要であり、上記の答えが有効である場合)。ツールの経験が豊富な人は、それについてコメントすることができます。

于 2012-05-26T06:41:29.300 に答える
2

(Stefanの回答によると、Verilogでこれを行うのではなく、Verilogバージョンに触発されたCバージョンについて質問していると思います。)

多くのアーキテクチャでは、ループ変数の追加が完全に無料であるのにビットシフトが余分な命令を必要とするため、これは実際にはさらに悪いことです。

完全に?

はい。多くのアーキテクチャには、カウンタをデクリメントしてゼロ以外の場合に分岐する単一の命令があるためです。これらの命令は、他の比較分岐命令と同じくらいの時間がかかります。一方、シフトを行う場合は、追加の命令サイクルが必要になります。プラットフォームに「同等と分岐の比較」命令がない場合はさらに悪化します。すべての命令にあるわけではありません。2つの命令で減算してゼロと比較するものもあります。

デクリメント比較分岐命令のないRISCプラットフォームでも、(1つの命令)を単純に減算してゼロ以外の分岐命令を使用できるため、カウントダウンループはおそらく高速ですが、ループではシフトが必要です。 (1つの命令)およびビット単位-および(1つの命令)分岐の前-ゼロの場合。そして、それはあなたがゼロの場合でもブランチを持っていると仮定しています。

さらに、単純なfor (i = 0; i < N; i++)ループの場合、コンパイラーがそれを「カウントダウン0」ループに変換するのは簡単です。それが速い場合は、自分でそのような巧妙さを行う必要はほとんどありません。

于 2012-05-26T06:50:56.920 に答える
1

実際のCPUでは、加算は実行できる最速のことの1つです。ビットシフトはこれ以上速くはありません。また、コンパイラが効率的に最適化するのが難しくなります。

于 2012-05-25T19:38:47.860 に答える
1

もっと早く?よろしいですか?少なくともMIPSアーキテクチャでは、ビットシフトは加算とまったく同じくらいの時間がかかります。これが最も一般的な消費者向けプロセッサアーキテクチャにも当てはまらないとしたら、私は驚きます。

その上、Oleksiが指摘するように、これはかなり読みにくいです。おそらく存在しない速度の増加の価値はありません。

于 2012-05-25T19:39:06.753 に答える
1

インクリメントは、加算の非常に特殊なケースです。ほとんどのプロセッサ、そして確かにほとんどのRISCプロセッサでは、シフトとインクリメントは実行時間で同じになります。実際、ほとんどのアーキテクチャでは、追加も必要ありません。

ループコードを慣用的に保つと、オプティマイザーはループを展開して、どのような場合でもループを高速化するのに適しています。ループメカニズムを「異常」にすると、オプティマイザーがループメカニズムを最適化できない場合があります。

于 2012-05-27T22:15:38.400 に答える
1

加算を伴わないので高速です

加算よりもシフトが速いCPUアーキテクチャはどれですか?また、その特定のアーキテクチャのコンパイラが、シフトがより高速であることが判明した場合、加算からシフトへの最適化を自動的に実行しないと思われる理由は何ですか?

これの用途はありますか?

最適化の目的で、それを使用することはありません。

他の目的では、はい、そのようなコードは、バイトの個々のビットをマスクするために一般的に使用されます。最も一般的な2つのアプローチは次のとおりです。

uint8_t mask; 

for(mask = 0x01; mask != 0x00; mask<<=1)
{
  do_something (data & mask);
}

また

for(i=0; i<8; i++)
{
  do_something (data & (1<<i));
}
于 2012-05-28T11:47:03.607 に答える
0

一般に、常に特定の回数> 0をループし、ループオーバーヘッドを最小限に抑えたい場合は、これが「最良」になると思います。

unsigned i = 16;

do {
// do something here
} while (--i);



You might get the same result with:

unsigned i = 0x8000;

do {
// do something here
} while (i>>=1);

その時点で、アセンブリを確認する必要があります。

于 2012-05-26T02:36:57.037 に答える