全面的程序员需要按位操作有多好?[关闭]


34

最近,我一直在浏览一些OpenJDK代码,并在其中找到了一些与按位操作有关的有趣代码。我什至在StackOverflow上问了一个问题

另一个例子说明了这一点:

 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类中找到此代码。

当我看着这个时,我不禁感到愚蠢。我是否错过了一两个大学的课程,或者这不是我应该得到的?我可以执行简单的按位运算(例如ANDing,ORing,XORing,shifting),但是,有人会想出上面的代码吗?

全面的程序员需要按位操作有多好?

附带一提...让 我担心的是,在StackOverflow上回答我的问题的人在几分钟之内回答了该问题。如果他能做到这一点,为什么我只是盯着大灯中的鹿?


4
您从事(或想要从事的,如果您现在不从事)哪种类型的开发工作?我认为这在Web开发中没有用,但是我看到嵌入式系统中有很多按位操作。
Thomas Owens

26
如果我要雇用某个人来进行用户界面开发或Web开发,那么我就不会要求进行位操作,因为很可能他们永远不会看到它。但是,我希望使用网络协议,嵌入式系统和设备驱动程序的人对此有所熟悉。
Thomas Owens

11
>>>作为操作员到底是什么?
DeadMG,2011年


3
// HD, Figure 5-2将是我首先要看的东西。根据文件开头的注释,HDHenry S. Warren, Jr.'s Hacker's Delight
schnaader 2011年

Answers:


38

我想说,作为一个全面的开发人员,您需要了解运算符和按位运算。

因此,至少经过一点思考,您应该就能弄清楚上面的代码。

按位操作通常级别较低,因此,如果您使用网站和LOB软件,则不太可能使用它们。

像其他东西一样,如果您不经常使用它们,您将不会精通它们。

因此,您不必担心有人能够很快弄清楚它,因为他们(可能)经常使用这种代码。可能编写OS代码,驱动程序代码或其他棘手的位操作。


1
+1:按位操作是任何开发人员的重要知识(无双关语),但它们只是真正的知识 在特定情况下真正重要。如果您在日常生活中从未遇到过它们,那么拥有一般知识总比奴役他们更好。保持大脑空间自由。
尼古拉斯·史密斯,

您还应该了解何时使用它们,并且如果它们是解决当前问题的正确解决方案,请不要回避使用它们。
2011年

