Целочисленный флаг Java и побитовые операции для сокращения памяти - PullRequest
9 голосов
/ 14 сентября 2011

Является ли использование целочисленного флага и побитовых операций эффективным способом уменьшения объема памяти объектов большого объема?

  • Объем памяти

    Насколько я понимаю, обычно boolean хранится как int в реализации JVM. Это правильно? В этом случае, безусловно, 32 флага представляют собой значительное сокращение объема памяти.

    Хотя, конечно, реализации JVM различаются, поэтому это не всегда так.

  • Производительность

    Насколько я понимаю, центральные процессоры очень сильно зависят от числа, а побитовые операции примерно так же эффективны, как и вычислительные процессы.

    Есть ли снижение производительности или даже выигрыш в использовании побитовых операций над логическими операциями?

  • Альтернативы

    Есть ли лучший способ сделать то же самое? Разрешает ли Enum комбинацию флагов, т.е. 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;
}

Ответы [ 4 ]

13 голосов
/ 14 сентября 2011

Насколько я понимаю, логическое значение обычно хранится в виде int в реализации JVM. Это правильно?

Конечно, это зависит от реализации JVM, но, вероятно, верно для реализаций на основных процессорах.

В этом случае, безусловно, 32 флага представляют собой значительное сокращение занимаемой памяти.

Если у вас есть 32 флага в классе и большое количество экземпляров этого класса, да. Если у вас никогда не бывает более нескольких сотен экземпляров, беспокоиться не о чем.

Насколько я понимаю, процессоры очень сильно зависят от числа, а побитовые операции примерно так же эффективны, как в вычислительных процессах.

Это правда.

Есть ли снижение производительности или даже выигрыш в использовании побитовых операций над логическими операциями?

Это зависит также от использования памяти. Если вы работаете очень интенсивно только с несколькими объектами, побитовые операции могут замедлить работу. Если у вас много объектов, уменьшенная память, вероятно, значительно улучшит производительность из-за лучшего поведения кэширования.

Есть ли лучший способ сделать то же самое? Разрешает ли Enum комбинацию флагов, т. Е. FLAGX = FLAG1 | Flag2

Вместо того, чтобы выполнять побитовые операции самостоятельно, вы можете (и должны) использовать BitSet. И да, было бы еще чище, если бы вы могли работать с Enums и EnumSet, но если бы у вас было несколько перечислений с несколькими элементами в каждом, это, вероятно, не дало бы желаемой экономии памяти, из-за накладных расходов для нескольких EnumSet экземпляров.

3 голосов
/ 14 сентября 2011

Да, логическое значение сохраняется как 32-битное целое число. Сигнатуры метода различают логические значения и целые, но в остальном они обрабатываются одинаково. Это часть спецификации байтового кода JVM.

Битовые операции на Java отображаются непосредственно в одну инструкцию на ЦПУ, которая выполняет ту же битовую операцию, так что это действительно довольно быстро. Конечно, быстрее хранить каждое значение в его собственном 32-битном слове (тогда вам вообще не нужно выполнять побитовые операции). Используя ваш подход, вы сэкономите память и будете тратить больше циклов ЦП.

В Java нет оператора для объединения enum, и я не понимаю, как это все равно имеет смысл ...

2 голосов
/ 14 сентября 2011

Является ли использование целочисленного флага и побитовых операций эффективным способом уменьшения объема памяти объектов большого объема?

Если объект имеет большое количество этих флагов И существует огромное количество этих объектов (или сокращение использования кучи является критической проблемой), то это может быть полезной микрооптимизацией. В противном случае нет (ИМО).

Проблема в том, что это делает ваш код труднее для чтения и более хрупким. Так что вам стоит задуматься об этом, только если использование памяти является критической проблемой.

1 голос
/ 14 сентября 2011

Поскольку нельзя доверять, что логические значения хранятся в виде битов или целых чисел (я думаю, что последняя является реальной реализацией), да, я думаю, битовые маски и флаги действительно улучшают производительность.

Я использовал их в некоторых проектах. Они менее читабельны, но я никогда не думал об этом, поскольку, по моему мнению, каждый программист должен понимать двоичную запись и арифметику.

Просто рекомендация: что касается Java 7, мы можем определить числовые литералы следующим образом:

   private static final int ENTER_TRAP = 0b00000000000100000000000000000000;

И даже так:

   private static final int ENTER_TRAP = 0b0000_0000_0001_0000_0000_0000_0000_0000;
...