Determine the optimal size for array with respect to the JVM's memory granularity

前端 未结 2 560
别跟我提以往
别跟我提以往 2021-01-05 13:12

When creating the backing array for (e.g.) a collection, you do not really care about the exact size of the array you create, it only needs to be at least as large as you ca

相关标签:
2条回答
  • 2021-01-05 13:51

    When dynamically sized collections increase the size of their backing array, they do not add a small amount to its size, they increase in proportion. Doubling is a common choice. They do this because it gives better performance. The tiny adjustment you suggest would not be worth the effort.

    0 讨论(0)
  • 2021-01-05 13:58

    Interesting idea. I think that the more portable method of determining this would be to actually measure usage. Example program:

    public class FindMemoryUsage {
        public static void main(String[] args) {
            for (int i=0; i<50; i+=2) {
                long actual = getActualUsageForN(i);
                System.out.println(i + " = " + actual);
                long theoretical = getTheoreticalUsageForN(i);
                if (theoretical != actual) {
                    throw new RuntimeException("Uh oh! Mismatch!");
                }
            }
        }
    
        private static long getTheoreticalUsageForN(long count) {
            long optimal = (Unsafe.ARRAY_BYTE_BASE_OFFSET + Unsafe.ARRAY_BYTE_INDEX_SCALE * count);
            return ((optimal - 1) & ~7) + 8;
        }
    
        private static long getActualUsageForN(int count) {
            System.gc();
            byte[][] arrays = new byte[3000000][];
            long begin = usedMemory();
            for (int i=0; i<arrays.length; i++) {
                arrays[i] = new byte[count];
            }
            long end = usedMemory();
            return Math.round((end - begin) / (double) arrays.length);
        }
    
        private static long usedMemory() {
            return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        }
    }
    

    This program gives you this info:

    0 = 16
    2 = 16
    4 = 16
    6 = 24
    8 = 24
    10 = 24
    12 = 24
    14 = 32
    16 = 32
    18 = 32
    20 = 32
    22 = 40
    24 = 40
    26 = 40
    28 = 40
    30 = 48
    32 = 48
    34 = 48
    36 = 48
    38 = 56
    40 = 56
    42 = 56
    44 = 56
    46 = 64
    48 = 64
    

    This data is from both the actual calculation of usage and the theoretical usage based on sun.misc.Unsafe's constants and 8-byte-rounding. This means that you could use these constants to "round up" like you suggested:

    private static int roundSizeUp(int from) {
        long size = (Unsafe.ARRAY_BYTE_BASE_OFFSET + Unsafe.ARRAY_BYTE_INDEX_SCALE * from);
        long actual = ((size - 1) & ~7) + 8;
        return (int) (actual - Unsafe.ARRAY_BYTE_BASE_OFFSET) / Unsafe.ARRAY_BYTE_INDEX_SCALE;
    }
    

    This is VM-specific code, but you could probably find how to do this based on the getActualUsageForN strategy if you need more portability.

    Note that this isn't production-quality code: you'd want to think carefully about overflows and change the Unsafe references to be the constants that actually apply to the type of array that you're working with.

    0 讨论(0)
提交回复
热议问题