用于减少内存的 Java 整数标志和按位运算
Posted
技术标签:
【中文标题】用于减少内存的 Java 整数标志和按位运算【英文标题】:Java integer flag and bitwise operations for memory reduction 【发布时间】:2011-11-16 22:51:57 【问题描述】:使用整数标志和按位运算是减少大容量对象内存占用的有效方法吗?
内存占用
据我了解,boolean
通常在 JVM 实现中存储为 int
。它是否正确?在这种情况下,32 个标志肯定代表内存占用量大大减少。
虽然 JVM 实现当然会有所不同,但情况可能并非总是如此。
性能
据我了解,CPU 是非常受数字驱动的,按位运算的效率与计算中的事物差不多。
使用按位运算而不是布尔运算是否会降低性能 - 甚至是增益?
替代品
有没有更好的方法来完成同样的事情?枚举是否允许组合标志,即FLAGX = FLAG1 | FLAG2
?
示例代码
注意最后一个方法propogateMove()
是递归的,每秒可能被调用数百次,直接影响我们应用程序的响应能力,因此使用标志来避免逻辑位和调用其他方法。
// FLAGS helper functions
private final void setclear(int mask, boolean set) if (set) set(mask); else clear(mask);
private final void set(int mask) flags |= mask;
private final void clear(int mask) flags &= ~mask;
private final boolean test(int mask) return ((flags & mask) == mask);
// Flags //////////////////////////////////////////////////////////////////////
private static final boolean HORIZONTAL = true;
private static final boolean VERTICAL = false;
private static final int ORIENT = 0x00000001;
private static final int DISPLAY = 0x00000002;
private static final int HSHRINK = 0x00000004;
private static final int VSHRINK = 0x00000008;
private static final int SHRINK = HSHRINK | VSHRINK;
private static final int TILE_IMAGE = 0x00000010;
private static final int CURSOR = 0x00000020;
private static final int MOUSEINSIDE = 0x00000040;
private static final int MOUSEINSIDE_BLOCKED = 0x00000080;
private static final int CONSTRAIN = 0x00000100;
private static final int CONSTRAIN_DESCENDENT = 0x00000200;
private static final int PLACE = 0x00000400;
private static final int PLACE_DESCENDENT = 0x00000800;
private static final int REFLOW = CONSTRAIN | CONSTRAIN_DESCENDENT | PLACE | PLACE_DESCENDENT;
private static final int PACK = 0x00001000;
private static final int CLIP = 0x00002000;
private static final int HAS_WIDTH_SLACK = 0x00004000;
private static final int HAS_HEIGHT_SLACK = 0x00008000;
private static final int ALIGN_TOP = 0x00010000;
private static final int ALIGN_BOTTOM = 0x00020000;
private static final int ALIGN_LEFT = 0x00040000;
private static final int ALIGN_RIGHT = 0x00080000;
private static final int ALIGNS = ALIGN_TOP | ALIGN_BOTTOM | ALIGN_LEFT | ALIGN_RIGHT;
private static final int ALIGN_TOPLEFT = ALIGN_TOP | ALIGN_LEFT;
private static final int ALIGN_TOPRIGHT = ALIGN_TOP | ALIGN_RIGHT;
private static final int ALIGN_BOTTOMLEFT = ALIGN_BOTTOM | ALIGN_LEFT;
private static final int ALIGN_BOTTOMRIGHT = ALIGN_BOTTOM | ALIGN_RIGHT;
private static final int ENTER_TRAP = 0x00100000;
private static final int LEAVE_TRAP = 0x00200000;
private static final int _MOVE_TRAP = 0x00400000;
private static final int MOVE_TRAP = 0x00800000;
private static final int CHILDREN_READ_TRAP = 0x01000000;
private static final int CHILDREN_TRAP = 0x02000000;
private static final int PLACE_CLEAN = 0x03000000;
private static final int SHRINK_TRAP = 0x04000000;
private static final int HSHRINK_TRAP = 0x10000000;
private static final int VSHRINK_TRAP = 0x20000000;
//private static final int UNUSED = 0x40000000;
//private static final int UNUSED = 0x80000000;
// Flags in switch ////////////////////////////////////////////////////////////
/** get align value as a string from align flags */
private JS alignToJS()
switch(flags & ALIGNS)
case (ALIGN_TOPLEFT):
return SC_align_topleft;
case (ALIGN_BOTTOMLEFT):
return SC_align_bottomleft;
case (ALIGN_TOPRIGHT):
return SC_align_topright;
case (ALIGN_BOTTOMRIGHT):
return SC_align_bottomright;
case ALIGN_TOP:
return SC_align_top;
case ALIGN_BOTTOM:
return SC_align_bottom;
case ALIGN_LEFT:
return SC_align_left;
case ALIGN_RIGHT:
return SC_align_right;
case 0: // CENTER
return SC_align_center;
default:
throw new Error("This should never happen; invalid alignment flags: " + (flags & ALIGNS));
// Flags in logic /////////////////////////////////////////////////////////////
private final boolean propagateMove(int mousex, int mousey) throws JSExn
// start with pre-event _Move which preceeds Enter/Leave
if (test(_MOVE_TRAP))
if (Interpreter.CASCADE_PREVENTED == justTriggerTraps(SC__Move, JSU.T))
// _Move cascade prevention induces Leave
propagateLeave();
// propagate cascade prevention
return true;
// REMARK: anything from here on in is a partial interruption relative
// to this box so we can not call propagateLeave() directly upon it
int i;
boolean interrupted = false;
if (!test(PACK))
// absolute layout - allows for interruption by overlaying siblings
for (Box b = getChild(i=treeSize()-1); b != null; b = getChild(--i))
if (!b.test(DISPLAY))
continue;
if (interrupted)
b.propagateLeave();
continue;
int b_mx = mousex-getXInParent(b);
int b_my = mousey-getYInParent(b);
if (b.inside(b_mx, b_my))
if (b.propagateMove(b_mx, b_my))
interrupted = true;
else
b.propagateLeave();
else
// packed layout - interrupted still applies, plus packedhit shortcut
boolean packedhit = false;
for (Box b = getChild(i=treeSize()-1); b != null; b = getChild(--i))
if (!b.test(DISPLAY))
continue;
if (packedhit)
b.propagateLeave();
continue;
int b_mx = mousex-getXInParent(b);
int b_my = mousey-getYInParent(b);
if (b.inside(b_mx, b_my))
packedhit = true;
if (b.propagateMove(b_mx, b_my))
interrupted = true;
else
b.propagateLeave();
// child prevented cascade during _Move/Move which blocks
// Enter on this box - invoking Leave if necessary
if (interrupted)
if (test(MOUSEINSIDE))
if (!test(MOUSEINSIDE_BLOCKED))
// mouse previously inside, now blocked so invoke Leave
set(MOUSEINSIDE_BLOCKED);
if (test(LEAVE_TRAP))
justTriggerTraps(SC_Leave, JSU.T);
else
// mouse not previously inside, Enter not yet triggered, so
// do not invoke Leave
set(MOUSEINSIDE);
set(MOUSEINSIDE_BLOCKED);
// propagate cascade prevention
return true;
// set cursor if applicable to this box
if (test(CURSOR))
Surface s = getSurface();
if (s!=null && !s.cursorset)
s.cursor = JSU.toString(getAndTriggerTraps(SC_cursor));
s.cursorset = true;
// fire Enter traps
if (!test(MOUSEINSIDE))
set(MOUSEINSIDE);
if (test(ENTER_TRAP))
justTriggerTraps(SC_Enter, JSU.T);
// finish post-event Move which follows Enter/Leave
if (test(MOVE_TRAP))
if (Interpreter.CASCADE_PREVENTED == justTriggerTraps(SC_Move, JSU.T))
// propagate cascade prevention
return true;
// propagation uninterrupted
return false;
【问题讨论】:
我会说答案是高度依赖上下文的。通常我喜欢枚举和构造,例如 EnumSet。但是,对于有线协议实现或示例,例如上面与显示代码按位操作相关的示例,提供了明显的优势。 【参考方案1】:由于您不能相信布尔值存储为位或整数(我认为后者是实际实现),是的,我认为位掩码和标志确实可以提高性能。
我在一些项目中使用过它们。它们的可读性较差,但我从不介意,因为我认为每个程序员都应该了解二进制表示法和算术。
只是一个建议:对于 java 7,我们可以像这样定义数字文字:
private static final int ENTER_TRAP = 0b00000000000100000000000000000000;
甚至像这样:
private static final int ENTER_TRAP = 0b0000_0000_0001_0000_0000_0000_0000_0000;
【讨论】:
【参考方案2】:是的,布尔值存储为 32 位整数。方法签名区分布尔值和整数,但除此之外它们的处理方式相同。这是 JVM 字节码规范的一部分。
Java 上的按位运算直接映射到 CPU 上的一条指令,该指令执行相同的按位运算,因此确实非常快。当然,将每个值保存在自己的 32 位字中会更快(然后您根本不需要进行任何按位运算)。使用您的方法,您将节省内存并花费更多 CPU 周期。
Java 没有用于组合枚举的运算符,而且我看不出这有什么意义......
【讨论】:
re:“Java 没有用于组合枚举的运算符”看看 EnumSet EnumSet 不会将两个枚举组合成一个新的枚举。【参考方案3】:使用整数标志和按位运算是减少大容量对象内存占用的有效方法吗?
如果一个对象有大量的这些标志并且这些对象的数量很大(或者减少堆使用是一个关键问题),那么这可能是一个值得进行的微优化。否则不 (IMO)。
这样做的问题是它使您的代码更难阅读且更脆弱。所以你真的应该只在内存使用是一个关键问题时才考虑它。
【讨论】:
【参考方案4】:据我了解,布尔值通常在 JVM 实现中存储为 int。它是否正确?
这当然取决于 JVM 的实现,但对于主流 CPU 上的实现来说可能是正确的。
在这种情况下,32 个标志肯定代表内存占用量大大减少。
如果您实际上在一个类中有 32 个标志,并且该类有大量实例,是的。如果您的实例从不超过几百个,则无需担心。
据我了解,CPU 是非常受数字驱动的,按位运算的效率与计算中的事物差不多。
这是真的。
使用按位运算而不是布尔运算是否会降低性能 - 甚至是增益?
这也取决于内存使用情况。如果您只使用几个对象非常密集地工作,则按位运算可能会减慢速度。如果您有很多对象,由于更好的缓存行为,减少的内存可能会大大提高性能。
有没有更好的方法来完成同样的事情?枚举是否允许组合标志,即 FLAGX = FLAG1 | FLAG2?
您可以(并且应该)使用BitSet
,而不是自己进行按位运算。是的,如果你可以使用 Enums 和 EnumSet
,它会更干净,但是如果你有许多枚举,每个枚举都有几个元素,它可能不会产生所需的内存节省,因为多个开销EnumSet
个实例。
【讨论】:
JVM字节码不区分整数和布尔值,所以我怀疑它取决于CPU。方法签名和字段包括允许正确类型检查的区别。 @Mathias:但问题基本上是关于布尔字段的。 VM 不是将多个布尔字段组合成一个更大的值(例如 int)吗?我想我听说了一些关于那件事的事情。 @soc:据我所知没有。但我当然很想听听任何具体的反例。以上是关于用于减少内存的 Java 整数标志和按位运算的主要内容,如果未能解决你的问题,请参考以下文章