私は最近いくつかのOpenJDKコードを閲覧していて、intriguingbit-wise operationsに関連するコードの断片をいくつか見つけました。 StackOverflowで question についても質問しました。
ポイントを説明する別の例:
1141 public static int bitCount(int i) {
1142 // HD, Figure 5-2
1143 i = i - ((i >>> 1) & 0x55555555);
1144 i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
1145 i = (i + (i >>> 4)) & 0x0f0f0f0f;
1146 i = i + (i >>> 8);
1147 i = i + (i >>> 16);
1148 return i & 0x3f;
1149 }
このコードは Integer クラスにあります。
私はこれを見るとバカな気がします。大学でクラスを1つか2つ欠けていたか、これは私が想定していることではありませんget?単純なビット単位の演算(ANDing、ORing、XORing、shiftingなど)を実行できますが、さて、どうすれば上記のようなコードを思いつくのでしょうか。
バランスの取れたプログラマーは、ビット単位の演算をどのように行う必要がありますか?
余談...心配なのは、StackOverflowで質問に回答した人が数分で回答したことです。彼がそれをすることができたなら、なぜ私はヘッドライトで鹿のように凝視したのですか?
バランスのとれた開発者として、あなたは理解演算子とビットごとの演算が必要だと私は言うでしょう。
したがって、少なくとも、少し考えれば、上記のコードを理解できるはずです。
ビット単位の操作はかなり低レベルになる傾向があるため、WebサイトやLOBソフトウェアで作業している場合、それらをあまり使用しない可能性があります。
他のもののように、あなたがそれらをあまり使わなければ、あなたはそれらに精通しません。
したがって、あなたはすべきではない誰かが(おそらく)この種のコードを頻繁に処理するため、誰かがそれをすぐに理解できることを心配しています。おそらく、OSコード、ドライバコード、またはその他のトリッキーなビット操作を記述しています。
「ビット3と8が設定されているかどうかを判断する」、「ビット5をクリアする」、または「ビット7から12で表される整数値を見つける」などの問題を解決する方法を理解していれば、「よく丸められた」チェックリストのTwiddle Bitsボックスはできますか?.
あなたの例の内容は Hacker's Delight から来ています。これは、整数のようなデータの小さなビットを操作するための高性能アルゴリズムのコンパイルです。そのコードを書いた人は、もともと5分で吐き出しただけではありませんでした。その背後にある話は、ビットをカウントする高速で分岐のない方法が必要である可能性が高く、著者はビットの文字列をじっと見つめて問題を解決する方法を準備するのに費やす時間を持っていました。彼らが以前にそれを見たことがない限り、誰もが一目でそれがどのように機能するかを理解するつもりはありません。ビット単位の基本をしっかりと理解し、コードの実験に少し時間を費やせば、おそらくそれがどのように機能するかを理解できるでしょう。
これらのアルゴリズムを理解していなくても、存在を知るだけで「丸み」が増します。たとえば、高性能ビットカウントを処理するときになると、何を調べればよいかがわかるからです。 Googleより前の世界では、これらのことを知るのは非常に困難でした。今、それは離れてキーストロークです。
SOの質問に回答したユーザーは、以前に問題を見たことがあるか、ハッシュを学んだことがあります。彼に書いて質問してください。
あなたの例から、本当に考えずに絶対に知っておくべきことがいくつかあります。
1143 i = i-((i >>> 1)&0x55555555);
ビットパターン0x555 ...を交互のビットパターン0101 0101 0101として認識し、演算子がそれを1ビット(右側に)オフセットしていること、および&がマスキング操作(およびマスキングの意味)であることを認識する必要があります。
1144 i =(i&0x33333333)+((i >>> 2)&0x33333333);
繰り返しになりますが、これは0011 0011 0011です。また、今回は2つシフトし、再びマスキングします。シフトとマスキングは、認識すべきパターンに従っています...
1145 i =(i +(i >>> 4))&0x0f0f0f0f;
パターンが固まります。今回は00001111 00001111です。もちろん、今回は4にシフトします。マスクのサイズだけシフトするたびに。
1148 return i&0x3f;
別のビットパターンである3fは、0のブロックの後に1のより大きなブロックが続きます。
あなたが「よく丸められている」ならば、これらすべてのものは一目で明白であるはずです。これを使用するとは思わない場合でも、これを知らなければ、コードを大幅に簡略化する機会を逃してしまうでしょう。
より高いレベルの言語でも、ビットパターンは、より小さなフィールドに多くの大量のデータを格納するために使用されます。これが、ゲームで常に127/8、63/4、255/6の制限が表示される理由です。これは、これらの多くのものを格納する必要があるためです。フィールドをパックしないと、10倍も使用する必要があります。メモリの量。 (まあ、究極的には、配列に膨大な数のブール値を格納する必要がある場合、それについて考えなかった場合に使用するのと同じように、32〜64倍のメモリ量を節約できます。ほとんどの言語はブール値を多くの場合32ビットになるWordこのレベルで不快な人は、未知のものを怖がっているからといって、このようなデータを保存する機会に抵抗します。
また、ネットワーク経由で配信されたパケットを手動でパック形式で解析することなどを恐れません。これは、恐れることがなければ簡単なことです。これにより、1kパケットを必要とするゲームから200バイトを必要とするゲームまで、小さなパケットがネットワークをより効率的にスライドし、レイテンシが低下し、より高い相互作用速度が可能になります(これにより、ゲームの新しいプレイモード全体が有効になる場合があります)。
コードを認識したのは、ビデオフレームを操作するソフトウェアで以前に見たからです。オーディオCODECやビデオCODEC、ネットワークプロトコル、チップレジスタなどを定期的に使用している場合、ビット単位の操作が多数発生し、2番目の性質になります。
作業がこれらのドメインと頻繁に一致しない場合でも、気分が悪くなることはありません。私はビット単位の操作をよく知っていますが、GUIを作成する必要があるまれな場合には、速度が遅くなります。これは、レイアウト、重み付け、および拡張のすべての癖のため、他の人に2番目の性質であることは確かです。あなたの強みは、あなたが最も経験を積んでいるところです。
知っておくべき主なことは、整数がどのように表されるか(一般に、長さがプラットフォームに依存する固定長ビットベクトル)と、それらで使用できる操作です。
主な算術演算+ - * / %
は、理解しなくても理解できますが、マイクロ最適化には便利です(ほとんどの場合、コンパイラーが処理します)。
ビット操作セット| & ~ ^ << >> >>>
を使用するには、少なくともある程度の理解が必要です
ただし、ほとんどの場合、それらを使用してビットフラグをメソッドにOR
ingとして渡し、intを渡してから、設定をAND
ingすることで、いくつかを渡すよりも読みやすくなります(最大32)長いパラメーターリストのブール値。インターフェイスを変更せずに、可能なフラグを変更できます。
言うまでもなく、ブール値は通常、フラグのように一緒にパックするのではなく、バイトまたは整数で別々に保持されます
コードスニペットについては、ビットの並列カウントを行うため、アルゴリズムはO(log(n))
で実行できます。ここで、nはO(n)
の単純なループではなくビット数です。
最初のステップは理解するのが最も難しいですが、ビットシーケンス0b00
を0b00
に、0b01
を0b01
に、0b10
を0b01
に、0b11
を0b10
に置き換える必要があるというセットアップから始めると、従うのが簡単になります
したがって、最初のステップi - ((i >>> 1) & 0x55555555)
では、i
を0b00_01_10_11
と等しいとすると、この出力は0b00_01_01_10
になります。
(0x5
は0b0101
と等しいことに注意してください)
私が受け取るiuf i = 0b00_01_10_11
これは、0b00_01_01_10 - (0b00_00_11_01 & 0b01_01_01_01)
が0b00_01_10_11 - 0b00_00_01_01
であり、次に0b00_01_01_10
になることを意味します
同じ結果を得るために(i & 0x55555555) + ((i >>> 1) & 0x55555555)
を実行することもできますが、これは1つの追加操作です
次の手順も同様に行われます
ハッカーの喜びは派生的な作品です。すべての祖先は1972年のHakMemです。 http://w3.pppl.gov/~Hammett/work/2009/AIM-239-ocr.pdf
重要なことは、どのタスクでも明白なアルゴリズムが必ずしも最良であるとは限らないことを知ることです。特定の問題に対するエレガントな解決策の存在を知ることが重要である場合がたくさんあります。
組み込みシステムをプログラミングしています。私はこれをたくさん練習しました。コードを含むハッシュマップに関するリンクされた質問
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
コードを声に出して指示するのにかかる限り、私には完璧な意味がありました。 bitCount
で説明されているイベントはすぐにわかりますが、実際にビットをカウントする理由を理解するには1分かかります。ただし、コメントはすばらしいものであり、コードがハッシュの問題よりも少しだけ難しいことを理解することになります。
コードの読み取りと理解を区別することが重要です。私はbitCount
コードを解釈し、それが何をするかを読み取ることができますが、なぜそれが機能するのか、または機能することさえ証明するのに1分かかります。コードをスムーズに読み取ることができることと、コードが現状どおりである理由を理解できることには違いがあります。一部のアルゴリズムは単純に困難です。 hash
コードのwhatは理にかなっていますが、コメントはwhy何が行われているかを説明しました。ビットワイズ演算子を使用する関数が理解しにくい場合でも落胆しないでください。それらは、形式に関係なく難しいトリッキーな数学的なことを行うためによく使用されます。
私はこれに慣れています。私が慣れていない1つの主題はregexです私はビルドスクリプトで時折それらに対処しますが、毎日の開発作業では決してしません。
正規表現の次の要素の使用方法を知っています。
[]
文字クラス*
、.
、および+
ワイルドカード^
の始まりと文字列$
の終わりこれは単純なクエリを作成するのに十分であり、私が目にするクエリの多くはこれから逸脱していません。
このリストにないものはすべて、チートシートを探します。何でも、つまり{}
と()
以外-チートシートでは不十分です。これらの人たちについては、ホワイトボード、リファレンスマニュアル、そしておそらく同僚が必要になることを知っているだけで十分です。クレイジーなアルゴリズムをいくつかの正規表現の短い行にまとめることができます。
既知の要素のリストにないものを必要とする、または提案する正規表現を設計するために、私が認識し、それらをテストスイートに入れると予想される入力のすべてのクラスをリストアップします。多くの断続的な手順を使用して、正規表現をゆっくりと段階的に作成し、これらの手順をソース管理にコミットするか、コメントに残して、後で壊れたときに何が起こるはずだったかを理解できるようにします。本番用のコードの場合は、より経験のある人にレビューされることを確認します。
これは、ビット単位の演算子を使用している場所ですか?
私の見積もりでは、このようなコードが紙を引き出すか、ホワイトボードに移動して手動で操作を実行することによって何を行うかを解釈できる場合、あなたは十分に丸い資格があります。ビット演算の分野で優れたバランスのとれたプログラマーとしての資格を得るには、次の4つのことを行うことができるはずです。
一般的な操作をスムーズに読み書きできる
アプリケーションプログラマーにとって、ビットごとの演算子を使用した一般的な操作には、フラグを設定およびクリアするための|
および&
の基本的な演算子が含まれます。これは簡単なはずです。次のようなものを読み書きできるはずです
open('file', O_WRONLY | O_APPEND | O_CREAT );
// Use an OR operator ^ here and ^ here to set multiple flags
速度を落とさずに(あなたが知っていると仮定すると フラグの意味 )。
いくつかの作業でより複雑な操作を読み取れるようになります
ブランチを使わずにO(log(n))時間でビットを非常に高速にカウントし、hashCodeの衝突の数が制限された量だけ異なることを保証します メールアドレスの解析 、 電話番号 、または [〜#〜] html [〜#〜] 正規表現を使用するのは難しい問題です。これらの分野の専門家がホワイトボードに手を伸ばすには、理解するための作業を開始できないのは無理です。
多くの作業を伴ういくつかの複雑なアルゴリズムを記述できるようになる
あなたが専門家でない場合、複雑で難しいことを行うことができると期待すべきではありません。ただし、優れたプログラマーは、継続的に作業することでそれを実現できるはずです。これを十分に行えば、すぐにエキスパートになります:)
誰もが基本的なビット単位の操作を理解する必要があります。これは、多くの練習を必要とする、最適化された堅牢な方法でタスクを実行するための基本操作の構成です。
もちろん、埋め込まれた人々のように、ビット操作を日常的に行う人々は、強力な直感と巧妙なトリックのバッグを開発します。
低レベルの作業を行わないプログラマは、ビット単位の操作でどの程度のスキルが必要ですか?貼り付けたようなスタンザで座って、頭の体操やパズルのようにゆっくりと作業するのに十分です。
同様に、Web開発者がビット単位の操作について理解しているのと同様に、組み込みプログラマーもhttpについて理解しているべきだと私は思います。つまり、常に使用していないのであれば、ビット操作に慣れないことは「OK」です。
まともな大学に行ったら、離散数学の授業を受ける必要があったはずです。 2進、8進、および16進の算術および論理ゲートを学習したでしょう。
その点について混乱するのは普通のことです。もし私がWebアプリケーションを書いているのでそれが何らかの慰めであるなら、私は主にこのようなコードを見たり書いたりする必要はほとんどありませんが、私は二項算術とビット演算子の動作を理解しているからです結局、十分な時間をかけてここで何が起こっているのかを理解することができます。
携帯電話のプログラマーとして、私はこの種のことを扱わなければなりませんでした。デバイスのメモリが少ない場合、または伝送速度が重要な場合は、かなり一般的です。どちらの場合も、できるだけ多くの情報を数バイトにパックしようとします。
Windowsプログラミングの10年ほどではなく、PHP(多分それは私だけかもしれません)の5年程度のビット単位演算子の使用を覚えていませんが、一部の下位レベルのWindowsのものはビットをパックします。
「これを見るとバカにならざるを得ない」と言う。しないでください-怒りを感じます。
カウボーイプログラマーの出力に会いました。
彼は保守可能なコードを書くことを何も知りませんか?彼が一年でこれに戻って、それが何を意味するかを覚えてみなければならない人であることを心から願っています。
コメントをカットしたのか、コメントがなかったのかはわかりませんが、このコードは、私がs/w QAマネージャーであった場合のコードレビューには合格しません(私は何度か行っています)。
これが良い経験則です-コードで許可される唯一の「裸の整数」は0 1nd 1です。All他の数値は#defines、costs、enumsである必要がありますなど、言語によって異なります。
これらの3と0x33333333がNUM_WIDGET_SHIFT_BITSやWIDGET_READ_MASKのようなものを言っている場合、コードは読みやすくなります。
オープンソースプロジェクトでこれを公開した人は恥ずかしいですが、個人的なコードでも十分にコメントし、意味のある定義/列挙を使用し、独自のコーディング標準を持っています。
この特定のコードは、本から直接抜粋したものです Hacker's Delight 、図5.2。 Cでのオンライン(pop関数) here 。著者は更新されたバージョンの使用を推奨することに注意してください: http://www.hackersdelight.org/HDcode/newCode/pop_arrayHS.c.txt
この種のマイクロ最適化を学びたいのであれば、その本をお勧めします。楽しいですが、非常に低レベルのビットプログラミングを行わない限り、おそらくそれを理解できません。ほとんどの場合、コンパイラはこれらの種類の最適化の多くを実行できます。
また、すべての16進数をバイナリで書き換えて、これらの種類のアルゴリズムを理解し、1つまたは2つのテストケースで処理することもできます。
例による説明。データはビットのシーケンスです。次の操作が可能なバイト01001101のビットをカウントしてみましょう。1.最後のビットの値を確認できます。 2.シーケンスをシフトできます。
私たちの答え:4。
難しいことではありませんでしたか?ビット単位の演算で重要なのは、実行できることは限られていることです。直接アクセスすることはできません。しかし、たとえば、最後のビットの値をMASK 00000001と比較して知ることができ、シフト演算ですべてのビットを最後のビットにすることができます。もちろん、結果として得られるアルゴリズムは、慣れていないものには恐ろしく見えます。知性とは何の関係もありません。
あなたがしている仕事が関連しているのでなければ、私はあなたがそれを必要とするとは言いません:
システムに特に複雑なアクセス許可モデルがある場合、または読みやすさを犠牲にしてすべてを1バイトに詰め込みたい場合は、UNIXスタイルのフラグにアクセス許可を格納することも別の用途です。
これらの領域は別として、開発者/上級開発者がビットシフトを実証でき、| &と^は、職業への関心を示しているため、より安定した信頼性の高いコードにつながると言えます。
メソッドを一目で「理解」しない限り、前述のように、メソッドの動作といくつかの背景の説明が必要です。それは知性に関連しているとは言えませんが、日常的に16進数を扱うこと、および特定のパターンで解決できる問題を認識することに慣れています。