kopia lustrzana https://github.com/onthegomap/planetiler
				
				
				
			Memory-mapped file improvements (#103)
* Use large memory-mapped file segments to avoid running out of space on smaller machines * Add `--nodemap-madvise` argument to opt into madvise(random) for memory-mapped file accesspull/138/head
							rodzic
							
								
									1c27d833b2
								
							
						
					
					
						commit
						0a064797fb
					
				|  | @ -22,6 +22,7 @@ The `planetiler-core` module includes the following software: | |||
|   - com.google.guava:guava (Apache license) | ||||
|   - org.openstreetmap.osmosis:osmosis-osm-binary (LGPL 3.0) | ||||
|   - com.carrotsearch:hppc (Apache license) | ||||
|   - com.github.jnr:jnr-ffi (Apache license) | ||||
| - Adapted code: | ||||
|   - `DouglasPeuckerSimplifier` from [JTS](https://github.com/locationtech/jts) (EDL) | ||||
|   - `OsmMultipolygon` from [imposm3](https://github.com/omniscale/imposm3) (Apache license) | ||||
|  | @ -31,6 +32,7 @@ The `planetiler-core` module includes the following software: | |||
|   - `Imposm3Parsers` from [imposm3](https://github.com/omniscale/imposm3) (Apache license) | ||||
|   - `PbfDecoder` from [osmosis](https://github.com/openstreetmap/osmosis) (Public Domain) | ||||
|   - `PbfFieldDecoder` from [osmosis](https://github.com/openstreetmap/osmosis) (Public Domain) | ||||
|   - `NativeUtil` from [uppend](https://github.com/upserve/uppend/) (MIT License) | ||||
| 
 | ||||
| Additionally, the `planetiler-basemap` module is based on [OpenMapTiles](https://github.com/openmaptiles/openmaptiles): | ||||
| 
 | ||||
|  |  | |||
|  | @ -217,6 +217,8 @@ Planetiler is made possible by these awesome open source projects: | |||
|   and [tag parsing utilities](planetiler-core/src/main/java/com/onthegomap/planetiler/util/Imposm3Parsers.java) | ||||
| - [HPPC](http://labs.carrotsearch.com/) for high-performance primitive Java collections | ||||
| - [Osmosis](https://wiki.openstreetmap.org/wiki/Osmosis) for Java utilities to parse OpenStreetMap data | ||||
| - [JNR-FFI](https://github.com/jnr/jnr-ffi) for utilities to access low-level system utilities to improve memory-mapped | ||||
|   file performance. | ||||
| 
 | ||||
| See [NOTICE.md](NOTICE.md) for a full list and license details. | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,7 +25,7 @@ public class LongLongMapBench { | |||
|     Format format = Format.defaultInstance(); | ||||
|     Path path = Path.of("./llmaptest"); | ||||
|     FileUtils.delete(path); | ||||
|     LongLongMap map = LongLongMap.from(args[0], args[1], path); | ||||
|     LongLongMap map = LongLongMap.from(args[0], args[1], path, args.length < 5 || Boolean.parseBoolean(args[4])); | ||||
|     long entries = Long.parseLong(args[2]); | ||||
|     int readers = Integer.parseInt(args[3]); | ||||
| 
 | ||||
|  | @ -36,6 +36,7 @@ public class LongLongMapBench { | |||
|     LocalCounter counter = new LocalCounter(); | ||||
|     ProgressLoggers loggers = ProgressLoggers.create() | ||||
|       .addRatePercentCounter("entries", entries, () -> counter.count, true) | ||||
|       .addFileSize(map) | ||||
|       .newLine() | ||||
|       .addProcessStats(); | ||||
|     AtomicReference<String> writeRate = new AtomicReference<>(); | ||||
|  |  | |||
|  | @ -32,6 +32,11 @@ | |||
|       <artifactId>osmosis-osm-binary</artifactId> | ||||
|       <version>0.48.3</version> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>com.github.jnr</groupId> | ||||
|       <artifactId>jnr-ffi</artifactId> | ||||
|       <version>2.2.11</version> | ||||
|     </dependency> | ||||
|     <dependency> | ||||
|       <groupId>org.locationtech.jts</groupId> | ||||
|       <artifactId>jts-core</artifactId> | ||||
|  |  | |||
|  | @ -170,7 +170,8 @@ public class Planetiler { | |||
|       ), | ||||
|       ifSourceUsed(name, () -> { | ||||
|         try ( | ||||
|           var nodeLocations = LongLongMap.from(config.nodeMapType(), config.nodeMapStorage(), nodeDbPath); | ||||
|           var nodeLocations = | ||||
|             LongLongMap.from(config.nodeMapType(), config.nodeMapStorage(), nodeDbPath, config.nodeMapMadvise()); | ||||
|           var osmReader = new OsmReader(name, thisInputFile, nodeLocations, profile(), stats) | ||||
|         ) { | ||||
|           osmReader.pass1(config); | ||||
|  |  | |||
|  | @ -1,12 +1,10 @@ | |||
| package com.onthegomap.planetiler.collection; | ||||
| 
 | ||||
| import com.onthegomap.planetiler.util.FileUtils; | ||||
| import com.onthegomap.planetiler.util.MmapUtil; | ||||
| import java.io.BufferedOutputStream; | ||||
| import java.io.DataOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.Method; | ||||
| import java.nio.ByteBuffer; | ||||
| import java.nio.MappedByteBuffer; | ||||
| import java.nio.channels.FileChannel; | ||||
| import java.nio.file.Files; | ||||
|  | @ -29,15 +27,21 @@ abstract class AppendStoreMmap implements AppendStore { | |||
|   final long segmentMask; | ||||
|   final long segmentBytes; | ||||
|   private final Path path; | ||||
|   private final boolean madvise; | ||||
|   long outIdx = 0; | ||||
|   private volatile MappedByteBuffer[] segments; | ||||
|   private volatile FileChannel channel; | ||||
| 
 | ||||
|   AppendStoreMmap(Path path) { | ||||
|     this(path, 1 << 20); // 1MB
 | ||||
|   static { | ||||
|     MmapUtil.init(); | ||||
|   } | ||||
| 
 | ||||
|   AppendStoreMmap(Path path, long segmentSizeBytes) { | ||||
|   AppendStoreMmap(Path path, boolean madvise) { | ||||
|     this(path, 1 << 30, madvise); // 1GB
 | ||||
|   } | ||||
| 
 | ||||
|   AppendStoreMmap(Path path, long segmentSizeBytes, boolean madvise) { | ||||
|     this.madvise = madvise; | ||||
|     segmentBits = (int) (Math.log(segmentSizeBytes) / Math.log(2)); | ||||
|     segmentMask = (1L << segmentBits) - 1; | ||||
|     segmentBytes = segmentSizeBytes; | ||||
|  | @ -58,6 +62,7 @@ abstract class AppendStoreMmap implements AppendStore { | |||
|       synchronized (this) { | ||||
|         if ((result = segments) == null) { | ||||
|           try { | ||||
|             boolean madviseFailed = false; | ||||
|             // prepare the memory mapped file: stop writing, start reading
 | ||||
|             outputStream.close(); | ||||
|             channel = FileChannel.open(path, StandardOpenOption.READ); | ||||
|  | @ -66,7 +71,19 @@ abstract class AppendStoreMmap implements AppendStore { | |||
|             int i = 0; | ||||
|             for (long segmentStart = 0; segmentStart < outIdx; segmentStart += segmentBytes) { | ||||
|               long segmentEnd = Math.min(segmentBytes, outIdx - segmentStart); | ||||
|               result[i++] = channel.map(FileChannel.MapMode.READ_ONLY, segmentStart, segmentEnd); | ||||
|               MappedByteBuffer thisBuffer = channel.map(FileChannel.MapMode.READ_ONLY, segmentStart, segmentEnd); | ||||
|               if (madvise) { | ||||
|                 try { | ||||
|                   MmapUtil.madvise(thisBuffer, MmapUtil.Madvice.RANDOM); | ||||
|                 } catch (IOException e) { | ||||
|                   if (!madviseFailed) { // log once
 | ||||
|                     LOGGER.info( | ||||
|                       "madvise not available on this system - node location lookup may be slower when less free RAM is available outside the JVM"); | ||||
|                     madviseFailed = true; | ||||
|                   } | ||||
|                 } | ||||
|               } | ||||
|               result[i++] = thisBuffer; | ||||
|             } | ||||
|             segments = result; | ||||
|           } catch (IOException e) { | ||||
|  | @ -87,27 +104,8 @@ abstract class AppendStoreMmap implements AppendStore { | |||
|       } | ||||
|       if (segments != null) { | ||||
|         try { | ||||
|           // attempt to force-unmap the file, so we can delete it later
 | ||||
|           // https://stackoverflow.com/questions/2972986/how-to-unmap-a-file-from-memory-mapped-using-filechannel-in-java
 | ||||
|           Class<?> unsafeClass; | ||||
|           try { | ||||
|             unsafeClass = Class.forName("sun.misc.Unsafe"); | ||||
|           } catch (Exception ex) { | ||||
|             unsafeClass = Class.forName("jdk.internal.misc.Unsafe"); | ||||
|           } | ||||
|           Method clean = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class); | ||||
|           clean.setAccessible(true); | ||||
|           Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe"); | ||||
|           theUnsafeField.setAccessible(true); | ||||
|           Object theUnsafe = theUnsafeField.get(null); | ||||
|           for (int i = 0; i < segments.length; i++) { | ||||
|             var buffer = segments[i]; | ||||
|             if (buffer != null) { | ||||
|               clean.invoke(theUnsafe, buffer); | ||||
|               segments[i] = null; | ||||
|             } | ||||
|           } | ||||
|         } catch (Exception e) { | ||||
|           MmapUtil.unmap(segments); | ||||
|         } catch (IOException e) { | ||||
|           LOGGER.info("Unable to unmap " + path + " " + e); | ||||
|         } | ||||
|         Arrays.fill(segments, null); | ||||
|  | @ -122,12 +120,12 @@ abstract class AppendStoreMmap implements AppendStore { | |||
| 
 | ||||
|   static class Ints extends AppendStoreMmap implements AppendStore.Ints { | ||||
| 
 | ||||
|     Ints(Path path) { | ||||
|       super(path); | ||||
|     Ints(Path path, boolean madvise) { | ||||
|       super(path, madvise); | ||||
|     } | ||||
| 
 | ||||
|     Ints(Path path, long segmentSizeBytes) { | ||||
|       super(path, segmentSizeBytes); | ||||
|     Ints(Path path, long segmentSizeBytes, boolean madvise) { | ||||
|       super(path, segmentSizeBytes, madvise); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | @ -158,12 +156,12 @@ abstract class AppendStoreMmap implements AppendStore { | |||
| 
 | ||||
|   static class Longs extends AppendStoreMmap implements AppendStore.Longs { | ||||
| 
 | ||||
|     Longs(Path path) { | ||||
|       super(path); | ||||
|     Longs(Path path, boolean madvise) { | ||||
|       super(path, madvise); | ||||
|     } | ||||
| 
 | ||||
|     Longs(Path path, long segmentSizeBytes) { | ||||
|       super(path, segmentSizeBytes); | ||||
|     Longs(Path path, long segmentSizeBytes, boolean madvise) { | ||||
|       super(path, segmentSizeBytes, madvise); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  |  | |||
|  | @ -40,16 +40,17 @@ public interface LongLongMap extends Closeable, MemoryEstimator.HasEstimate, Dis | |||
|    * @param name    which implementation to use: {@code "noop"}, {@code "sortedtable"} or {@code "sparsearray"} | ||||
|    * @param storage how to store data: {@code "ram"} or {@code "mmap"} | ||||
|    * @param path    where to store data (if mmap) | ||||
|    * @param madvise whether to use linux madvise random to improve read performance | ||||
|    * @return A longlong map instance | ||||
|    * @throws IllegalArgumentException if {@code name} or {@code storage} is not valid | ||||
|    */ | ||||
|   static LongLongMap from(String name, String storage, Path path) { | ||||
|   static LongLongMap from(String name, String storage, Path path, boolean madvise) { | ||||
|     boolean ram = isRam(storage); | ||||
| 
 | ||||
|     return switch (name) { | ||||
|       case "noop" -> noop(); | ||||
|       case "sortedtable" -> ram ? newInMemorySortedTable() : newDiskBackedSortedTable(path); | ||||
|       case "sparsearray" -> ram ? newInMemorySparseArray() : newDiskBackedSparseArray(path); | ||||
|       case "sortedtable" -> ram ? newInMemorySortedTable() : newDiskBackedSortedTable(path, madvise); | ||||
|       case "sparsearray" -> ram ? newInMemorySparseArray() : newDiskBackedSparseArray(path, madvise); | ||||
|       default -> throw new IllegalArgumentException("Unexpected value: " + name); | ||||
|     }; | ||||
|   } | ||||
|  | @ -125,11 +126,11 @@ public interface LongLongMap extends Closeable, MemoryEstimator.HasEstimate, Dis | |||
|   } | ||||
| 
 | ||||
|   /** Returns a memory-mapped longlong map that uses 12-bytes per node and binary search to find values. */ | ||||
|   static LongLongMap newDiskBackedSortedTable(Path dir) { | ||||
|   static LongLongMap newDiskBackedSortedTable(Path dir, boolean madvise) { | ||||
|     FileUtils.createDirectory(dir); | ||||
|     return new SortedTable( | ||||
|       new AppendStore.SmallLongs(i -> new AppendStoreMmap.Ints(dir.resolve("keys-" + i))), | ||||
|       new AppendStoreMmap.Longs(dir.resolve("values")) | ||||
|       new AppendStore.SmallLongs(i -> new AppendStoreMmap.Ints(dir.resolve("keys-" + i), madvise)), | ||||
|       new AppendStoreMmap.Longs(dir.resolve("values"), madvise) | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|  | @ -145,8 +146,8 @@ public interface LongLongMap extends Closeable, MemoryEstimator.HasEstimate, Dis | |||
|    * Returns a memory-mapped longlong map that uses 8-bytes per node and O(1) lookup but wastes space storing lots of | ||||
|    * 0's when the key space is fragmented. | ||||
|    */ | ||||
|   static LongLongMap newDiskBackedSparseArray(Path path) { | ||||
|     return new SparseArray(new AppendStoreMmap.Longs(path)); | ||||
|   static LongLongMap newDiskBackedSparseArray(Path path, boolean madvise) { | ||||
|     return new SparseArray(new AppendStoreMmap.Longs(path, madvise)); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  |  | |||
|  | @ -21,6 +21,7 @@ public record PlanetilerConfig( | |||
|   int sortMaxWriters, | ||||
|   String nodeMapType, | ||||
|   String nodeMapStorage, | ||||
|   boolean nodeMapMadvise, | ||||
|   String httpUserAgent, | ||||
|   Duration httpTimeout, | ||||
|   int httpRetries, | ||||
|  | @ -75,6 +76,8 @@ public record PlanetilerConfig( | |||
|       arguments | ||||
|         .getString("nodemap_type", "type of node location map: noop, sortedtable, or sparsearray", "sortedtable"), | ||||
|       arguments.getString("nodemap_storage", "storage for location map: mmap or ram", "mmap"), | ||||
|       arguments.getBoolean("nodemap_madvise", "use linux madvise(random) to improve memory-mapped read performance", | ||||
|         false), | ||||
|       arguments.getString("http_user_agent", "User-Agent header to set when downloading files over HTTP", | ||||
|         "Planetiler downloader (https://github.com/onthegomap/planetiler)"), | ||||
|       arguments.getDuration("http_timeout", "Timeout to use when downloading files over HTTP", "30s"), | ||||
|  |  | |||
|  | @ -0,0 +1,98 @@ | |||
| /* | ||||
| MIT License | ||||
| 
 | ||||
| Copyright (c) 2017 Upserve, Inc. | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
|  */ | ||||
| package com.onthegomap.planetiler.util; | ||||
| 
 | ||||
| import com.kenai.jffi.MemoryIO; | ||||
| import java.io.IOException; | ||||
| import java.nio.ByteBuffer; | ||||
| import jnr.ffi.LibraryLoader; | ||||
| import jnr.ffi.types.size_t; | ||||
| 
 | ||||
| /** | ||||
|  * Wrapper for native madvise function to be used via the public API | ||||
|  * {@link MmapUtil#madvise(ByteBuffer, MmapUtil.Madvice)}. | ||||
|  * <p> | ||||
|  * Ported from <a href= | ||||
|  * "https://github.com/upserve/uppend/blob/70967c6f24d7f1a3bbc18799f485d981da93f53b/src/main/java/com/upserve/uppend/blobs/NativeIO.java">upserve/uppend/NativeIO</a>. | ||||
|  * | ||||
|  * @see <a href="https://man7.org/linux/man-pages/man2/madvise.2.html">madvise(2) — Linux manual page</a> | ||||
|  */ | ||||
| class Madvise { | ||||
| 
 | ||||
|   private static final NativeC nativeC = LibraryLoader.create(NativeC.class).load("c"); | ||||
|   static int pageSize; | ||||
| 
 | ||||
|   static { | ||||
|     try { | ||||
|       pageSize = nativeC.getpagesize(); // 4096 on most Linux
 | ||||
|     } catch (UnsatisfiedLinkError e) { | ||||
|       pageSize = -1; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private static long alignedAddress(long address) { | ||||
|     return address & (-pageSize); | ||||
|   } | ||||
| 
 | ||||
|   private static long alignedSize(long address, int capacity) { | ||||
|     long end = address + capacity; | ||||
|     end = (end + pageSize - 1) & (-pageSize); | ||||
|     return end - alignedAddress(address); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Give a hint to the system how a mapped memory segment will be used so the OS can optimize performance. | ||||
|    * | ||||
|    * @param buffer The mapped memory segment. | ||||
|    * @param value  The advice to use. | ||||
|    * @throws IOException If an error occurs or madvise not available on this system | ||||
|    * @see <a href="https://man7.org/linux/man-pages/man2/madvise.2.html">madvise(2) — Linux manual page</a> | ||||
|    */ | ||||
|   static void madvise(ByteBuffer buffer, int value) throws IOException { | ||||
|     if (pageSize <= 0) { | ||||
|       throw new IOException("madvise failed, pagesize not available"); | ||||
|     } | ||||
|     final long address = MemoryIO.getInstance().getDirectBufferAddress(buffer); | ||||
|     final int capacity = buffer.capacity(); | ||||
| 
 | ||||
|     long alignedAddress = alignedAddress(address); | ||||
|     long alignedSize = alignedSize(alignedAddress, capacity); | ||||
|     try { | ||||
|       int val = nativeC.madvise(alignedAddress, alignedSize, value); | ||||
|       if (val != 0) { | ||||
|         throw new IOException(String.format("System call madvise failed with code: %d", val)); | ||||
|       } | ||||
|     } catch (UnsatisfiedLinkError error) { | ||||
|       throw new IOException("madvise failed", error); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** JNR-FFI will automatically compile these to wrappers around native functions with the same signatures. */ | ||||
|   public interface NativeC { | ||||
| 
 | ||||
|     int madvise(@size_t long address, @size_t long size, int advice); | ||||
| 
 | ||||
|     int getpagesize(); | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,87 @@ | |||
| package com.onthegomap.planetiler.util; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.lang.reflect.Field; | ||||
| import java.lang.reflect.Method; | ||||
| import java.nio.ByteBuffer; | ||||
| import java.nio.MappedByteBuffer; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| 
 | ||||
| /** | ||||
|  * Utilities for working with memory-mapped files. | ||||
|  */ | ||||
| public class MmapUtil { | ||||
| 
 | ||||
|   private static final Logger LOGGER = LoggerFactory.getLogger(MmapUtil.class); | ||||
| 
 | ||||
| 
 | ||||
|   /** Attempts to invoke native utility and logs an error message if not available. */ | ||||
|   public static void init() { | ||||
|     if (Madvise.pageSize < 0) { | ||||
|       try { | ||||
|         madvise(ByteBuffer.allocateDirect(1), Madvice.RANDOM); | ||||
|       } catch (IOException e) { | ||||
|         LOGGER.info("madvise not available on this system"); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Give a hint to the system how a mapped memory segment will be used so the OS can optimize performance. | ||||
|    * | ||||
|    * @param buffer The mapped memory segment. | ||||
|    * @param value  The advice to use. | ||||
|    * @throws IOException If an error occurs or madvise not available on this system | ||||
|    * @see <a href="https://man7.org/linux/man-pages/man2/madvise.2.html">madvise(2) — Linux manual page</a> | ||||
|    */ | ||||
|   public static void madvise(ByteBuffer buffer, Madvice value) throws IOException { | ||||
|     Madvise.madvise(buffer, value.value); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Attempt to force-unmap a list of memory-mapped file segments so it can safely be deleted. | ||||
|    * | ||||
|    * @param segments The segments to unmap | ||||
|    * @throws IOException If any error occurs unmapping the segment | ||||
|    */ | ||||
|   public static void unmap(MappedByteBuffer... segments) throws IOException { | ||||
|     try { | ||||
|       // attempt to force-unmap the file, so we can delete it later
 | ||||
|       // https://stackoverflow.com/questions/2972986/how-to-unmap-a-file-from-memory-mapped-using-filechannel-in-java
 | ||||
|       Class<?> unsafeClass; | ||||
|       try { | ||||
|         unsafeClass = Class.forName("sun.misc.Unsafe"); | ||||
|       } catch (Exception ex) { | ||||
|         unsafeClass = Class.forName("jdk.internal.misc.Unsafe"); | ||||
|       } | ||||
|       Method clean = unsafeClass.getMethod("invokeCleaner", ByteBuffer.class); | ||||
|       clean.setAccessible(true); | ||||
|       Field theUnsafeField = unsafeClass.getDeclaredField("theUnsafe"); | ||||
|       theUnsafeField.setAccessible(true); | ||||
|       Object theUnsafe = theUnsafeField.get(null); | ||||
|       for (MappedByteBuffer buffer : segments) { | ||||
|         if (buffer != null) { | ||||
|           clean.invoke(theUnsafe, buffer); | ||||
|         } | ||||
|       } | ||||
|     } catch (Exception e) { | ||||
|       throw new IOException("Unable to unmap", e); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** Values from https://man7.org/linux/man-pages/man2/madvise.2.html */ | ||||
|   public enum Madvice { | ||||
|     NORMAL(0), | ||||
|     RANDOM(1), | ||||
|     SEQUENTIAL(2), | ||||
|     WILLNEED(3), | ||||
|     DONTNEED(4); | ||||
| 
 | ||||
|     final int value; | ||||
| 
 | ||||
|     Madvice(int value) { | ||||
|       this.value = value; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -80,7 +80,7 @@ public class AppendStoreTest { | |||
| 
 | ||||
|     @BeforeEach | ||||
|     public void setup(@TempDir Path path) { | ||||
|       this.store = new AppendStoreMmap.Ints(path.resolve("ints"), 4 << 2); | ||||
|       this.store = new AppendStoreMmap.Ints(path.resolve("ints"), 4 << 2, true); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -96,7 +96,7 @@ public class AppendStoreTest { | |||
| 
 | ||||
|     @BeforeEach | ||||
|     public void setup(@TempDir Path path) { | ||||
|       this.store = new AppendStoreMmap.Longs(path.resolve("longs"), 4 << 2); | ||||
|       this.store = new AppendStoreMmap.Longs(path.resolve("longs"), 4 << 2, true); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | @ -104,7 +104,8 @@ public class AppendStoreTest { | |||
| 
 | ||||
|     @BeforeEach | ||||
|     public void setup(@TempDir Path path) { | ||||
|       this.store = new AppendStore.SmallLongs((i) -> new AppendStoreMmap.Ints(path.resolve("smalllongs" + i), 4 << 2)); | ||||
|       this.store = new AppendStore.SmallLongs( | ||||
|         (i) -> new AppendStoreMmap.Ints(path.resolve("smalllongs" + i), 4 << 2, true)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,45 @@ | |||
| package com.onthegomap.planetiler.util; | ||||
| 
 | ||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.nio.MappedByteBuffer; | ||||
| import java.nio.channels.FileChannel; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.nio.file.StandardOpenOption; | ||||
| import java.util.Locale; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.junit.jupiter.api.io.TempDir; | ||||
| 
 | ||||
| public class MmapUtilTest { | ||||
| 
 | ||||
|   @Test | ||||
|   public void testMadviseAndUnmap(@TempDir Path dir) throws IOException { | ||||
|     String osName = System.getProperty("os.name", "").toLowerCase(Locale.ROOT); | ||||
|     String data = "test"; | ||||
|     int bytes = data.getBytes(StandardCharsets.UTF_8).length; | ||||
|     var path = dir.resolve("file"); | ||||
|     Files.writeString(path, data, StandardOpenOption.CREATE, StandardOpenOption.WRITE); | ||||
|     try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) { | ||||
|       MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, bytes); | ||||
|       try { | ||||
|         MmapUtil.madvise(buffer, MmapUtil.Madvice.RANDOM); | ||||
|         byte[] received = new byte[bytes]; | ||||
|         buffer.get(received); | ||||
|         assertEquals(data, new String(received, StandardCharsets.UTF_8)); | ||||
|       } catch (IOException e) { | ||||
|         if (osName.startsWith("mac") || osName.startsWith("linux")) { | ||||
|           throw e; | ||||
|         } else { | ||||
|           System.out.println("madvise failed, but the system may not support it"); | ||||
|         } | ||||
|       } finally { | ||||
|         MmapUtil.unmap(buffer); | ||||
|       } | ||||
|     } finally { | ||||
|       Files.delete(path); | ||||
|     } | ||||
|   } | ||||
| } | ||||
		Ładowanie…
	
		Reference in New Issue
	
	 Michael Barry
						Michael Barry