要添加到@ user606723的注释中-确实只有少数几个地方通常使用按位填充,或多或少地遇到这种情况-散列(以及与之相关的填充)并提取/设置RGB的特定颜色(如果它们存储在一个int。例如,可以通过检查从特定寄存器返回的位标志来读取CPU信息,但这涉及asm,并且如果需要,通常具有更高的lvl包装器。
TC1

36

如果您了解如何解决“确定是否设置了第3位和第8位”,“清除第5位”或“找到由第7-12位表示的整数值”之类的问题,则您对按位运算符有足够的了解以检查Can “全面”清单上的“ 旋转位”框。

您的示例中的内容来自Hacker's Delight,这是一种高性能算法的汇编,用于处理整数之类的小数据。最初编写该代码的人并不仅仅是在五分钟之内就将其吐出来。其背后的故事更有可能是需要一种快速,无分支的计数位数的方法,而作者有一些时间花在盯着比特串上,并想出一种解决问题的方法。除非他们以前从未看过它,否则没人会一目了然地理解它的工作原理。对位基础知识有扎实的理解,并花了一些时间在代码上进行实验,您可能会发现它是如何工作的。

即使您不了解这些算法,也只是知道它们的存在会增加您的“四舍五入”,因为当需要处理高性能位计数时,您就知道该学习什么。在Google之前的世界里,要找到这些东西要困难得多。现在它已经击键了。

回答了您的SO问题的用户可能之前已经看过问题或已经研究过哈希。写给他问。


至少知道这些事情时+1。了解很多是一件好事。如果行业中的人们开始谈论这样的事情,那么您就不想成为会议室里没有丝毫线索的人。
maple_shaft

3
+1,用于解决上面的代码注释中的缩写“ HD”。
彼得Török

我喜欢这种东西,只是订购了高清书。感谢您的参考。
tcrosley

8

从您的示例中,有一些事情您应该完全不经思考就应该知道。

1143我=我-((i >>> 1)&0x55555555);

您应该将位模式0x555 ...识别为交替位模式0101 0101 0101,并且运算符将其偏移1位(向右),并且&是屏蔽操作(以及屏蔽的含义)。

1144我=(i&0x33333333)+((i >>> 2)&0x33333333);

还是一个模式,这个是0011 00110011。而且这次是移两个,然后再次屏蔽。移位和遮罩遵循您应该识别的模式...

1145我=(i +(i >>> 4))&0x0f0f0f0f;

图案固化。这次是00001111 00001111,当然,这次是将其移位4。每次我们移动蒙版的大小时。

1148返回i&0x3f;

另一个位模式3f是一个零块,后跟一个更大的1块。

如果您“圆润”,所有这些事情一目了然。即使您从未想到会使用它,也可能会错过一些机会,即使您不知道,也可以大大简化您的代码。

即使使用高级语言,位模式也可用于在较小的字段中存储大量数据。这就是为什么您总是在游戏中看到127 / 8、63 / 4和255/6的限制的原因,这是因为您必须存储如此多的内容,因此如果不打包字段,您将被迫使用多达10倍的字段。内存量。(嗯,最终的结果是,如果您需要在数组中存储大量的布尔值,那么您可以节省32-64倍的内存,这是您不考虑它时将使用的内存的大小-大多数语言都将布尔值实现为一个通常为32位的单词,那些对此水平感到不舒服的单词会因为害怕未知而拒绝存储这样的数据的机会。

他们还会避开诸如手动解析以打包格式通过网络传递的数据包之类的东西-如果您不害怕的话,这很简单。这可能会使需要1k数据包的游戏减少到需要200个字节,较小的数据包将更有效地在网络中滑动并降低延迟并实现更高的交互速度(这可能会启用游戏的全新玩法)。


5

我碰巧认出了该代码,因为我之前在操纵视频帧的软件中已经看过它。如果您经常使用音频和视频编解码器,网络协议或芯片寄存器之类的东西,您会发现很多按位操作,这将成为您的第二天性。

如果您的工作碰巧与这些领域经常不一致,您应该不会感到难过。我非常了解按位运算,但是在极少数情况下,我需要编写GUI来减慢速度,因为所有的怪癖都带有布局,权重和扩展,因此我敢肯定,这是其他人的第二天性。您的优势就是您拥有最多经验的地方。


4

您应该了解的主要事情是如何表示整数(通常是固定长度的位向量,长度取决于平台),并且对它们进行哪些操作

+ - * / %可以理解主要的算术运算,尽管它对于微优化是很方便的(尽管大多数时候编译器将能够为您解决)

位操作集| & ~ ^ << >> >>>至少需要经过一定的了解才能使用它们

但是,大多数情况下,您只会使用它们将位标志OR一起传递给一个方法,然后传递一个int值,然后AND读出设置,这比在长参数列表中传递几个(最多32个)布尔值更具可读性,并且允许无需更改接口即可更改的可能标志

更不用说布尔值通常以字节或整数形式保存,而不是像标志那样将它们打包在一起


至于代码片段,它对位进行并行计数,这允许算法在O(log(n))其中n是位数的情况下运行,而不是朴素的循环O(n)

第一步是最难理解的,但如果你从它替换位序列的设置开始0b000b000b010b010b100b01,并0b110b10它变得更容易执行

因此,对于第一步,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个额外的操作

以下步骤与此类似


4
该代码最重要的质量是它是无分支的,与降低复杂度相比,它可能带来更大的好处。
西蒙·里希特

3

每个人都应该了解基本的按位操作。这是基本操作的组成,以优化,可靠的方式执行任务需要大量的练习。

当然,那些每天进行位操作的人(例如嵌入式人员)将发展强烈的直觉和许多技巧。

不做底层工作的程序员应该具备多少技巧?足以像坐在您上面粘贴的节上坐下来,并像在脑筋急转弯或拼图一样缓慢地处理它。

出于同样的原因,我想说嵌入式程序员应该对HTTP的理解与Web开发人员对按位操作的理解一样多。换句话说,如果您不一直使用它,那么在位操作上不要精打细算是“ OK”的。


3
实际上,在某些情况下,嵌入式程序员必须比Web开发人员更多地了解http(两者都做)。在进行Web开发时,通常可以依靠某种类型的框架。作为与Internet连接的设备一起工作的嵌入式开发人员,我不得不从头开始编写http堆栈。
tcrosley

@tcrosely,是的,你是绝对正确的。也许比“ http”更好的例子是“ ORM”或“ JEE”。要点是,除非他们定期练习,否则通常无法精通某些主题。
Angelo

我同意,而且我从来不必处理ORM或JEE(在JME被称为J2ME时也只是JME)。
tcrosley


3

按位运算符的解释难度有多大?

我对嵌入式系统进行编程。我已经练习了很多东西。您与代码有关的哈希映射的链接问题

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是立即清除的,但是要花一点时间才能弄清楚为什么它实际上对位进行计数。不过,注释会很棒,而且会使理解代码比哈希问题稍微难一些。

区分阅读和理解代码很重要。我可以解释bitCount代码,并阅读代码的作用,但是要证明它为什么起作用甚至是起作用都需要一分钟。能够流畅地阅读代码和理解代码为何如此存在着区别。有些算法很难。在什么样hash代码是有道理的,但注释解释为什么在被正在做什么。如果使用位运算符的函数难以理解,不要气,,它们经常被用来做棘手的数学工作,而无论采用哪种格式,都很难做到。

打个比方

我已经习惯了这种东西。我不习惯的一个主题是正则表达式。 我偶尔会在构建脚本中处理它们,但在日常开发工作中则不会。

我知道如何使用正则表达式的以下元素:

  • [] 人物类
  • *.+通配符
  • 字符串的开头^和结尾$
  • \ d,\ w和\ s字符类
  • / g标志

这足以制作简单的查询,而我看到的许多查询离此也不远。

除了这个清单上的任何东西,我都准备了一份备忘单。除了{}()- 以外,其他任何内容都不够。我对这些人了解得足够多,所以我将需要白板,参考手册,甚至可能需要一位同事。您可以将一些疯狂的算法打包到几行正则表达式中。

为了设计一个正则表达式,它需要或建议不在我的已知元素列表中的任何东西,我将列出我希望识别的所有输入类别,并将它们放入测试套件中。我将通过许多间歇性步骤来逐步地逐步创建正则表达式,并将这些步骤提交给源代码控制和/或在注释中注明,以便我可以理解在中断时应该发生的情况。如果它在生产代码中,那么我将确保它被有更多经验的人审查。

这是按位运算符所在的位置吗?

因此,您想全面发展吗?

据我估计,如果您能够抽出一张纸或进入白板并手动执行这些操作来解释这样的代码,那么您就算是全面的。要在按位运算领域成为合格的全面程序员,您应该能够做四件事:

  1. 能够流畅地读写常用操作
    对于应用程序程序员,按位运算符的常用操作包括|&设置和清除标志的基本操作符。这应该很容易。您应该能够读写类似

    open('file', O_WRONLY | O_APPEND | O_CREAT );
    // Use an OR operator ^ here and ^ here to set multiple flags
    

    而不放慢速度(假设您知道这些标志的含义)。

  2. 能够通过一些工作读取更复杂的操作
    在O(log(n))时间内真正快速计数位而无分支,确保hashCodes中的冲突数可以相差一定数量,并且可以解析电子邮件地址电话号码或带有正则表达式的HTML很难解决。对于那些不是这些领域的专家的人来说,获得白板是合理的,无法开始努力理解是不合理的。

  3. 能够编写大量工作而复杂的算法
    如果您不是专家,则不要指望能够做复杂而困难的事情。但是,一个好的程序员应该能够通过不断地工作来完成它。做到这一点,您很快就会成为专家:)


2

如果您去了一所体面的大学,那么您应该被要求参加离散数学课程。您将了解二进制,八进制和十六进制的算术和逻辑门。

关于这一点,对此感到困惑是很正常的,因为这对您来说是一个安慰,因为我主要编写Web应用程序,所以我很少需要查看或编写类似这样的代码,但是由于我了解二进制算术和按位运算符的行为如果有足够的时间,我最终可以弄清楚这里发生了什么。


2

作为手机程序员,我不得不处理这类事情。在设备内存不足或传输速度很重要的情况下,这是相当普遍的。在这两种情况下,您都希望将尽可能多的信息打包到几个字节中。

我不记得在5年左右的PHP(也许只是我)中使用了按位运算符,而不是在Windows编程的10年左右中使用了按位运算符,尽管一些较低级别的Windows确实可以打包位。

您说:“当我看着这个时,我不禁感到愚蠢”。不要-生气。

您刚刚遇到了牛仔程序员的输出。

他不知道编写可维护的代码吗?我衷心希望他是一年后必须回到这一问题上来并记住它的含义的人。

我不知道您是否删减注释,或者是否没有注释,但是此代码不会通过我曾是软件质量检查经理的代码审查(而我来过几次)。

这是一个很好的经验法则-代码中允许的唯一“裸整数”为0 1nd1。所有其他数字应为#define,cost,enum等,具体取决于您的语言。

如果那3和0x33333333说了类似NUM_WIDGET_SHIFT_BITS和WIDGET_READ_MASK之类的代码,该代码将更易于阅读。

无论在开放源代码项目中将其发布给谁,都应该感到羞耻,但即使是对个人代码,也可以很好地注释并使用有意义的定义/枚举,并拥有自己的编码标准。


我认为十六进制常量也是允许的。 0xFF00(对我而言)比更具可读性0b1111111100000000。我不想计数以确定已设置的位数。
凯文·维米尔

1

这段特殊的代码直接摘自《黑客的喜悦》一书(图5.2)。它在C(弹出功能)在线这里。请注意,作者现在建议使用更新的版本:http : //www.hackersdelight.org/HDcode/newCode/pop_arrayHS.c.txt

如果您想学习这些微观优化,建议您选择这本书。它很有趣,但是除非您执行的是非常低级的位编程,否则您通常可能不会理解它。而且大多数时候,您的编译器将能够为您执行许多此类优化。

它还有助于用二进制重写所有十六进制数字,以了解这些算法,并在一个或两个测试用例中进行研究。


1

举例说明。数据是位序列。让我们对字节01001101上具有以下可用操作的位进行计数:1.我们可以检查最后一位的值。2.我们可以改变顺序。

  1. 01001101->最后一个字节为1,总计= 1。转变
  2. 10100110->最后一个字节为0,总计= 1 转变
  3. 01010011->最后一个字节为1,总计= 2。转变
  4. 10101001->最后一个字节为1,总计= 3。转变
  5. 11010100->最后一个字节为0,总计= 3。转变
  6. 01101010->最后一个字节为0,总计= 3。转变
  7. 00110101->最后一个字节为1,总计= 4。转变
  8. 10011010->最后一个字节为0,总计= 4。转变

我们的答案:4。

这并不难,不是吗?按位运算的大问题是我们只能做有限的事情。我们无法直接访问。但是,例如,我们可以知道最后一位的值并将其与MASK 00000001进行比较,并且可以通过移位操作使每一位成为最后一位。当然,对于那些不习惯的算法来说,生成的算法看起来会很恐怖。与智力无关。


0

除非您所做的工作与以下方面有关,否则我不会说您需要它:

  • 音频处理
  • 视频处理
  • 图形
  • 联网(尤其是数据包大小很重要的地方)
  • 大量数据

如果您的系统具有特别复杂的权限模型,或者真的想以牺牲可读性为代价将所有内容都填充到一个字节中,则将权限存储在Unix样式标记中也是另一种用途。

除了这些领域,如果开发人员/高级开发人员可以证明位移,并使用| &和^,因为它显示了对该行业的兴趣,您可以说这导致了更稳定和可靠的代码。

至于没有一眼就“了解”该方法的问题,如上所述,您需要对它的作用和背景知识进行解释。我不会说这与智能有关,但是您对日常使用十六进制以及识别某些模式可以解决的问题有多熟悉。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.