001/* 002 * To change this template, choose Tools | Templates 003 * and open the template in the editor. 004 */ 005 006package com.nativelibs4java.opencl.util; 007 008import org.bridj.Pointer; 009import static org.bridj.Pointer.*; 010 011import java.io.IOException; 012import java.util.Random; 013import java.util.concurrent.ExecutorService; 014import java.util.concurrent.Executors; 015import java.util.concurrent.TimeUnit; 016import java.util.logging.Level; 017import java.util.logging.Logger; 018 019import com.nativelibs4java.opencl.CLBuildException; 020import com.nativelibs4java.opencl.CLContext; 021import com.nativelibs4java.opencl.CLEvent; 022import com.nativelibs4java.opencl.CLBuffer; 023import com.nativelibs4java.opencl.JavaCL; 024import com.nativelibs4java.opencl.CLQueue; 025import com.nativelibs4java.opencl.CLMem.Usage; 026 027/** 028 * Parallel Random numbers generator (<a href="http://en.wikipedia.org/wiki/Xorshift">Xorshift</a> adapted to work in parallel).<br> 029 * This class was designed as a drop-in replacement for java.util.Random (albeit with a more limited API) : 030 * <pre>{@code 031 * ParallelRandom r = new ParallelRandom(); 032 * r.setPreload(true); // faster 033 * while (true) { 034 * System.out.println(r.nextDouble()); 035 * } 036 * }</pre> 037 * <br> 038 * It is also possible to get entire batches of random integers with {@link ParallelRandom#next()} or {@link ParallelRandom#next(Pointer)}.<br> 039 * The preload feature precomputes a new batch in background as soon as one starts to consume numbers from the current batch. 040 * @author ochafik 041 */ 042public class ParallelRandom { 043 044 protected final XORShiftRandom randomProgram; 045 //private final IntBuffer outputBuffer; 046 //private IntBuffer mappedOutputBuffer; 047 protected final CLQueue queue; 048 protected final CLContext context; 049 protected final int parallelSize; 050 protected final int[] globalWorkSizes; 051 052 protected int consumedInts = 0; 053 054 boolean preload; 055 CLEvent preloadEvent; 056 protected CLBuffer<Integer> seeds, output; 057 Pointer<Integer> lastData; 058 boolean isDataFresh; 059 060 public ParallelRandom() throws IOException { 061 this(JavaCL.createBestContext().createDefaultQueue(), 32 * 1024, System.currentTimeMillis()); 062 } 063 public ParallelRandom(CLQueue queue, int parallelSize, final long seed) throws IOException { 064 try { 065 this.queue = queue; 066 this.context = queue.getContext(); 067 randomProgram = new XORShiftRandom(context); 068 this.parallelSize = parallelSize; 069 070 int seedsNeededByWorkItem = 4; 071 //int generatedNumbersByWorkItemIteration = 1; 072 int maxUnits = queue.getDevice().getMaxComputeUnits(); 073 int unitsFactor = maxUnits < 10 ? 1 : 16; 074 int scheduledWorkItems = maxUnits * unitsFactor; 075 //int countByWorkItem = parallelSize / scheduledWorkItems; 076 if (scheduledWorkItems > parallelSize / seedsNeededByWorkItem) { 077 scheduledWorkItems = parallelSize / seedsNeededByWorkItem; 078 scheduledWorkItems += parallelSize % seedsNeededByWorkItem; 079 } 080 //int iterationsByWorkItem = parallelCount / (generatedNumbersByWorkItemIteration * scheduledWorkItems); 081 globalWorkSizes = new int[] { scheduledWorkItems }; 082 083 //int lws = 1;//(int)queue.getDevice().getMaxWorkGroupSize(); 084 //if (lws > 32) 085 // lws = 32; 086 //localWorkSizes = new int[] { lws }; 087 088 randomProgram.getProgram().defineMacro("NUMBERS_COUNT", parallelSize); 089 randomProgram.getProgram().defineMacro("WORK_ITEMS_COUNT", scheduledWorkItems); 090 091 final int nSeeds = seedsNeededByWorkItem * parallelSize; 092 final Pointer<Integer> seedsBuf = allocateInts(nSeeds).order(context.getKernelsDefaultByteOrder()); 093 initSeeds(seedsBuf, seed); 094 //println(seedsBuf); 095 this.seeds = context.createBuffer(Usage.InputOutput, seedsBuf, true); 096 //this.lastOutputData = NIOUtils.directInts(parallelSize, context.getKernelsDefaultByteOrder()); 097 this.output = context.createBuffer(Usage.Output, Integer.class, parallelSize); 098 } catch (InterruptedException ex) { 099 Logger.getLogger(ParallelRandom.class.getName()).log(Level.SEVERE, null, ex); 100 throw new RuntimeException("Failed to initialized parallel random", ex); 101 } 102 } 103 104 static final int floatMask = 0x00ffffff; 105 static final double floatDivid = (1 << 24); 106 //static final int mask = (1 << 30) - 1; 107 //static final double divid = (1 << 30); 108 109 public int nextInt() { 110 waitForData(1); 111 return lastData.get(consumedInts++); 112 } 113 114 /** 115 * If true, a new batch of parallel random numbers is automatically precomputed in background as soon as one starts to consume numbers from the current batch (this gives faster random numbers, at the risk of computing more values than needed) 116 */ 117 public synchronized boolean isPreload() { 118 return preload; 119 } 120 /** 121 * If true, a new batch of parallel random numbers is automatically precomputed in background as soon as one starts to consume numbers from the current batch (this gives faster random numbers, at the risk of computing more values than needed) 122 */ 123 public synchronized void setPreload(boolean preload) throws CLBuildException { 124 this.preload = preload; 125 if (preload && preloadEvent == null) { 126 if (lastData == null) { 127 preloadEvent = randomProgram.gen_numbers(queue, seeds, output, globalWorkSizes, null); 128 } else if (consumedInts > 0) { 129 preload(); 130 } 131 } 132 } 133 private synchronized CLEvent preload() throws CLBuildException { 134 return preloadEvent = randomProgram.gen_numbers(queue, seeds, output, globalWorkSizes, null, preloadEvent); 135 } 136 private synchronized void waitForData(int n) { 137 try { 138 if (lastData == null) { 139 //lastOutputData = NIOUtils.directInts(parallelSize, context.getKernelsDefaultByteOrder()); 140 if (preloadEvent == null) 141 preloadEvent = randomProgram.gen_numbers(queue, seeds, output, globalWorkSizes, null); 142 143 readLastOutputData(); 144 } 145 if (consumedInts > parallelSize - n) { 146 preload().waitFor(); 147 consumedInts = 0; 148 readLastOutputData(); 149 } 150 if (preload && preloadEvent == null) 151 preload(); 152 } catch (CLBuildException ex) { 153 throw new RuntimeException(ex); 154 } 155 } 156 private synchronized void readLastOutputData() { 157 if (lastData == null) 158 lastData = output.read(queue, preloadEvent); 159 else 160 output.read(queue, lastData, true, preloadEvent); 161 preloadEvent = null; 162 } 163 public long nextLong() { 164 return (((long)nextInt()) << 32) | nextInt(); 165 } 166 167 private static final int intSignMask = 1 << 31; 168 public int nextInt(int n) { 169 if (n <= 0) 170 throw new IllegalArgumentException("n must be positive"); 171 172 if ((n & -n) == n) // i.e., n is a power of 2 173 return (int)((n * (long)(nextInt() & intSignMask)) >> 31); 174 175 int bits, val; 176 do { 177 bits = nextInt() & intSignMask; 178 val = bits % n; 179 } while (bits - val + (n-1) < 0); 180 return val; 181 } 182 183 public float nextFloat() { 184 return (float)((nextInt() & floatMask) / floatDivid); 185 } 186 187 private static final int doubleMask = (1 << 27) - 1; 188 private static final double doubleDivid = 1L << 53; 189 190 public double nextDouble() { 191 return (((long)(nextInt() & doubleMask) << 27) | (nextInt() & doubleMask)) / doubleDivid; 192 } 193 194 public CLBuffer<Integer> getSeeds() { 195 return seeds; 196 } 197 public CLQueue getQueue() { 198 return queue; 199 } 200 201 /** 202 * Number of random numbers generated at each call of {@link ParallelRandom#next() } or {@link ParallelRandom#next(Pointer) }<br> 203 * The numbers might not all be generated exactly in parallel, the level of parallelism is implementation-dependent. 204 * @return size of each buffer returned by {@link ParallelRandom#next() } 205 */ 206 public int getParallelSize() { 207 return parallelSize; 208 } 209 210 public synchronized CLEvent doNext() { 211 try { 212 //if (mappedOutputBuffer != null) { 213 // //output.unmap(queue, mappedOutputBuffer); 214 // mappedOutputBuffer = null; 215 //} 216 return randomProgram.gen_numbers(queue, seeds, //parallelSize, 217 output, globalWorkSizes, null); 218 } catch (CLBuildException ex) { 219 Logger.getLogger(ParallelRandom.class.getName()).log(Level.SEVERE, null, ex); 220 throw new RuntimeException("Failed to compile the random number generation routine", ex); 221 } 222 } 223 224 /** 225 * Copies the next {@link ParallelRandom#getParallelSize() } random integers in the provided output buffer 226 * @param output 227 */ 228 public synchronized void next(Pointer<Integer> output) { 229 CLEvent evt = doNext(); 230 this.output.read(queue, output, true, evt); 231 } 232 233 234 /** 235 * Returns a direct NIO buffer containing the next {@link ParallelRandom#getParallelSize() } random integers.<br> 236 * This buffer is read only and will only be valid until any of the "next" method is called again. 237 * @return output buffer of capacity ; see {@link ParallelRandom#getParallelSize() } 238 */ 239 public synchronized Pointer<Integer> next() { 240 CLEvent evt = doNext(); 241 //queue.finish(); evt = null; 242 //return outputBuffer; 243 //return (mappedOutputBuffer = output.map(queue, MapFlags.Read, evt)).asReadOnlyBuffer(); 244 return output.read(queue, evt); 245 } 246 247 private void initSeeds(final Pointer<Integer> seedsBuf, final long seed) throws InterruptedException { 248 final long nSeeds = seedsBuf.getValidElements(); 249 250 long start = System.nanoTime(); 251 252 // TODO benchmark threshold : 253 boolean parallelize = nSeeds > 10000; 254 //parallelize = false; 255 if (parallelize) { 256 Random random = new Random(seed); 257 for (long i = nSeeds; i-- != 0;) 258 seedsBuf.set(i, random.nextInt()); 259 } else { 260 // Parallelize seeds initialization 261 final int nThreads = Runtime.getRuntime().availableProcessors();// * 2; 262 ExecutorService service = Executors.newFixedThreadPool(nThreads); 263 for (int i = 0; i < nThreads; i++) { 264 final int iThread = i; 265 service.execute(new Runnable() { 266 267 public void run() { 268 long n = nSeeds / nThreads; 269 long offset = n * iThread; 270 Random random = new Random(seed + iThread);// * System.currentTimeMillis()); 271 if (iThread == nThreads - 1) 272 n += nSeeds - n * nThreads; 273 274 for (long i = n; i-- != 0;) 275 seedsBuf.set(offset++, random.nextInt()); 276 } 277 }); 278 } 279 service.shutdown(); 280 service.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); 281 } 282 283 long time = System.nanoTime() - start; 284 Logger.getLogger(ParallelRandom.class.getName()).log(Level.INFO, "Initialization of " + nSeeds + " seeds took " + (time/1000000) + " ms (" + (time / (double)nSeeds) + " ns per seed)"); 285 } 286}