Статьи

Использование файла с отображенной памятью для огромной матрицы

обзор

Матрицы могут быть очень большими, иногда больше, чем вы можете держать в одном массиве. Вы можете увеличить максимальный размер, имея несколько массивов, однако это может сделать ваш размер кучи действительно большим и неэффективным. Альтернативой является использование оболочки над отображенным в память файлом. Преимущество файлов, отображаемых в память, состоит в том, что они оказывают очень незначительное влияние на кучу и могут достаточно прозрачно загружаться и выгружаться ОС.

Огромная матрица

Этот код поддерживает большие матрицы double. Он разбивает файл на 1 ГБ сопоставления. (Поскольку Java не поддерживает сопоставления размером 2 ГБ и более одновременно, моя любимая ненависть;)

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import sun.misc.Cleaner;
import sun.nio.ch.DirectBuffer;
 
import java.io.Closeable;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
 
public class LargeDoubleMatrix implements Closeable {
    private static final int MAPPING_SIZE = 1 << 30;
    private final RandomAccessFile raf;
    private final int width;
    private final int height;
    private final List mappings = new ArrayList();
 
    public LargeDoubleMatrix(String filename, int width, int height) throws IOException {
        this.raf = new RandomAccessFile(filename, "rw");
        try {
            this.width = width;
            this.height = height;
            long size = 8L * width * height;
            for (long offset = 0; offset < size; offset += MAPPING_SIZE) {
                long size2 = Math.min(size - offset, MAPPING_SIZE);
                mappings.add(raf.getChannel().map(FileChannel.MapMode.READ_WRITE, offset, size2));
            }
        } catch (IOException e) {
            raf.close();
            throw e;
        }
    }
 
    protected long position(int x, int y) {
        return (long) y * width + x;
    }
 
    public int width() {
        return width;
    }
 
    public int height() {
        return height;
    }
 
    public double get(int x, int y) {
        assert x >= 0 && x < width;
        assert y >= 0 && y < height;
        long p = position(x, y) * 8;
        int mapN = (int) (p / MAPPING_SIZE);
        int offN = (int) (p % MAPPING_SIZE);
        return mappings.get(mapN).getDouble(offN);
    }
 
    public void set(int x, int y, double d) {
        assert x >= 0 && x < width;
        assert y >= 0 && y < height;
        long p = position(x, y) * 8;
        int mapN = (int) (p / MAPPING_SIZE);
        int offN = (int) (p % MAPPING_SIZE);
        mappings.get(mapN).putDouble(offN, d);
    }
 
    public void close() throws IOException {
        for (MappedByteBuffer mapping : mappings)
            clean(mapping);
        raf.close();
    }
 
    private void clean(MappedByteBuffer mapping) {
        if (mapping == null) return;
        Cleaner cleaner = ((DirectBuffer) mapping).cleaner();
        if (cleaner != null) cleaner.clean();
    }
}
 
public class LargeDoubleMatrixTest {
    @Test
    public void getSetMatrix() throws IOException {
        long start = System.nanoTime();
        final long used0 = usedMemory();
        LargeDoubleMatrix matrix = new LargeDoubleMatrix("ldm.test", 1000 * 1000, 1000 * 1000);
        for (int i = 0; i < matrix.width(); i++)
            matrix.set(i, i, i);
        for (int i = 0; i < matrix.width(); i++)
            assertEquals(i, matrix.get(i, i), 0.0);
        long time = System.nanoTime() - start;
        final long used = usedMemory() - used0;
        if (used == 0)
            System.err.println("You need to use -XX:-UseTLAB to see small changes in memory usage.");
        System.out.printf("Setting the diagonal took %,d ms, Heap used is %,d KB%n", time / 1000 / 1000, used / 1024);
        matrix.close();
    }
 
    private long usedMemory() {
        return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    }
}

С помощью следующего теста, который записывает в каждое из диагональных значений матрицу миллион * миллион. Это слишком много, чтобы надеяться создать в куче.

1
2
3
4
5
6
Setting the diagonal took 314,819 ms, Heap used is 2,025 KB
 
$ ls -l ldm.test
-rw-rw-r-- 1 peter peter 8000000000000 2011-12-30 12:42 ldm.test
$ du -s ldm.test
4010600 ldm.test

Это 8 000 000 000 000 байтов или ~ 7,3 ТБ в виртуальной памяти в процессе Java! Это работает, потому что он только выделяет или страниц на страницах, которые вы используете. Таким образом, хотя размер файла составляет почти 8 ТБ, фактическое дисковое пространство и используемая память составляют 4 ГБ.
С более скромным размером файла матрицы 100K * 100K вы видите что-то вроде следующего. Это все еще матрица на 80 ГБ, которая использует тривиальное пространство кучи. ?

1
2
3
4
5
6
Setting the diagonal took 110 ms, Heap used is 71 KB
 
$ ls -l ldm.test
-rw-rw-r-- 1 peter peter 80000000000 2011-12-30 12:49 ldm.test
$ du -s ldm.test
400000 ldm.test

Ссылка: Использование файла отображения памяти для огромной матрицы от нашего партнера по JCG Питера Лоури из блога Vanilla Java

Статьи по Теме :